diff --git a/CHANGELOG.md b/CHANGELOG.md index ce16faf..7afe708 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,184 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +## 4.0.0 (2025-12-29) + +### ⚠️ BREAKING CHANGES + +#### **`useFigmaToken` Export Changed** + +`useFigmaToken` is now a **named export** instead of a default export for consistency with the rest of the library: + +```tsx +// Before (3.x) - NO LONGER WORKS +import useFigmaToken from '@figma-vars/hooks' + +// After (4.0) - USE THIS +import { useFigmaToken } from '@figma-vars/hooks' +``` + +**Why?** Named exports enable better tree-shaking, align with other hooks in the library, and support future package splitting. + +### ✨ Added - New Utilities + +#### **`withRetry()` - Automatic Retry with Exponential Backoff** + +New utility for wrapping async operations with automatic retry logic, especially useful for rate-limited API calls: + +```ts +import { withRetry } from '@figma-vars/hooks' + +const fetchWithRetry = withRetry(() => fetcher('/api/endpoint', token), { + maxRetries: 3, + initialDelayMs: 1000, + backoffMultiplier: 2, + maxDelayMs: 30000, + retryOnlyRateLimits: true, // Only retry 429 errors + onRetry: (attempt, delayMs, error) => { + console.log(`Retry ${attempt} after ${delayMs}ms`) + }, +}) + +const data = await fetchWithRetry() +``` + +**Features:** + +- Respects `Retry-After` header from Figma API +- Configurable exponential backoff +- Optional callback for retry notifications +- Can retry all errors or only rate limits (429) + +#### **`redactToken()` - Safe Token Logging** + +New utility to safely redact Figma tokens for logging or display: + +```ts +import { redactToken } from '@figma-vars/hooks' + +redactToken('figd_abc123xyz789secret') +// Returns: 'figd_***...***cret' + +redactToken('short', { prefixLength: 2, suffixLength: 2 }) +// Returns: '***...***' (too short, fully redacted) + +redactToken(null) // Returns: '' +``` + +**Options:** + +- `prefixLength` - Characters to show at start (default: 5) +- `suffixLength` - Characters to show at end (default: 5) +- `redactionString` - Replacement string (default: `'***...***'`) + +#### **`baseUrl` Option for API Utilities** + +Both `fetcher` and `mutator` now accept a `baseUrl` option to override the default Figma API endpoint: + +```ts +import { fetcher, mutator } from '@figma-vars/hooks/core' + +// Use a mock server for testing +const data = await fetcher('/v1/files/KEY/variables/local', token, { + baseUrl: 'http://localhost:3000', +}) + +// Or use Figma Enterprise endpoint +await mutator('/v1/files/KEY/variables', token, 'UPDATE', payload, { + baseUrl: 'https://enterprise.figma.com', +}) +``` + +#### **`caseInsensitive` Option for `filterVariables()`** + +Added case-insensitive name matching to the `filterVariables` utility: + +```ts +import { filterVariables } from '@figma-vars/hooks' + +// Case-sensitive (default) +filterVariables(variables, { name: 'Primary' }) +// Matches: "Primary Color", not "primary color" + +// Case-insensitive +filterVariables(variables, { name: 'primary', caseInsensitive: true }) +// Matches: "Primary Color", "primary color", "PRIMARY" +``` + +### 🐛 Fixed - Critical Bug Fixes + +#### **SWR Key Caching Issue** + +Fixed a critical bug where fallback data could be cached under live API keys when both credentials and fallback were provided. Now: + +- If `fallbackFile` is provided, data is always cached under fallback-specific keys +- Prevents stale fallback data from blocking actual API calls +- Ensures cache consistency when switching between fallback and live modes + +#### **Improved Error Parsing for Non-JSON Responses** + +Fixed error handling when Figma API returns HTML or plain text errors (e.g., 502 Bad Gateway): + +```ts +// Before: Generic "An API error occurred" message +// After: Actual response body (truncated to 200 chars for HTML) +``` + +The `fetcher` and `mutator` now check `Content-Type` headers and parse errors appropriately. + +### 📚 Documentation + +#### **Mutation Return Type Semantics** + +Added comprehensive JSDoc documentation explaining mutation hook return values: + +- `mutate()` returns `Promise` +- On success: returns `TData` +- On error with `throwOnError: false` (default): returns `undefined`, error available in state +- On error with `throwOnError: true`: throws the error + +All mutation hooks (`useCreateVariable`, `useUpdateVariable`, `useDeleteVariable`, `useBulkUpdateVariables`) now have detailed examples showing all three usage patterns. + +### 🔧 Changed + +- **Named Exports**: `useFigmaToken` changed from default to named export (see Breaking Changes) +- **Coverage Comments**: Changed `/* istanbul ignore next */` to `/* c8 ignore next */` for proper Vitest V8 coverage exclusion + +### 🎯 Migration Guide (3.x → 4.0) + +**1. Update `useFigmaToken` import (required if used):** + +```tsx +// Find this in your code: +import useFigmaToken from '@figma-vars/hooks' + +// Replace with: +import { useFigmaToken } from '@figma-vars/hooks' +``` + +**2. New utilities are opt-in:** + +- Use `withRetry()` to add automatic retry logic to API calls +- Use `redactToken()` before logging tokens +- Use `baseUrl` option when testing with mock servers +- Use `caseInsensitive: true` for flexible variable filtering + +**3. Automatic improvements (no action needed):** + +- SWR caching now works correctly with fallback + live credentials +- Better error messages for non-JSON API responses +- Improved documentation for mutation return types + +### 🙏 Acknowledgments + +This release addresses issues identified through a comprehensive Codex audit. All 25 audit items have been validated and resolved where applicable. + +## 3.1.1 (2025-12-28) + +### 📚 Documentation + +- Minor documentation file updates + ## 3.1.0 (2025-12-27) ### 🐛 Fixed - Critical TypeScript & Runtime Issues diff --git a/README.md b/README.md index bd742d7..2bce9e9 100644 --- a/README.md +++ b/README.md @@ -10,24 +10,25 @@ A fast, typed React 19.2.3 hooks library for the Figma Variables API: fetch, upd Built for the modern web, this library provides a suite of hooks to fetch, manage, and mutate your design tokens/variables, making it easy to sync them between Figma and your React applications, Storybooks, or design system dashboards. -![Status](https://img.shields.io/badge/status-stable-brightgreen) -![CI](https://github.com/marklearst/figma-vars-hooks/actions/workflows/ci.yml/badge.svg) -[![codecov](https://codecov.io/gh/marklearst/figma-vars-hooks/branch/main/graph/badge.svg)](https://codecov.io/gh/marklearst/figma-vars-hooks) -![Test Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen) -![License](https://img.shields.io/github/license/marklearst/figma-vars-hooks) -![GitHub last commit](https://img.shields.io/github/last-commit/marklearst/figma-vars-hooks) -![GitHub code size](https://img.shields.io/github/languages/code-size/marklearst/figma-vars-hooks) - -## 📌 Why 3.1.1 - -- ✨ **New DX Features**: SWR configuration support, error handling utilities, cache invalidation helpers -- 🔧 **React 19.2 Ready**: Optimized hooks with proper cleanup and stable function references -- 🛡️ **Better Error Handling**: `FigmaApiError` class with HTTP status codes for better error differentiation -- ✅ **Type Safety**: Removed unsafe type assertions, improved type definitions throughout -- 🚀 **Performance**: Hardened SWR usage (stable keys, `null` to disable, cleaner fallback handling) +| Package | Quality | Activity | +| --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| [![npm version](https://img.shields.io/npm/v/%40figma-vars%2Fhooks)](https://www.npmjs.com/package/@figma-vars/hooks) | ![CI](https://github.com/marklearst/figma-vars-hooks/actions/workflows/ci.yml/badge.svg) | ![GitHub last commit](https://img.shields.io/github/last-commit/marklearst/figma-vars-hooks) | +| ![npm downloads](https://img.shields.io/npm/dm/%40figma-vars%2Fhooks) | [![codecov](https://codecov.io/gh/marklearst/figma-vars-hooks/branch/main/graph/badge.svg)](https://codecov.io/gh/marklearst/figma-vars-hooks) | ![Status](https://img.shields.io/badge/status-stable-brightgreen) | +| ![bundle size](https://img.shields.io/bundlephobia/minzip/%40figma-vars%2Fhooks) | ![Test Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen) | ![License](https://img.shields.io/github/license/marklearst/figma-vars-hooks) | +| ![node version](https://img.shields.io/node/v/%40figma-vars%2Fhooks) | ![TypeScript](https://img.shields.io/badge/TypeScript-100%25_Strict-blue?logo=typescript) | | + +## 📌 Why 4.0.0 + +- ✨ **New Utilities**: `withRetry()` for automatic retry with exponential backoff, `redactToken()` for safe logging +- 🔧 **Flexible API**: `baseUrl` option for fetcher/mutator, `caseInsensitive` option for filterVariables +- 🛡️ **Better Error Handling**: Improved parsing for non-JSON API responses (HTML, plain text) +- 🐛 **Critical Bug Fix**: SWR cache keys now correctly separate fallback and live data +- 📚 **Improved Docs**: Comprehensive mutation return type documentation with examples - 📦 **Modern Tooling**: Node 20+ toolchain, strict TypeScript, and ESM-first packaging with CJS interop - 🖥️ **CLI Export Tool**: Automate variable exports with `figma-vars-export` for CI/CD (Enterprise required) +> ⚠️ **Breaking Change**: `useFigmaToken` is now a named export. See [Migration Guide](#-migration-guide-3x--40). + ## 🚀 Features at a Glance - **Modern React 19.2 hooks** for variables, collections, modes, and published variables @@ -424,11 +425,13 @@ Customize SWR behavior globally through the provider: ### Utilities -- **Filtering**: `filterVariables` (filter by type, name, etc.) -- **Error Handling**: `isFigmaApiError`, `getErrorStatus`, `getErrorMessage`, `hasErrorStatus` +- **Filtering**: `filterVariables` (filter by type, name, with optional `caseInsensitive` matching) +- **Retry**: `withRetry` (automatic retry with exponential backoff for rate limits) +- **Security**: `redactToken` (safely redact tokens for logging/display) +- **Error Handling**: `isFigmaApiError`, `getErrorStatus`, `getErrorMessage`, `hasErrorStatus`, `isRateLimited`, `getRetryAfter` - **Type Guards**: `isLocalVariablesResponse`, `isPublishedVariablesResponse`, `validateFallbackData` (runtime validation) - **SWR Keys**: `getVariablesKey`, `getPublishedVariablesKey`, `getInvalidationKeys` (centralized cache key construction) -- **Core helpers**: `fetcher`, `mutator`, constants for endpoints and headers +- **Core helpers**: `fetcher`, `mutator` (with `baseUrl` option), constants for endpoints and headers ### Types @@ -454,13 +457,30 @@ Customize SWR behavior globally through the provider: - Never commit PATs or file keys to git, Storybook static builds, or client bundles. - Use environment variables (`process.env` / `import.meta.env`) and secret managers; keep them server-side where possible. - Prefer `fallbackFile` with `token={null}`/`fileKey={null}` for demos and public Storybooks. -- Avoid logging tokens or keys; scrub them from error messages and analytics. +- Use `redactToken()` when logging tokens for debugging: + +```ts +import { redactToken } from '@figma-vars/hooks' + +// Safe logging +console.log('Using token:', redactToken(token)) +// Output: "Using token: figd_***...***cret" +``` ## 📈 Rate Limits - Figma enforces per-token limits. Rely on SWR/TanStack caching, avoid unnecessary refetches, and prefer fallback JSON for static sites. - Use `swrConfig` to customize `dedupingInterval` and `errorRetryCount` to optimize API usage. -- Handle `429` rate limit errors with `isFigmaApiError` and implement exponential backoff if needed. +- Use `withRetry()` utility for automatic retry with exponential backoff on 429 errors: + +```ts +import { withRetry, fetcher } from '@figma-vars/hooks' + +const fetchWithRetry = withRetry(() => fetcher('/v1/files/KEY/variables/local', token), { + maxRetries: 3, + onRetry: (attempt, delay) => console.log(`Retry ${attempt}...`), +}) +``` ## 📚 Storybook & Next.js @@ -511,11 +531,50 @@ export function Providers({ children }: { children: React.ReactNode }) { - `pnpm run build`, `pnpm test`, `pnpm run test:coverage` - `pnpm run check:publint`, `pnpm run check:attw`, `pnpm run check:size` -## 🧭 Release Checklist (for 3.1.0) +## 🧭 Release Checklist (for 4.0.0) - Run `pnpm run check:release` -- Tag `v3.1.0` (CI publishes to npm) -- Update dist-tags on npm if needed (`latest` → 3.1.0) +- Run `pnpm version major` (creates `v4.0.0` tag) +- CI publishes to npm automatically +- Update dist-tags on npm if needed (`latest` → 4.0.0) + +## 🔄 Migration Guide (3.x → 4.0) + +### Breaking Change: `useFigmaToken` Export + +```tsx +// Before (3.x) - NO LONGER WORKS +import useFigmaToken from '@figma-vars/hooks' + +// After (4.0) - USE THIS +import { useFigmaToken } from '@figma-vars/hooks' +``` + +### New Utilities (opt-in) + +```ts +import { withRetry, redactToken, filterVariables } from '@figma-vars/hooks' + +// Automatic retry with exponential backoff +const fetchWithRetry = withRetry(() => myApiCall(), { maxRetries: 3 }) + +// Safe token logging +console.log('Token:', redactToken(token)) // "figd_***...***cret" + +// Case-insensitive filtering +filterVariables(vars, { name: 'primary', caseInsensitive: true }) +``` + +### Custom API Base URL + +```ts +import { fetcher, mutator } from '@figma-vars/hooks/core' + +// Use mock server for testing +await fetcher('/v1/files/KEY/variables/local', token, { + baseUrl: 'http://localhost:3000', +}) +``` ---