From 79f46e431544ca45594d11c6aa06d852a9476b6e Mon Sep 17 00:00:00 2001 From: Tom Larcher Date: Tue, 9 Dec 2025 23:27:27 +1100 Subject: [PATCH] feat: add Figma-native export functionality with flexible output options and update plugin version to `0.3.0` --- README.md | 99 +- manifest.json | 3 +- package-lock.json | 2250 +++++++---------- package.json | 4 +- src/constants.ts | 1 + src/export/buildDtcgJson.ts | 63 +- src/export/buildExportBundle.ts | 87 + src/export/buildFigmaNativeExport.ts | 517 ++++ src/export/buildVariablesJson.ts | 203 -- src/export/valueNormalization.ts | 44 + src/github/githubClient.ts | 236 +- src/messaging.ts | 26 +- src/plugin.ts | 564 ++--- src/shared/figma-native-types.ts | 63 + src/shared/types.ts | 134 - src/types/export.ts | 39 + src/ui/components/export/StatusSummary.tsx | 33 +- src/ui/components/preview/DiffViewer.tsx | 517 ++-- src/ui/components/preview/JsonViewer.tsx | 181 +- .../settings/ExportOptionsDisclosure.tsx | 94 + src/ui/components/settings/SettingsTab.tsx | 5 + src/ui/context/PluginContext.tsx | 65 +- src/util/dtcgUtils.ts | 10 +- src/util/path.ts | 9 + src/util/slugify.ts | 11 + tests/dtcgUtils.test.ts | 2 +- tests/figmaNativeExport.test.ts | 132 + 27 files changed, 2984 insertions(+), 2408 deletions(-) create mode 100644 src/constants.ts create mode 100644 src/export/buildExportBundle.ts create mode 100644 src/export/buildFigmaNativeExport.ts delete mode 100644 src/export/buildVariablesJson.ts create mode 100644 src/export/valueNormalization.ts create mode 100644 src/shared/figma-native-types.ts delete mode 100644 src/shared/types.ts create mode 100644 src/types/export.ts create mode 100644 src/ui/components/settings/ExportOptionsDisclosure.tsx create mode 100644 src/util/path.ts create mode 100644 src/util/slugify.ts create mode 100644 tests/figmaNativeExport.test.ts diff --git a/README.md b/README.md index 02c0de0..fb7b7ba 100644 --- a/README.md +++ b/README.md @@ -15,17 +15,19 @@ ![DTCG](https://img.shields.io/badge/DTCG-v2025.10-blue) ![Code Style](https://img.shields.io/badge/code_style-prettier-ff69b4.svg) -A Figma plugin that extracts design variables (colors, typography, spacing, etc.) and commits them as [DTCG-compliant](https://www.designtokens.org/) JSON to a GitHub repository. +A Figma plugin that extracts design variables (colors, typography, spacing, etc.) and commits them to GitHub as either [DTCG-compliant](https://www.designtokens.org/) design tokens or a Figma-native JSON structure that mirrors the Variables UI. ## Features - **DTCG-Compliant Export**: Exports design tokens following the [Design Tokens Community Group (DTCG) specification v2025.10](https://www.designtokens.org/tr/2025.10/) +- **Figma-Native Export**: Generate JSON that preserves collections, groups, modes, and IDs exactly as shown in the Variables table (per collection or aggregated) +- **Flexible Output**: Export a single JSON or automatically split one file per collection with deterministic file naming - **One-Click Export**: Extract all Figma variables with full metadata - **Tab-Based Interface**: Organized UI with Export, Preview, and Settings tabs - **Direct GitHub Integration**: Commit directly to any branch (creates branch if needed) - **Smart Change Detection**: SHA-256 content hashing prevents redundant commits - **Diff Preview**: See token-level changes before committing (added/modified/removed) -- **JSON Preview**: View and copy exported DTCG JSON directly in the plugin +- **JSON Preview**: View and copy any exported JSON document (DTCG or Figma-native) directly in the plugin - **Dry Run Mode**: Test without committing to verify changes - **Automatic Conflict Resolution**: Auto-retry on 409 conflicts - **Remote Variable Support**: Handles references to external/library variables @@ -49,15 +51,15 @@ A Figma plugin that extracts design variables (colors, typography, spacing, etc. > [!TIP] > The plugin will remember your settings between sessions. Use the "Settings" tab to configure your repository and save your GitHub token for easy reuse. -## What's in this package? +## Release Bundle Contents -This development package contains three files: +Each release ships as a folder containing `manifest.json` plus a compiled `dist/` directory. Keep the structure intact when importing the plugin: -- `manifest.json` - Plugin configuration (required for import) -- `plugin.js` - The plugin's backend code -- `index.html` - The plugin's user interface +- `manifest.json` – References the compiled assets inside `dist/` +- `dist/plugin.js` – Bundled plugin code that runs in the Figma sandbox +- `dist/index.html` (and related assets) – Bundled UI served inside the plugin iframe -All three files must stay together in the same folder. +If you build from source (`npm run build`), the same files are generated locally before packaging. ## Updating @@ -69,7 +71,7 @@ When a new version is available: ## JSON Output Schema -The plugin exports design tokens in [DTCG (Design Tokens Community Group) format v2025.10](https://www.designtokens.org/tr/2025.10/), a standardized format for exchanging design tokens between tools. +FigGit can export either [DTCG (Design Tokens Community Group) format v2025.10](https://www.designtokens.org/tr/2025.10/) or a Figma-native JSON structure that mirrors the Variables table. Choose the format and document strategy (single file vs per-collection) from **Settings → Export Options**. ### DTCG Format Structure @@ -152,6 +154,77 @@ Example DTCG export: | `boolean` | BOOLEAN | `true` | | `fontFamily` | STRING (font context) | `"Inter"` | +### Figma-Native Format Structure + +The Figma-native export mirrors collections, groups, and variables exactly as they appear in the Variables table. Each JSON document includes deterministic metadata so change detection and Git diffs continue to work even when splitting into multiple files. + +- **Top-level metadata**: `collectionsCount`, `variablesCount`, `contentHash`, `exportedAt`, `fileName`, `pluginVersion`, `exportFormat`, `exportType` +- **Per-collection docs**: include `collectionId`, `collectionName`, and `collectionVariablesCount` +- **Groups**: Real Variable groups (e.g., `Brand / Surfacing / Primary`) preserved as nested `groups` +- **Variables**: Carry Figma IDs, names (leaf node), full `path`, scopes, code syntax, and per-mode `valueByMode` +- **Modes**: Stored once per collection and referenced via `modeId` + +Example Figma-native export (per collection): + +```json +{ + "collectionsCount": 1, + "variablesCount": 12, + "contentHash": "c19f6a...", + "exportedAt": "2025-12-03T23:21:11.219Z", + "fileName": "Katalyst Design System", + "pluginVersion": "0.2.0", + "exportFormat": "figma-native", + "exportType": "perCollection", + "collectionId": "889:34", + "collectionName": "Core Colors", + "collections": [ + { + "id": "889:34", + "name": "Core Colors", + "modes": [ + { "id": "889:56", "name": "Light" }, + { "id": "889:57", "name": "Dark" } + ], + "groups": [ + { + "id": "889:34:brand", + "name": "Brand", + "path": "Core Colors/Brand", + "groups": [], + "variables": [ + { + "id": "var-brand-primary", + "name": "Primary", + "path": "Brand/Primary", + "type": "color", + "valueByMode": { + "889:56": { "value": "#0F62FE" }, + "889:57": { "value": "#78A9FF" } + } + } + ] + } + ], + "variables": [ + { + "id": "var-brand-primary", + "name": "Primary", + "path": "Brand/Primary", + "type": "color", + "valueByMode": { + "889:56": { "value": "#0F62FE" }, + "889:57": { "value": "#78A9FF" } + } + } + ] + } + ] +} +``` + +Use **single-file** export when you want one consolidated JSON, or switch to **per-collection** to create deterministic files such as `tokens/core-colors.json`, `tokens/spacing.json`, etc. + ## Installation ### Requirements @@ -232,7 +305,7 @@ Extract and commit your design tokens: - Click "Commit to GitHub" (or "Dry Run" if enabled) - If variables haven't changed, commit is automatically skipped -- If committed, you'll see a link to the updated file +- If committed, you'll see a link to the GitHub commit that contains the updated file(s) ### Preview Tab @@ -240,7 +313,7 @@ Review your export before committing: #### Review Changes -- View the exported DTCG JSON (expandable, with copy to clipboard) +- Browse each exported JSON document (expandable, with copy to clipboard) - Open "Diff Viewer" to see token-level changes (added/removed/changed) - Review changes before committing @@ -310,13 +383,11 @@ src/ ├── messaging.ts # Type-safe UI ↔ Plugin communication ├── export/ │ ├── buildDtcgJson.ts # DTCG-compliant variable extraction -│ ├── buildVariablesJson.ts # Legacy format (deprecated) │ └── hash.ts # Pure JavaScript SHA-256 implementation ├── github/ │ └── githubClient.ts # GitHub API client with base64 encoding ├── shared/ -│ ├── dtcg-types.ts # DTCG type definitions -│ └── types.ts # Legacy type definitions +│ └── dtcg-types.ts # DTCG type definitions ├── ui/ │ ├── index.tsx # Preact UI entry point │ ├── components/ # UI components diff --git a/manifest.json b/manifest.json index 2bc1bd5..6edec5b 100644 --- a/manifest.json +++ b/manifest.json @@ -4,7 +4,8 @@ "api": "1.0.0", "main": "dist/plugin.js", "ui": "dist/index.html", - "editorType": ["figma"], + "editorType": ["figma","dev"], + "capabilities": ["inspect", "vscode"], "networkAccess": { "allowedDomains": ["https://api.github.com"], "reasoning": "Need to push variable JSON to GitHub repository contents API" diff --git a/package-lock.json b/package-lock.json index 4984934..36445ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "figgit", - "version": "0.1.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "figgit", - "version": "0.1.0", + "version": "0.3.0", "license": "MIT", "dependencies": { "@create-figma-plugin/ui": "^4.0.3", @@ -25,7 +25,7 @@ "@vitest/coverage-v8": "^4.0.7", "@vitest/ui": "^4.0.7", "concurrently": "^9.2.1", - "esbuild": "0.21.5", + "esbuild": "0.25.0", "eslint": "^8.57.1", "husky": "^9.1.7", "jsdom": "^27.1.0", @@ -39,9 +39,9 @@ } }, "node_modules/@acemir/cssom": { - "version": "0.9.19", - "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.19.tgz", - "integrity": "sha512-Pp2gAQXPZ2o7lt4j0IMwNRXqQ3pagxtDj5wctL5U2Lz4oV0ocDNlkgx4DpxfyKav4S/bePuI+SMqcBSUHLy9kg==", + "version": "0.9.28", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.28.tgz", + "integrity": "sha512-LuS6IVEivI75vKN8S04qRD+YySP0RmU/cV8UNukhQZvprxF+76Z43TNo/a08eCodaGhT1Us8etqS1ZRY9/Or0A==", "dev": true, "license": "MIT" }, @@ -53,9 +53,9 @@ "license": "MIT" }, "node_modules/@asamuzakjp/css-color": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz", - "integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.0.tgz", + "integrity": "sha512-9xiBAtLn4aNsa4mDnpovJvBn72tNEIACyvlqaNJ+ADemR+yeMJWnBudOi2qGDviJa7SwcDOU/TRh5dnET7qk0w==", "dev": true, "license": "MIT", "dependencies": { @@ -63,23 +63,23 @@ "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", - "lru-cache": "^11.2.1" + "lru-cache": "^11.2.2" } }, "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } }, "node_modules/@asamuzakjp/dom-selector": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.4.tgz", - "integrity": "sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==", + "version": "6.7.6", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.6.tgz", + "integrity": "sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==", "dev": true, "license": "MIT", "dependencies": { @@ -87,15 +87,15 @@ "bidi-js": "^1.0.3", "css-tree": "^3.1.0", "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.2" + "lru-cache": "^11.2.4" } }, "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } @@ -569,9 +569,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.15.tgz", - "integrity": "sha512-q0p6zkVq2lJnmzZVPR33doA51G7YOja+FBvRdp5ISIthL0MtFCgYHHhR563z9WFGxcOn0WfjSkPDJ5Qig3H3Sw==", + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.14.tgz", + "integrity": "sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==", "dev": true, "funding": [ { @@ -586,6 +586,9 @@ "license": "MIT-0", "engines": { "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, "node_modules/@csstools/css-tokenizer": { @@ -610,9 +613,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", + "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", "cpu": [ "ppc64" ], @@ -623,13 +626,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", + "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", "cpu": [ "arm" ], @@ -640,13 +643,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", + "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", "cpu": [ "arm64" ], @@ -657,13 +660,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", + "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", "cpu": [ "x64" ], @@ -674,13 +677,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", + "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", "cpu": [ "arm64" ], @@ -691,13 +694,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", + "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", "cpu": [ "x64" ], @@ -708,13 +711,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", + "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", "cpu": [ "arm64" ], @@ -725,13 +728,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", + "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", "cpu": [ "x64" ], @@ -742,13 +745,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", + "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", "cpu": [ "arm" ], @@ -759,13 +762,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", + "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", "cpu": [ "arm64" ], @@ -776,13 +779,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", + "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", "cpu": [ "ia32" ], @@ -793,13 +796,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", + "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", "cpu": [ "loong64" ], @@ -810,13 +813,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", + "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", "cpu": [ "mips64el" ], @@ -827,13 +830,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", + "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", "cpu": [ "ppc64" ], @@ -844,13 +847,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", + "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", "cpu": [ "riscv64" ], @@ -861,13 +864,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", + "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", "cpu": [ "s390x" ], @@ -878,13 +881,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", + "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", "cpu": [ "x64" ], @@ -895,13 +898,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", + "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", "cpu": [ "arm64" ], @@ -916,9 +919,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", + "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", "cpu": [ "x64" ], @@ -929,13 +932,13 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", + "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", "cpu": [ "arm64" ], @@ -950,9 +953,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", + "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", "cpu": [ "x64" ], @@ -963,13 +966,13 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz", + "integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==", "cpu": [ "arm64" ], @@ -984,9 +987,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", + "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", "cpu": [ "x64" ], @@ -997,13 +1000,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", + "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", "cpu": [ "arm64" ], @@ -1014,13 +1017,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", + "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", "cpu": [ "ia32" ], @@ -1031,13 +1034,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", + "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", "cpu": [ "x64" ], @@ -1048,7 +1051,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -1353,9 +1356,9 @@ "license": "MIT" }, "node_modules/@prefresh/core": { - "version": "1.5.8", - "resolved": "https://registry.npmjs.org/@prefresh/core/-/core-1.5.8.tgz", - "integrity": "sha512-T7HMpakS1iPVCFZvfDLMGyrWAcO3toUN9/RkJUqqoRr/vNhQrZgHjidfhq3awDzAQtw1emDWH8dsOeu0DWqtgA==", + "version": "1.5.9", + "resolved": "https://registry.npmjs.org/@prefresh/core/-/core-1.5.9.tgz", + "integrity": "sha512-IKBKCPaz34OFVC+adiQ2qaTF5qdztO2/4ZPf4KsRTgjKosWqxVXmEbxCiUydYZRY8GVie+DQlKzQr9gt6HQ+EQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1401,30 +1404,10 @@ "node": ">= 8.0.0" } }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/pluginutils/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", - "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", "cpu": [ "arm" ], @@ -1436,9 +1419,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", - "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", "cpu": [ "arm64" ], @@ -1450,9 +1433,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", - "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", "cpu": [ "arm64" ], @@ -1464,9 +1447,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", - "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", "cpu": [ "x64" ], @@ -1478,9 +1461,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", - "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", "cpu": [ "arm64" ], @@ -1492,9 +1475,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", - "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", "cpu": [ "x64" ], @@ -1506,9 +1489,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", - "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", "cpu": [ "arm" ], @@ -1520,9 +1503,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", - "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", "cpu": [ "arm" ], @@ -1534,9 +1517,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", - "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", "cpu": [ "arm64" ], @@ -1548,9 +1531,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", - "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", "cpu": [ "arm64" ], @@ -1562,9 +1545,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", - "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", "cpu": [ "loong64" ], @@ -1576,9 +1559,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", - "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", "cpu": [ "ppc64" ], @@ -1590,9 +1573,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", - "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", "cpu": [ "riscv64" ], @@ -1604,9 +1587,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", - "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", "cpu": [ "riscv64" ], @@ -1618,9 +1601,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", - "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", "cpu": [ "s390x" ], @@ -1632,9 +1615,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", - "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", "cpu": [ "x64" ], @@ -1646,9 +1629,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", - "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", "cpu": [ "x64" ], @@ -1660,9 +1643,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", - "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", "cpu": [ "arm64" ], @@ -1674,9 +1657,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", - "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", "cpu": [ "arm64" ], @@ -1688,9 +1671,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", - "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", "cpu": [ "ia32" ], @@ -1702,9 +1685,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", - "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", "cpu": [ "x64" ], @@ -1716,9 +1699,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", - "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", "cpu": [ "x64" ], @@ -1838,9 +1821,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", - "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", + "version": "24.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.2.tgz", + "integrity": "sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==", "dev": true, "license": "MIT", "peer": true, @@ -1856,18 +1839,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.3.tgz", - "integrity": "sha512-sbaQ27XBUopBkRiuY/P9sWGOWUW4rl8fDoHIUmLpZd8uldsTyB4/Zg6bWTegPoTLnKj9Hqgn3QD6cjPNB32Odw==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.49.0.tgz", + "integrity": "sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.46.3", - "@typescript-eslint/type-utils": "8.46.3", - "@typescript-eslint/utils": "8.46.3", - "@typescript-eslint/visitor-keys": "8.46.3", - "graphemer": "^1.4.0", + "@typescript-eslint/scope-manager": "8.49.0", + "@typescript-eslint/type-utils": "8.49.0", + "@typescript-eslint/utils": "8.49.0", + "@typescript-eslint/visitor-keys": "8.49.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" @@ -1880,27 +1862,26 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.46.3", + "@typescript-eslint/parser": "^8.49.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.3.tgz", - "integrity": "sha512-f/NvtRjOm80BtNM5OQtlaBdM5BRFUv7gf381j9wygDNL+qOYSNOgtQ/DCndiYi80iIOv76QqaTmp4fa9hwI0OA==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.49.0.tgz", + "integrity": "sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.46.3", - "@typescript-eslint/tsconfig-utils": "8.46.3", - "@typescript-eslint/types": "8.46.3", - "@typescript-eslint/visitor-keys": "8.46.3", + "@typescript-eslint/project-service": "8.49.0", + "@typescript-eslint/tsconfig-utils": "8.49.0", + "@typescript-eslint/types": "8.49.0", + "@typescript-eslint/visitor-keys": "8.49.0", "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", + "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "engines": { @@ -1915,16 +1896,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.3.tgz", - "integrity": "sha512-VXw7qmdkucEx9WkmR3ld/u6VhRyKeiF1uxWwCy/iuNfokjJ7VhsgLSOTjsol8BunSw190zABzpwdNsze2Kpo4g==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.49.0.tgz", + "integrity": "sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.46.3", - "@typescript-eslint/types": "8.46.3", - "@typescript-eslint/typescript-estree": "8.46.3" + "@typescript-eslint/scope-manager": "8.49.0", + "@typescript-eslint/types": "8.49.0", + "@typescript-eslint/typescript-estree": "8.49.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1968,17 +1949,17 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.3.tgz", - "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.49.0.tgz", + "integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.46.3", - "@typescript-eslint/types": "8.46.3", - "@typescript-eslint/typescript-estree": "8.46.3", - "@typescript-eslint/visitor-keys": "8.46.3", + "@typescript-eslint/scope-manager": "8.49.0", + "@typescript-eslint/types": "8.49.0", + "@typescript-eslint/typescript-estree": "8.49.0", + "@typescript-eslint/visitor-keys": "8.49.0", "debug": "^4.3.4" }, "engines": { @@ -1994,21 +1975,20 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.3.tgz", - "integrity": "sha512-f/NvtRjOm80BtNM5OQtlaBdM5BRFUv7gf381j9wygDNL+qOYSNOgtQ/DCndiYi80iIOv76QqaTmp4fa9hwI0OA==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.49.0.tgz", + "integrity": "sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.46.3", - "@typescript-eslint/tsconfig-utils": "8.46.3", - "@typescript-eslint/types": "8.46.3", - "@typescript-eslint/visitor-keys": "8.46.3", + "@typescript-eslint/project-service": "8.49.0", + "@typescript-eslint/tsconfig-utils": "8.49.0", + "@typescript-eslint/types": "8.49.0", + "@typescript-eslint/visitor-keys": "8.49.0", "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", + "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "engines": { @@ -2052,14 +2032,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.3.tgz", - "integrity": "sha512-Fz8yFXsp2wDFeUElO88S9n4w1I4CWDTXDqDr9gYvZgUpwXQqmZBr9+NTTql5R3J7+hrJZPdpiWaB9VNhAKYLuQ==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.49.0.tgz", + "integrity": "sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.46.3", - "@typescript-eslint/types": "^8.46.3", + "@typescript-eslint/tsconfig-utils": "^8.49.0", + "@typescript-eslint/types": "^8.49.0", "debug": "^4.3.4" }, "engines": { @@ -2074,14 +2054,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.3.tgz", - "integrity": "sha512-FCi7Y1zgrmxp3DfWfr+3m9ansUUFoy8dkEdeQSgA9gbm8DaHYvZCdkFRQrtKiedFf3Ha6VmoqoAaP68+i+22kg==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.49.0.tgz", + "integrity": "sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.3", - "@typescript-eslint/visitor-keys": "8.46.3" + "@typescript-eslint/types": "8.49.0", + "@typescript-eslint/visitor-keys": "8.49.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2092,9 +2072,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.3.tgz", - "integrity": "sha512-GLupljMniHNIROP0zE7nCcybptolcH8QZfXOpCfhQDAdwJ/ZTlcaBOYebSOZotpti/3HrHSw7D3PZm75gYFsOA==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.49.0.tgz", + "integrity": "sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==", "dev": true, "license": "MIT", "engines": { @@ -2109,15 +2089,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.3.tgz", - "integrity": "sha512-ZPCADbr+qfz3aiTTYNNkCbUt+cjNwI/5McyANNrFBpVxPt7GqpEYz5ZfdwuFyGUnJ9FdDXbGODUu6iRCI6XRXw==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.49.0.tgz", + "integrity": "sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.3", - "@typescript-eslint/typescript-estree": "8.46.3", - "@typescript-eslint/utils": "8.46.3", + "@typescript-eslint/types": "8.49.0", + "@typescript-eslint/typescript-estree": "8.49.0", + "@typescript-eslint/utils": "8.49.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -2134,21 +2114,20 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.3.tgz", - "integrity": "sha512-f/NvtRjOm80BtNM5OQtlaBdM5BRFUv7gf381j9wygDNL+qOYSNOgtQ/DCndiYi80iIOv76QqaTmp4fa9hwI0OA==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.49.0.tgz", + "integrity": "sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.46.3", - "@typescript-eslint/tsconfig-utils": "8.46.3", - "@typescript-eslint/types": "8.46.3", - "@typescript-eslint/visitor-keys": "8.46.3", + "@typescript-eslint/project-service": "8.49.0", + "@typescript-eslint/tsconfig-utils": "8.49.0", + "@typescript-eslint/types": "8.49.0", + "@typescript-eslint/visitor-keys": "8.49.0", "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", + "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "engines": { @@ -2163,16 +2142,16 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.3.tgz", - "integrity": "sha512-VXw7qmdkucEx9WkmR3ld/u6VhRyKeiF1uxWwCy/iuNfokjJ7VhsgLSOTjsol8BunSw190zABzpwdNsze2Kpo4g==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.49.0.tgz", + "integrity": "sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.46.3", - "@typescript-eslint/types": "8.46.3", - "@typescript-eslint/typescript-estree": "8.46.3" + "@typescript-eslint/scope-manager": "8.49.0", + "@typescript-eslint/types": "8.49.0", + "@typescript-eslint/typescript-estree": "8.49.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2216,9 +2195,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.3.tgz", - "integrity": "sha512-G7Ok9WN/ggW7e/tOf8TQYMaxgID3Iujn231hfi0Pc7ZheztIJVpO44ekY00b7akqc6nZcvregk0Jpah3kep6hA==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.49.0.tgz", + "integrity": "sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==", "dev": true, "license": "MIT", "engines": { @@ -2406,13 +2385,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.3.tgz", - "integrity": "sha512-uk574k8IU0rOF/AjniX8qbLSGURJVUCeM5e4MIMKBFFi8weeiLrG1fyQejyLXQpRZbU/1BuQasleV/RfHC3hHg==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.49.0.tgz", + "integrity": "sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/types": "8.49.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -2444,30 +2423,30 @@ "license": "ISC" }, "node_modules/@vitest/coverage-v8": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.7.tgz", - "integrity": "sha512-MXc+kEA5EUwMMGmNt1S6CIOEl/iCmAhGZQq1QgMNC3/QpYSOxkysEi6pxWhkqJ7YT/RduoVEV5rxFxHG18V3LA==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.15.tgz", + "integrity": "sha512-FUJ+1RkpTFW7rQITdgTi93qOCWJobWhBirEPCeXh2SW2wsTlFxy51apDz5gzG+ZEYt/THvWeNmhdAoS9DTwpCw==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.0.7", - "ast-v8-to-istanbul": "^0.3.5", - "debug": "^4.4.3", + "@vitest/utils": "4.0.15", + "ast-v8-to-istanbul": "^0.3.8", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.2.0", - "magicast": "^0.3.5", - "std-env": "^3.9.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "std-env": "^3.10.0", "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.0.7", - "vitest": "4.0.7" + "@vitest/browser": "4.0.15", + "vitest": "4.0.15" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2476,17 +2455,17 @@ } }, "node_modules/@vitest/expect": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.7.tgz", - "integrity": "sha512-jGRG6HghnJDjljdjYIoVzX17S6uCVCBRFnsgdLGJ6CaxfPh8kzUKe/2n533y4O/aeZ/sIr7q7GbuEbeGDsWv4Q==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.15.tgz", + "integrity": "sha512-Gfyva9/GxPAWXIWjyGDli9O+waHDC0Q0jaLdFP1qPAUUfo1FEXPXUfUkp3eZA0sSq340vPycSyOlYUeM15Ft1w==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.7", - "@vitest/utils": "4.0.7", - "chai": "^6.0.1", + "@vitest/spy": "4.0.15", + "@vitest/utils": "4.0.15", + "chai": "^6.2.1", "tinyrainbow": "^3.0.3" }, "funding": { @@ -2494,15 +2473,15 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.7.tgz", - "integrity": "sha512-OsDwLS7WnpuNslOV6bJkXVYVV/6RSc4eeVxV7h9wxQPNxnjRvTTrIikfwCbMyl8XJmW6oOccBj2Q07YwZtQcCw==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.15.tgz", + "integrity": "sha512-CZ28GLfOEIFkvCFngN8Sfx5h+Se0zN+h4B7yOsPVCcgtiO7t5jt9xQh2E1UkFep+eb9fjyMfuC5gBypwb07fvQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.0.7", + "@vitest/spy": "4.0.15", "estree-walker": "^3.0.3", - "magic-string": "^0.30.19" + "magic-string": "^0.30.21" }, "funding": { "url": "https://opencollective.com/vitest" @@ -2520,10 +2499,20 @@ } } }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/@vitest/pretty-format": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.7.tgz", - "integrity": "sha512-YY//yxqTmk29+/pK+Wi1UB4DUH3lSVgIm+M10rAJ74pOSMgT7rydMSc+vFuq9LjZLhFvVEXir8EcqMke3SVM6Q==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.15.tgz", + "integrity": "sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A==", "dev": true, "license": "MIT", "dependencies": { @@ -2534,13 +2523,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.7.tgz", - "integrity": "sha512-orU1lsu4PxLEcDWfjVCNGIedOSF/YtZ+XMrd1PZb90E68khWCNzD8y1dtxtgd0hyBIQk8XggteKN/38VQLvzuw==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.15.tgz", + "integrity": "sha512-+A+yMY8dGixUhHmNdPUxOh0la6uVzun86vAbuMT3hIDxMrAOmn5ILBHm8ajrqHE0t8R9T1dGnde1A5DTnmi3qw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.0.7", + "@vitest/utils": "4.0.15", "pathe": "^2.0.3" }, "funding": { @@ -2548,14 +2537,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.7.tgz", - "integrity": "sha512-xJL+Nkw0OjaUXXQf13B8iKK5pI9QVtN9uOtzNHYuG/o/B7fIEg0DQ+xOe0/RcqwDEI15rud1k7y5xznBKGUXAA==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.15.tgz", + "integrity": "sha512-A7Ob8EdFZJIBjLjeO0DZF4lqR6U7Ydi5/5LIZ0xcI+23lYlsYJAfGn8PrIWTYdZQRNnSRlzhg0zyGu37mVdy5g==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.7", - "magic-string": "^0.30.19", + "@vitest/pretty-format": "4.0.15", + "magic-string": "^0.30.21", "pathe": "^2.0.3" }, "funding": { @@ -2563,9 +2552,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.7.tgz", - "integrity": "sha512-FW4X8hzIEn4z+HublB4hBF/FhCVaXfIHm8sUfvlznrcy1MQG7VooBgZPMtVCGZtHi0yl3KESaXTqsKh16d8cFg==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.15.tgz", + "integrity": "sha512-+EIjOJmnY6mIfdXtE/bnozKEvTC4Uczg19yeZ2vtCz5Yyb0QQ31QWVQ8hswJ3Ysx/K2EqaNsVanjr//2+P3FHw==", "dev": true, "license": "MIT", "funding": { @@ -2573,14 +2562,14 @@ } }, "node_modules/@vitest/ui": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.0.7.tgz", - "integrity": "sha512-aIFPci9xoTmVkxpqsSKcRG/Hn0lTy421jsCehHydYeIMd+getn0Pue0JqY5cW8yZglZjMeX0YfIy5wDtQDHEcA==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.0.15.tgz", + "integrity": "sha512-sxSyJMaKp45zI0u+lHrPuZM1ZJQ8FaVD35k+UxVrha1yyvQ+TZuUYllUixwvQXlB7ixoDc7skf3lQPopZIvaQw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@vitest/utils": "4.0.7", + "@vitest/utils": "4.0.15", "fflate": "^0.8.2", "flatted": "^3.3.3", "pathe": "^2.0.3", @@ -2592,17 +2581,17 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "4.0.7" + "vitest": "4.0.15" } }, "node_modules/@vitest/utils": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.7.tgz", - "integrity": "sha512-HNrg9CM/Z4ZWB6RuExhuC6FPmLipiShKVMnT9JlQvfhwR47JatWLChA6mtZqVHqypE6p/z6ofcjbyWpM7YLxPQ==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.15.tgz", + "integrity": "sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.7", + "@vitest/pretty-format": "4.0.15", "tinyrainbow": "^3.0.3" }, "funding": { @@ -2751,6 +2740,16 @@ "js-tokens": "^9.0.1" } }, + "node_modules/ast-v8-to-istanbul/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", @@ -2776,9 +2775,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.24", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.24.tgz", - "integrity": "sha512-uUhTRDPXamakPyghwrUcjaGvvBqGrWvBHReoiULMIpOJVM9IYzQh83Xk2Onx5HlGI2o10NNCzcs9TG/S3TkwrQ==", + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.5.tgz", + "integrity": "sha512-D5vIoztZOq1XM54LUdttJVc96ggEsIfju2JBvht06pSzpckp3C7HReun67Bghzrtdsq9XdMGbSSB3v3GhMNmAA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2826,9 +2825,9 @@ } }, "node_modules/browserslist": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", - "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -2847,11 +2846,11 @@ "license": "MIT", "peer": true, "dependencies": { - "baseline-browser-mapping": "^2.8.19", - "caniuse-lite": "^1.0.30001751", - "electron-to-chromium": "^1.5.238", - "node-releases": "^2.0.26", - "update-browserslist-db": "^1.1.4" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -2871,9 +2870,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001753", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001753.tgz", - "integrity": "sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==", + "version": "1.0.30001760", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz", + "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==", "dev": true, "funding": [ { @@ -2892,9 +2891,9 @@ "license": "CC-BY-4.0" }, "node_modules/chai": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.0.tgz", - "integrity": "sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.1.tgz", + "integrity": "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==", "dev": true, "license": "MIT", "engines": { @@ -2918,6 +2917,19 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cli-cursor": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", @@ -2951,65 +2963,62 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-truncate/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/cli-truncate/node_modules/string-width": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", - "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.0", - "strip-ansi": "^7.1.0" - }, "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/cli-truncate/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=8" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=12" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/color-convert": { @@ -3081,22 +3090,6 @@ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, - "node_modules/concurrently/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -3171,14 +3164,14 @@ "license": "MIT" }, "node_modules/cssstyle": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.2.tgz", - "integrity": "sha512-zDMqXh8Vs1CdRYZQ2M633m/SFgcjlu8RB8b/1h82i+6vpArF507NSYIWJHGlJaTWoS+imcnctmEz43txhbVkOw==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.4.tgz", + "integrity": "sha512-KyOS/kJMEq5O9GdPnaf82noigg5X5DYn0kZPJTaAsCUaBizp6Xa1y9D4Qoqf/JazEXWuruErHgVXwjN5391ZJw==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^4.0.3", - "@csstools/css-syntax-patches-for-csstree": "^1.0.14", + "@asamuzakjp/css-color": "^4.1.0", + "@csstools/css-syntax-patches-for-csstree": "1.0.14", "css-tree": "^3.1.0" }, "engines": { @@ -3347,9 +3340,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.245", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.245.tgz", - "integrity": "sha512-rdmGfW47ZhL/oWEJAY4qxRtdly2B98ooTJ0pdEI4jhVLZ6tNf8fPtov2wS1IRKwFJT92le3x4Knxiwzl7cPPpQ==", + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", "dev": true, "license": "ISC" }, @@ -3394,9 +3387,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", + "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3404,32 +3397,34 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.25.0", + "@esbuild/android-arm": "0.25.0", + "@esbuild/android-arm64": "0.25.0", + "@esbuild/android-x64": "0.25.0", + "@esbuild/darwin-arm64": "0.25.0", + "@esbuild/darwin-x64": "0.25.0", + "@esbuild/freebsd-arm64": "0.25.0", + "@esbuild/freebsd-x64": "0.25.0", + "@esbuild/linux-arm": "0.25.0", + "@esbuild/linux-arm64": "0.25.0", + "@esbuild/linux-ia32": "0.25.0", + "@esbuild/linux-loong64": "0.25.0", + "@esbuild/linux-mips64el": "0.25.0", + "@esbuild/linux-ppc64": "0.25.0", + "@esbuild/linux-riscv64": "0.25.0", + "@esbuild/linux-s390x": "0.25.0", + "@esbuild/linux-x64": "0.25.0", + "@esbuild/netbsd-arm64": "0.25.0", + "@esbuild/netbsd-x64": "0.25.0", + "@esbuild/openbsd-arm64": "0.25.0", + "@esbuild/openbsd-x64": "0.25.0", + "@esbuild/sunos-x64": "0.25.0", + "@esbuild/win32-arm64": "0.25.0", + "@esbuild/win32-ia32": "0.25.0", + "@esbuild/win32-x64": "0.25.0" } }, "node_modules/escalade": { @@ -3632,14 +3627,11 @@ } }, "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } + "license": "MIT" }, "node_modules/esutils": { "version": "2.0.3", @@ -3659,9 +3651,9 @@ "license": "MIT" }, "node_modules/expect-type": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", - "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3729,24 +3721,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, "node_modules/fflate": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", @@ -4186,13 +4160,19 @@ } }, "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "dev": true, "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-glob": { @@ -4267,6 +4247,19 @@ "node": ">=10" } }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/istanbul-lib-source-maps": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", @@ -4304,9 +4297,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -4317,16 +4310,16 @@ } }, "node_modules/jsdom": { - "version": "27.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.1.0.tgz", - "integrity": "sha512-Pcfm3eZ+eO4JdZCXthW9tCDT3nF4K+9dmeZ+5X39n+Kqz0DDIABRP5CAEOHRFZk8RGuC2efksTJxrjp8EXCunQ==", + "version": "27.3.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.3.0.tgz", + "integrity": "sha512-GtldT42B8+jefDUC4yUKAvsaOrH7PDHmZxZXNgF2xMmymjUbRYJvpAybZAKEmXDGTM0mCsz8duOa4vTm5AY2Kg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@acemir/cssom": "^0.9.19", - "@asamuzakjp/dom-selector": "^6.7.3", - "cssstyle": "^5.3.2", + "@acemir/cssom": "^0.9.28", + "@asamuzakjp/dom-selector": "^6.7.6", + "cssstyle": "^5.3.4", "data-urls": "^6.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^4.0.0", @@ -4436,13 +4429,13 @@ } }, "node_modules/lint-staged": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.6.tgz", - "integrity": "sha512-s1gphtDbV4bmW1eylXpVMk2u7is7YsrLl8hzrtvC70h4ByhcMLZFY01Fx05ZUDNuv1H8HO4E+e2zgejV1jVwNw==", + "version": "16.2.7", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz", + "integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==", "dev": true, "license": "MIT", "dependencies": { - "commander": "^14.0.1", + "commander": "^14.0.2", "listr2": "^9.0.5", "micromatch": "^4.0.8", "nano-spawn": "^2.0.0", @@ -4478,91 +4471,6 @@ "node": ">=20.0.0" } }, - "node_modules/listr2/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/listr2/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/listr2/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, - "license": "MIT" - }, - "node_modules/listr2/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/listr2/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/listr2/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4619,44 +4527,6 @@ "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/log-update/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-update/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-update/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/log-update/node_modules/strip-ansi": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", @@ -4673,24 +4543,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -4722,15 +4574,15 @@ } }, "node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", + "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "source-map-js": "^1.2.1" } }, "node_modules/make-dir": { @@ -4793,19 +4645,6 @@ "node": ">=8.6" } }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/mimic-function": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", @@ -4938,6 +4777,17 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5095,14 +4945,13 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=12" + "node": ">=8.6" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -5141,6 +4990,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -5151,9 +5001,9 @@ } }, "node_modules/preact": { - "version": "10.27.2", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.27.2.tgz", - "integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==", + "version": "10.28.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.0.tgz", + "integrity": "sha512-rytDAoiXr3+t6OIP3WGlDd0ouCUG1iCWzkcY3++Nreuoi17y6T5i/zRhe6uYfoVcxq6YU+sBtJouuRDsq8vvqA==", "license": "MIT", "peer": true, "funding": { @@ -5172,9 +5022,9 @@ } }, "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", "bin": { @@ -5215,13 +5065,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/pretty-format/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "license": "MIT" - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5253,6 +5096,13 @@ ], "license": "MIT" }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -5372,9 +5222,9 @@ } }, "node_modules/rollup": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", - "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", "dev": true, "license": "MIT", "peer": true, @@ -5389,28 +5239,28 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.52.5", - "@rollup/rollup-android-arm64": "4.52.5", - "@rollup/rollup-darwin-arm64": "4.52.5", - "@rollup/rollup-darwin-x64": "4.52.5", - "@rollup/rollup-freebsd-arm64": "4.52.5", - "@rollup/rollup-freebsd-x64": "4.52.5", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", - "@rollup/rollup-linux-arm-musleabihf": "4.52.5", - "@rollup/rollup-linux-arm64-gnu": "4.52.5", - "@rollup/rollup-linux-arm64-musl": "4.52.5", - "@rollup/rollup-linux-loong64-gnu": "4.52.5", - "@rollup/rollup-linux-ppc64-gnu": "4.52.5", - "@rollup/rollup-linux-riscv64-gnu": "4.52.5", - "@rollup/rollup-linux-riscv64-musl": "4.52.5", - "@rollup/rollup-linux-s390x-gnu": "4.52.5", - "@rollup/rollup-linux-x64-gnu": "4.52.5", - "@rollup/rollup-linux-x64-musl": "4.52.5", - "@rollup/rollup-openharmony-arm64": "4.52.5", - "@rollup/rollup-win32-arm64-msvc": "4.52.5", - "@rollup/rollup-win32-ia32-msvc": "4.52.5", - "@rollup/rollup-win32-x64-gnu": "4.52.5", - "@rollup/rollup-win32-x64-msvc": "4.52.5", + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" } }, @@ -5599,22 +5449,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -5670,18 +5504,49 @@ } }, "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/strip-ansi": { @@ -5724,16 +5589,19 @@ } }, "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/symbol-tree": { @@ -5758,11 +5626,14 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/tinyglobby": { "version": "0.2.15", @@ -5781,33 +5652,65 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyrainbow": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", - "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.0.0" - } - }, + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tldts": { - "version": "7.0.17", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.17.tgz", - "integrity": "sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==", + "version": "7.0.19", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.19.tgz", + "integrity": "sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.0.17" + "tldts-core": "^7.0.19" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.0.17", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.17.tgz", - "integrity": "sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==", + "version": "7.0.19", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.19.tgz", + "integrity": "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==", "dev": true, "license": "MIT" }, @@ -5891,13 +5794,13 @@ "license": "0BSD" }, "node_modules/tsx": { - "version": "4.20.6", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", - "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "~0.25.0", + "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "bin": { @@ -5911,9 +5814,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz", + "integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==", "cpu": [ "ppc64" ], @@ -5928,9 +5831,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz", + "integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==", "cpu": [ "arm" ], @@ -5945,9 +5848,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz", + "integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==", "cpu": [ "arm64" ], @@ -5962,9 +5865,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz", + "integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==", "cpu": [ "x64" ], @@ -5979,9 +5882,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz", + "integrity": "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==", "cpu": [ "arm64" ], @@ -5996,9 +5899,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz", + "integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==", "cpu": [ "x64" ], @@ -6013,9 +5916,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz", + "integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==", "cpu": [ "arm64" ], @@ -6030,9 +5933,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz", + "integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==", "cpu": [ "x64" ], @@ -6047,9 +5950,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz", + "integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==", "cpu": [ "arm" ], @@ -6064,9 +5967,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz", + "integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==", "cpu": [ "arm64" ], @@ -6081,9 +5984,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz", + "integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==", "cpu": [ "ia32" ], @@ -6098,9 +6001,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz", + "integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==", "cpu": [ "loong64" ], @@ -6115,9 +6018,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz", + "integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==", "cpu": [ "mips64el" ], @@ -6132,9 +6035,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz", + "integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==", "cpu": [ "ppc64" ], @@ -6149,9 +6052,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz", + "integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==", "cpu": [ "riscv64" ], @@ -6166,9 +6069,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz", + "integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==", "cpu": [ "s390x" ], @@ -6183,9 +6086,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz", + "integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==", "cpu": [ "x64" ], @@ -6199,10 +6102,27 @@ "node": ">=18" } }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz", + "integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz", + "integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==", "cpu": [ "x64" ], @@ -6216,10 +6136,27 @@ "node": ">=18" } }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz", + "integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz", + "integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==", "cpu": [ "x64" ], @@ -6234,9 +6171,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz", + "integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==", "cpu": [ "x64" ], @@ -6251,9 +6188,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz", + "integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==", "cpu": [ "arm64" ], @@ -6268,9 +6205,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz", + "integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==", "cpu": [ "ia32" ], @@ -6285,9 +6222,9 @@ } }, "node_modules/tsx/node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz", + "integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==", "cpu": [ "x64" ], @@ -6302,9 +6239,9 @@ } }, "node_modules/tsx/node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.1.tgz", + "integrity": "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -6315,32 +6252,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" + "@esbuild/aix-ppc64": "0.27.1", + "@esbuild/android-arm": "0.27.1", + "@esbuild/android-arm64": "0.27.1", + "@esbuild/android-x64": "0.27.1", + "@esbuild/darwin-arm64": "0.27.1", + "@esbuild/darwin-x64": "0.27.1", + "@esbuild/freebsd-arm64": "0.27.1", + "@esbuild/freebsd-x64": "0.27.1", + "@esbuild/linux-arm": "0.27.1", + "@esbuild/linux-arm64": "0.27.1", + "@esbuild/linux-ia32": "0.27.1", + "@esbuild/linux-loong64": "0.27.1", + "@esbuild/linux-mips64el": "0.27.1", + "@esbuild/linux-ppc64": "0.27.1", + "@esbuild/linux-riscv64": "0.27.1", + "@esbuild/linux-s390x": "0.27.1", + "@esbuild/linux-x64": "0.27.1", + "@esbuild/netbsd-arm64": "0.27.1", + "@esbuild/netbsd-x64": "0.27.1", + "@esbuild/openbsd-arm64": "0.27.1", + "@esbuild/openbsd-x64": "0.27.1", + "@esbuild/openharmony-arm64": "0.27.1", + "@esbuild/sunos-x64": "0.27.1", + "@esbuild/win32-arm64": "0.27.1", + "@esbuild/win32-ia32": "0.27.1", + "@esbuild/win32-x64": "0.27.1" } }, "node_modules/type-check": { @@ -6392,9 +6329,9 @@ "license": "MIT" }, "node_modules/update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", + "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", "dev": true, "funding": [ { @@ -6433,9 +6370,9 @@ } }, "node_modules/vite": { - "version": "7.1.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", - "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", + "version": "7.2.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.7.tgz", + "integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==", "dev": true, "license": "MIT", "peer": true, @@ -6543,463 +6480,62 @@ "vite": "5.x || 6.x || 7.x" } }, - "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">=18" + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, + "peer": true, "engines": { - "node": ">=18" + "node": ">=12" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/vitest": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.7.tgz", - "integrity": "sha512-xQroKAadK503CrmbzCISvQUjeuvEZzv6U0wlnlVFOi5i3gnzfH4onyQ29f3lzpe0FresAiTAd3aqK0Bi/jLI8w==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.15.tgz", + "integrity": "sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@vitest/expect": "4.0.7", - "@vitest/mocker": "4.0.7", - "@vitest/pretty-format": "4.0.7", - "@vitest/runner": "4.0.7", - "@vitest/snapshot": "4.0.7", - "@vitest/spy": "4.0.7", - "@vitest/utils": "4.0.7", - "debug": "^4.4.3", + "@vitest/expect": "4.0.15", + "@vitest/mocker": "4.0.15", + "@vitest/pretty-format": "4.0.15", + "@vitest/runner": "4.0.15", + "@vitest/snapshot": "4.0.15", + "@vitest/spy": "4.0.15", + "@vitest/utils": "4.0.15", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", - "magic-string": "^0.30.19", + "magic-string": "^0.30.21", + "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", - "std-env": "^3.9.0", + "std-env": "^3.10.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.2", + "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", @@ -7016,12 +6552,12 @@ }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/debug": "^4.1.12", + "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.0.7", - "@vitest/browser-preview": "4.0.7", - "@vitest/browser-webdriverio": "4.0.7", - "@vitest/ui": "4.0.7", + "@vitest/browser-playwright": "4.0.15", + "@vitest/browser-preview": "4.0.15", + "@vitest/browser-webdriverio": "4.0.15", + "@vitest/ui": "4.0.15", "happy-dom": "*", "jsdom": "*" }, @@ -7029,7 +6565,7 @@ "@edge-runtime/vm": { "optional": true }, - "@types/debug": { + "@opentelemetry/api": { "optional": true }, "@types/node": { @@ -7055,6 +6591,19 @@ } } }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -7159,23 +6708,90 @@ } }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -7240,9 +6856,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "dev": true, "license": "ISC", "bin": { @@ -7250,6 +6866,9 @@ }, "engines": { "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } }, "node_modules/yargs": { @@ -7281,6 +6900,31 @@ "node": ">=12" } }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index a83a7a5..3105c04 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "figgit", - "version": "0.2.0", + "version": "0.3.0", "description": "A Figma plugin for exporting variables to GitHub.", "author": "Tom Larcher ", "license": "MIT", @@ -58,7 +58,7 @@ "@vitest/coverage-v8": "^4.0.7", "@vitest/ui": "^4.0.7", "concurrently": "^9.2.1", - "esbuild": "0.21.5", + "esbuild": "0.25.0", "eslint": "^8.57.1", "husky": "^9.1.7", "jsdom": "^27.1.0", diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..2ed81c1 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1 @@ +export const PLUGIN_VERSION = '0.3.0'; diff --git a/src/export/buildDtcgJson.ts b/src/export/buildDtcgJson.ts index d5295f0..f891a94 100644 --- a/src/export/buildDtcgJson.ts +++ b/src/export/buildDtcgJson.ts @@ -14,7 +14,6 @@ */ import type { DtcgRoot, DtcgToken, DtcgTokenValue } from '../shared/dtcg-types'; -import type { VariableModeValue } from '../shared/types'; import { buildTokenPath, convertValue, @@ -24,6 +23,7 @@ import { } from '../util/dtcgUtils'; import { stableStringify } from '../util/stableStringify'; import { sha256 } from './hash'; +import { normalizeModeValue } from './valueNormalization'; /** Plugin version - can be replaced during build via define if desired */ const PLUGIN_VERSION = '0.2.0'; @@ -208,64 +208,3 @@ export async function buildDtcgJson(figmaApi: PluginAPI): Promise { ); } } - -/** - * Normalizes a variable's mode value into our structured format. - * - * Figma's variable values can be: - * - Colors (objects with r, g, b, a properties) - * - Aliases (references to other variables) - * - Primitives (strings, numbers, booleans) - * - * @param raw - Raw value from Figma's variable API - * @returns Normalized value with explicit type - */ -function normalizeModeValue(raw: unknown): VariableModeValue { - if (!raw || typeof raw !== 'object') { - // Primitives (string/number/boolean) or unexpected values - return inferPrimitive(raw); - } - - const obj = raw as Record; - - // Alias detection: Figma 2025 API uses { type: 'VARIABLE_ALIAS', id: '...' } - if (obj.type === 'VARIABLE_ALIAS' && typeof obj.id === 'string') { - return { type: 'ALIAS', refVariableId: obj.id }; - } - - // Color detection: Figma colors have r, g, b properties (0-1 range) - if (typeof obj.r === 'number' && typeof obj.g === 'number' && typeof obj.b === 'number') { - return { - type: 'COLOR', - value: { - r: obj.r, - g: obj.g, - b: obj.b, - a: typeof obj.a === 'number' ? obj.a : 1, - }, - }; - } - - // Fallback to primitive inference - return inferPrimitive(raw); -} - -/** - * Infers the type of a primitive value and wraps it in our value format. - * - * @param val - Primitive value - * @returns Typed value object - */ -function inferPrimitive(val: unknown): VariableModeValue { - switch (typeof val) { - case 'string': - return { type: 'STRING', value: val }; - case 'number': - return { type: 'NUMBER', value: val }; - case 'boolean': - return { type: 'BOOLEAN', value: val }; - default: - // Fallback: convert to string - return { type: 'STRING', value: String(val) }; - } -} diff --git a/src/export/buildExportBundle.ts b/src/export/buildExportBundle.ts new file mode 100644 index 0000000..b582b8c --- /dev/null +++ b/src/export/buildExportBundle.ts @@ -0,0 +1,87 @@ +/** + * Central export orchestrator. + * + * Chooses the correct builder (DTCG vs Figma-native) according to the + * current plugin settings and always returns a normalized ExportBundle + * so downstream code can render previews, diffs, and commits without + * format-specific branching. + */ +import { buildDtcgJson } from './buildDtcgJson'; +import { buildFigmaNativeExport } from './buildFigmaNativeExport'; +import { buildRepoPath } from '../util/path'; +import { stableStringify } from '../util/stableStringify'; +import { sha256 } from './hash'; +import { PLUGIN_VERSION } from '../constants'; +import type { ExportBundle, ExportDocument, ExportSummary } from '../types/export'; +import type { PersistedSettings } from '../messaging'; +import type { DtcgRoot, DtcgFigmaExtensions } from '../shared/dtcg-types'; + +export async function buildExportBundle( + figmaApi: PluginAPI, + settings: PersistedSettings +): Promise { + const format = settings.exportFormat || 'dtcg'; + const exportType = format === 'dtcg' ? 'singleFile' : settings.exportType || 'singleFile'; + + if (format === 'figma-native') { + return buildFigmaNativeExport(figmaApi, { + exportType, + folder: settings.folder, + singleFileName: settings.filename || 'variables.json', + }); + } + + return buildDtcgBundle(figmaApi, settings); +} + +async function buildDtcgBundle( + figmaApi: PluginAPI, + settings: PersistedSettings +): Promise { + const root = (await buildDtcgJson(figmaApi)) as DtcgRoot; + const figmaExt = (root.$extensions?.['com.figma'] as DtcgFigmaExtensions | undefined) || {}; + + const exportedAt = + typeof figmaExt.exportedAt === 'string' ? figmaExt.exportedAt : new Date().toISOString(); + const pluginVersion = figmaExt.pluginVersion || PLUGIN_VERSION; + const fileName = figmaExt.fileName || figmaApi.root.name; + const variablesCount = figmaExt.variablesCount ?? 0; + const collectionsCount = figmaExt.collectionsCount ?? 0; + + let contentHash = figmaExt.contentHash; + if (!contentHash) { + const tokensOnly = { ...root }; + delete tokensOnly.$extensions; + contentHash = await sha256(stableStringify(tokensOnly)); + } + + const fileNameSetting = settings.filename || 'variables.json'; + const relativePath = buildRepoPath(settings.folder, fileNameSetting); + + const document: ExportDocument = { + id: 'dtcg:all', + label: 'All Collections (DTCG)', + format: 'dtcg', + exportType: 'singleFile', + relativePath, + fileName: fileNameSetting, + data: root, + contentHash, + variablesCount, + collectionsCount, + }; + + const summary: ExportSummary = { + format: 'dtcg', + exportType: 'singleFile', + exportedAt, + pluginVersion, + fileName, + variablesCount, + collectionsCount, + documentsCount: 1, + contentHash, + }; + + return { summary, documents: [document] }; +} diff --git a/src/export/buildFigmaNativeExport.ts b/src/export/buildFigmaNativeExport.ts new file mode 100644 index 0000000..25748c8 --- /dev/null +++ b/src/export/buildFigmaNativeExport.ts @@ -0,0 +1,517 @@ +/** + * Builds the "figma-native" export bundle. + * + * The output mirrors Figma's Variables UI hierarchy, including + * collections, nested groups, mode metadata, and per-variable paths. + * Every document embeds deterministic metadata and hashes so Git diffs + * remain stable even when exporting one file per collection. + */ +import { PLUGIN_VERSION } from '../constants'; +import { stableStringify } from '../util/stableStringify'; +import { sha256 } from './hash'; +import { normalizeModeValue } from './valueNormalization'; +import { rgbToHex } from '../util/colorUtils'; +import { slugify } from '../util/slugify'; +import { buildRepoPath } from '../util/path'; +import type { ExportBundle, ExportDocument, ExportSummary, ExportType } from '../types/export'; +import type { + FigmaNativeExportDocument, + FigmaNativeCollection, + FigmaNativeGroup, + FigmaNativeVariable, + ValueByModeEntry, +} from '../shared/figma-native-types'; +import type { VariableModeValue } from '../util/dtcgUtils'; + +interface BuildNativeOptions { + exportType: ExportType; + folder?: string; + singleFileName: string; +} + +export async function buildFigmaNativeExport( + figmaApi: PluginAPI, + options: BuildNativeOptions +): Promise { + const collections = await figma.variables.getLocalVariableCollectionsAsync(); + const allVariables = await figma.variables.getLocalVariablesAsync(); + + const fileName = figmaApi.root.name; + const exportedAt = new Date().toISOString(); + const totalVariables = allVariables.length; + const totalCollections = collections.length; + + const variablesById = new Map(allVariables.map((variable) => [variable.id, variable])); + const aliasSources = buildAliasSources(allVariables); + const remoteVariableNames = await resolveRemoteVariableNames(allVariables, variablesById); + + const sanitizedSingleFileName = ensureJsonExtension(options.singleFileName || 'variables.json'); + + const documents: ExportDocument[] = []; + + if (options.exportType === 'singleFile') { + const documentData = await buildDocumentData({ + collections, + variables: allVariables, + fileName, + exportedAt, + exportType: 'singleFile', + variablesById, + aliasSources, + remoteVariableNames, + pluginVersion: PLUGIN_VERSION, + }); + + const relativePath = buildRepoPath(options.folder, sanitizedSingleFileName); + documents.push({ + id: 'figma-native:all', + label: 'All Collections', + format: 'figma-native', + exportType: 'singleFile', + relativePath, + fileName: sanitizedSingleFileName, + data: documentData, + contentHash: documentData.contentHash, + variablesCount: documentData.variablesCount, + collectionsCount: documentData.collectionsCount, + }); + } else { + const slugUsage = new Map(); + + for (const collection of collections) { + const collectionVariables = allVariables.filter( + (variable) => variable.variableCollectionId === collection.id + ); + + const documentData = await buildDocumentData({ + collections: [collection], + variables: collectionVariables, + fileName, + exportedAt, + exportType: 'perCollection', + variablesById, + aliasSources, + remoteVariableNames, + pluginVersion: PLUGIN_VERSION, + collectionContext: collection, + }); + + const slugBase = slugify(collection.name, `collection-${documents.length + 1}`); + const occurrences = slugUsage.get(slugBase) ?? 0; + slugUsage.set(slugBase, occurrences + 1); + const slug = occurrences === 0 ? slugBase : `${slugBase}-${occurrences + 1}`; + const fileNameForCollection = `${slug}.json`; + const relativePath = buildRepoPath(options.folder, fileNameForCollection); + + documents.push({ + id: `figma-native:${collection.id}`, + label: collection.name, + format: 'figma-native', + exportType: 'perCollection', + relativePath, + fileName: fileNameForCollection, + data: documentData, + contentHash: documentData.contentHash, + variablesCount: documentData.variablesCount, + collectionsCount: documentData.collectionsCount, + collectionId: collection.id, + collectionName: collection.name, + }); + } + } + + const summaryHash = await sha256( + stableStringify( + documents + .map((doc) => ({ path: doc.relativePath, hash: doc.contentHash })) + .sort((a, b) => a.path.localeCompare(b.path)) + ) + ); + + const summary: ExportSummary = { + format: 'figma-native', + exportType: options.exportType, + exportedAt, + pluginVersion: PLUGIN_VERSION, + fileName, + variablesCount: totalVariables, + collectionsCount: totalCollections, + documentsCount: documents.length, + contentHash: summaryHash, + }; + + return { summary, documents }; +} + +interface DocumentBuildParams { + collections: readonly VariableCollection[]; + variables: readonly Variable[]; + fileName: string; + exportedAt: string; + exportType: ExportType; + variablesById: Map; + aliasSources: Map; + remoteVariableNames: Map; + pluginVersion: string; + collectionContext?: VariableCollection; +} + +async function buildDocumentData(params: DocumentBuildParams): Promise { + const { + collections, + variables, + fileName, + exportedAt, + exportType, + variablesById, + aliasSources, + remoteVariableNames, + pluginVersion, + collectionContext, + } = params; + + const collectionPayloads: FigmaNativeCollection[] = []; + + for (const collection of collections) { + const scopedVariables = variables.filter( + (variable) => variable.variableCollectionId === collection.id + ); + collectionPayloads.push( + buildCollectionPayload({ + collection, + variables: scopedVariables, + variablesById, + aliasSources, + remoteVariableNames, + }) + ); + } + + const variablesCount = variables.length; + const collectionsCount = collectionPayloads.length; + + const document: FigmaNativeExportDocument = { + collectionsCount, + variablesCount, + contentHash: '', + exportedAt, + fileName, + pluginVersion, + exportFormat: 'figma-native', + exportType, + collectionId: collectionContext?.id, + collectionName: collectionContext?.name, + collections: collectionPayloads, + }; + + const hashInput = stableStringify({ + ...document, + contentHash: undefined, + exportedAt: undefined, + }); + document.contentHash = await sha256(hashInput); + + return document; +} + +interface CollectionPayloadParams { + collection: VariableCollection; + variables: readonly Variable[]; + variablesById: Map; + aliasSources: Map; + remoteVariableNames: Map; +} + +function buildCollectionPayload(params: CollectionPayloadParams): FigmaNativeCollection { + const { collection, variables, variablesById, aliasSources, remoteVariableNames } = params; + + const rootVariables: FigmaNativeVariable[] = []; + const rootGroups: MutableGroup[] = []; + + for (const variable of variables) { + const nameSegments = getVariableSegments(variable.name); + const variableNode = buildVariableNode( + variable, + collection, + { + variablesById, + aliasSources, + remoteVariableNames, + }, + nameSegments + ); + + const groupSegments = nameSegments.slice(0, nameSegments.length - 1); + + if (groupSegments.length === 0) { + rootVariables.push(variableNode); + } else { + insertIntoGroups(rootGroups, groupSegments, collection, variableNode); + } + } + + const sortedGroups = sortGroups(rootGroups); + const groupedVariables = flattenGroupVariables(sortedGroups); + const sortedVariables = [...rootVariables, ...groupedVariables].sort((a, b) => + a.path.localeCompare(b.path) + ); + + return { + id: collection.id, + name: collection.name, + description: (collection as { description?: string }).description, + modes: collection.modes.map((mode) => ({ + id: mode.modeId, + name: mode.name, + description: (mode as { description?: string }).description, + })), + groups: sortedGroups, + variables: sortedVariables, + collectionVariablesCount: variables.length, + }; +} + +interface BuildVariableContext { + variablesById: Map; + aliasSources: Map; + remoteVariableNames: Map; +} + +function buildVariableNode( + variable: Variable, + collection: VariableCollection, + context: BuildVariableContext, + nameSegments: string[] +): FigmaNativeVariable { + const type = mapVariableType(variable.resolvedType); + const scopes = Array.isArray(variable.scopes) ? Array.from(new Set(variable.scopes)) : undefined; + const codeSyntaxEntries = variable.codeSyntax + ? Object.entries(variable.codeSyntax).map(([platform, value]) => ({ platform, value })) + : []; + const aliases = context.aliasSources.get(variable.id) || []; + const normalizedSegments = nameSegments.length + ? nameSegments + : [variable.name] + .map((segment) => segment.trim()) + .filter((segment): segment is string => Boolean(segment)); + const variableName = normalizedSegments.length + ? normalizedSegments[normalizedSegments.length - 1] + : variable.name.trim() || variable.name; + const variablePath = normalizedSegments.length ? normalizedSegments.join('/') : variableName; + + const valueByMode: Record = {}; + for (const mode of collection.modes) { + const rawValue = variable.valuesByMode[mode.modeId]; + if (!rawValue) continue; + + const normalized = normalizeModeValue(rawValue); + const entry = buildModeValueEntry( + normalized, + context.variablesById, + context.remoteVariableNames + ); + if (!entry) continue; + valueByMode[mode.modeId] = entry; + } + + const node: FigmaNativeVariable = { + id: variable.id, + name: variableName, + path: variablePath, + type, + description: variable.description?.trim() || undefined, + scopes, + codeSyntax: codeSyntaxEntries.length ? codeSyntaxEntries : undefined, + aliases: aliases.length + ? aliases.map((aliasId) => context.variablesById.get(aliasId)?.name || aliasId).sort() + : undefined, + hiddenFromPublishing: variable.hiddenFromPublishing, + valueByMode, + }; + + if (!node.hiddenFromPublishing) { + delete node.hiddenFromPublishing; + } + + return node; +} + +function buildModeValueEntry( + modeValue: VariableModeValue, + variablesById: Map, + remoteVariableNames: Map +): ValueByModeEntry | undefined { + if ( + modeValue.type === 'COLOR' && + modeValue.value && + typeof modeValue.value === 'object' && + 'r' in modeValue.value + ) { + const color = modeValue.value as { r: number; g: number; b: number; a: number }; + return { value: colorToString(color) }; + } + + if (modeValue.type === 'STRING' && typeof modeValue.value === 'string') { + return { value: modeValue.value }; + } + + if (modeValue.type === 'NUMBER' && typeof modeValue.value === 'number') { + return { value: modeValue.value }; + } + + if (modeValue.type === 'BOOLEAN' && typeof modeValue.value === 'boolean') { + return { value: modeValue.value }; + } + + if (modeValue.type === 'ALIAS' && modeValue.refVariableId) { + const referencedVariable = variablesById.get(modeValue.refVariableId); + const referencedName = + referencedVariable?.name || remoteVariableNames.get(modeValue.refVariableId); + const value = referencedName || modeValue.refVariableId; + return { + value, + referencedVariableId: modeValue.refVariableId, + referencedVariableName: referencedName, + }; + } + + return undefined; +} + +function colorToString(color: { r: number; g: number; b: number; a: number }): string { + const hex = rgbToHex(color.r, color.g, color.b); + if (color.a === undefined || Math.abs(color.a - 1) < 0.0001) { + return hex; + } + const r = Math.round(color.r * 255); + const g = Math.round(color.g * 255); + const b = Math.round(color.b * 255); + return `rgba(${r}, ${g}, ${b}, ${Number(color.a.toFixed(3))})`; +} + +interface MutableGroup extends FigmaNativeGroup { + groups: MutableGroup[]; + variables: FigmaNativeVariable[]; +} + +function insertIntoGroups( + groups: MutableGroup[], + segments: string[], + collection: VariableCollection, + variable: FigmaNativeVariable +) { + let currentGroups = groups; + const pathSegments: string[] = []; + let parent: MutableGroup | undefined; + + for (const segment of segments) { + pathSegments.push(segment); + let group = currentGroups.find((g) => g.name === segment); + if (!group) { + const path = `${collection.name}/${pathSegments.join('/')}`; + group = { + id: `${collection.id}:${pathSegments.join('/') || 'root'}`, + name: segment, + path, + groups: [], + variables: [], + }; + currentGroups.push(group); + } + parent = group; + currentGroups = group.groups; + } + + parent?.variables.push(variable); +} + +function sortGroups(groups: MutableGroup[]): FigmaNativeGroup[] { + return groups + .sort((a, b) => a.name.localeCompare(b.name)) + .map((group) => ({ + id: group.id, + name: group.name, + path: group.path, + groups: sortGroups(group.groups), + variables: [...group.variables].sort((a, b) => a.path.localeCompare(b.path)), + })); +} + +function flattenGroupVariables(groups: FigmaNativeGroup[]): FigmaNativeVariable[] { + const result: FigmaNativeVariable[] = []; + for (const group of groups) { + result.push(...group.variables); + result.push(...flattenGroupVariables(group.groups)); + } + return result; +} + +function buildAliasSources(variables: readonly Variable[]): Map { + const map = new Map(); + for (const variable of variables) { + for (const value of Object.values(variable.valuesByMode)) { + const normalized = normalizeModeValue(value); + if (normalized.type === 'ALIAS' && normalized.refVariableId) { + const arr = map.get(normalized.refVariableId) ?? []; + arr.push(variable.id); + map.set(normalized.refVariableId, arr); + } + } + } + return map; +} + +async function resolveRemoteVariableNames( + variables: readonly Variable[], + variablesById: Map +): Promise> { + const remoteIds = new Set(); + for (const variable of variables) { + for (const value of Object.values(variable.valuesByMode)) { + const normalized = normalizeModeValue(value); + if (normalized.type === 'ALIAS' && normalized.refVariableId) { + if (!variablesById.has(normalized.refVariableId)) { + remoteIds.add(normalized.refVariableId); + } + } + } + } + + const map = new Map(); + for (const id of remoteIds) { + try { + const remote = await figma.variables.getVariableByIdAsync(id); + if (remote) { + map.set(id, remote.name); + } + } catch { + // Ignore failures + } + } + return map; +} + +function mapVariableType(resolvedType: string): FigmaNativeVariable['type'] { + switch (resolvedType) { + case 'COLOR': + return 'color'; + case 'BOOLEAN': + return 'boolean'; + case 'STRING': + return 'string'; + default: + return 'number'; + } +} + +function ensureJsonExtension(name: string): string { + if (!name) return 'variables.json'; + return name.toLowerCase().endsWith('.json') ? name : `${name}.json`; +} + +function getVariableSegments(name: string): string[] { + return name + .split('/') + .map((segment) => segment.trim()) + .filter((segment): segment is string => Boolean(segment)); +} diff --git a/src/export/buildVariablesJson.ts b/src/export/buildVariablesJson.ts deleted file mode 100644 index 96cdf49..0000000 --- a/src/export/buildVariablesJson.ts +++ /dev/null @@ -1,203 +0,0 @@ -/** - * Builds a complete JSON export of all local variables from a Figma file. - * - * This module handles the extraction and transformation of Figma's variable - * data into a structured, deterministic JSON format suitable for version control. - * - * Key features: - * - Extracts all local variables and collections - * - Normalizes variable values (colors, numbers, strings, booleans, aliases) - * - Sorts collections and variables alphabetically for determinism - * - Generates a content hash for change detection - * - Includes metadata (export time, file info, counts) - */ - -import { - ExportedCollection, - ExportedVariable, - ExportRoot, - VariableModeValue, -} from '../shared/types'; -import { stableStringify } from '../util/stableStringify'; -import { sha256 } from './hash'; - -/** Plugin version - can be replaced during build via define if desired */ -const PLUGIN_VERSION = '0.1.0'; - -/** - * Minimal shape for a collection mode to avoid implicit any. - * We only need the essential fields for export. - */ -interface CollectionModeLite { - modeId: string; - name: string; -} - -/** - * Builds a complete variables export from the current Figma file. - * - * Process: - * 1. Fetch all local variable collections and variables - * 2. Group variables by collection - * 3. Normalize variable values (colors, primitives, aliases) - * 4. Sort collections and variables alphabetically - * 5. Generate metadata (counts, file info, export time) - * 6. Calculate content hash for change detection - * - * @param figmaApi - Figma plugin API instance - * @returns Promise resolving to complete export data structure - * @throws Error if variable extraction fails - */ -export async function buildVariablesJson(figmaApi: PluginAPI): Promise { - try { - const collections = await figma.variables.getLocalVariableCollectionsAsync(); - const allVars = await figma.variables.getLocalVariablesAsync(); - const fileName = figmaApi.root.name; - const fileId = figmaApi.root.id; - - // Group variables by collection ID - const byCollection: Record = {}; - - // Initialize collection objects - for (const c of collections) { - byCollection[c.id] = { - id: c.id, - name: c.name, - modes: (c.modes as readonly CollectionModeLite[]).map((m) => ({ - id: m.modeId, - name: m.name, - })), - variables: [], - }; - } - - // Process each variable and add to its collection - for (const v of allVars) { - const col = byCollection[v.variableCollectionId]; - if (!col) continue; // Should not happen - variable without collection - - // Normalize values for each mode - const valuesByMode: Record = {}; - for (const modeId of Object.keys(v.valuesByMode)) { - const raw = v.valuesByMode[modeId]; - valuesByMode[modeId] = normalizeModeValue(raw); - } - - // Build exported variable object - const exported: ExportedVariable = { - id: v.id, - name: v.name, - resolvedType: v.resolvedType, - isAlias: Object.values(valuesByMode).some((m) => m.type === 'ALIAS'), - scopes: (v.scopes || []) as string[], - valuesByMode, - codeSyntax: v.codeSyntax || undefined, - }; - col.variables.push(exported); - } - - // Sort collections and variables alphabetically for determinism - const sortedCollections = Object.values(byCollection) - .sort((a, b) => a.name.localeCompare(b.name)) - .map((c) => ({ - ...c, - variables: c.variables.sort((a, b) => a.name.localeCompare(b.name)), - })); - - // Build root export structure with metadata - const root: ExportRoot = { - meta: { - exportedAt: new Date().toISOString(), - fileName, - pluginVersion: PLUGIN_VERSION, - figmaFileId: fileId, - collectionsCount: sortedCollections.length, - variablesCount: sortedCollections.reduce((sum, c) => sum + c.variables.length, 0), - contentHash: '', // Will be set below - }, - collections: sortedCollections, - }; - - // Calculate hash of stable content (exclude volatile fields like exportedAt) - // This ensures the hash only changes when actual variable data changes - const stableContent = { - meta: { - fileName, - pluginVersion: PLUGIN_VERSION, - figmaFileId: fileId, - collectionsCount: root.meta.collectionsCount, - variablesCount: root.meta.variablesCount, - }, - collections: sortedCollections, - }; - const json = stableStringify(stableContent, 2); - root.meta.contentHash = await sha256(json); - - return root; - } catch (error) { - throw new Error( - `Failed to build variables JSON: ${error instanceof Error ? error.message : 'Unknown error'}` - ); - } -} - -/** - * Normalizes a raw mode value from Figma into our structured format. - * - * Figma's variable API returns mode values in different shapes: - * - Alias: { type: 'VARIABLE_ALIAS', id: '...' } - * - Color: { r: 0.5, g: 0.5, b: 0.5, a?: 1 } - * - Primitives (strings, numbers, booleans) - * - * This function detects the type and returns a normalized object. - * - * @param raw - Raw value from Figma's variable API - * @returns Normalized value with explicit type - */ -function normalizeModeValue(raw: unknown): VariableModeValue { - if (!raw || typeof raw !== 'object') { - // Primitives (string/number/boolean) or unexpected values - return inferPrimitive(raw); - } - - // Alias detection: Figma 2025 API uses { type: 'VARIABLE_ALIAS', id: '...' } - if ('type' in raw && raw.type === 'VARIABLE_ALIAS' && 'id' in raw && typeof raw.id === 'string') { - return { type: 'ALIAS', refVariableId: raw.id }; - } - - // Color detection: Figma colors have r, g, b properties (0-1 range) - if ( - 'r' in raw && - typeof raw.r === 'number' && - 'g' in raw && - typeof raw.g === 'number' && - 'b' in raw && - typeof raw.b === 'number' - ) { - const a = 'a' in raw && typeof raw.a === 'number' ? raw.a : 1; - return { type: 'COLOR', value: { r: raw.r, g: raw.g, b: raw.b, a } }; - } - - // Fallback to primitive inference - return inferPrimitive(raw); -} - -/** - * Infers the type of a primitive value and wraps it in our value format. - * - * @param val - Primitive value - * @returns Typed value object - */ -function inferPrimitive(val: unknown): VariableModeValue { - switch (typeof val) { - case 'string': - return { type: 'STRING', value: val }; - case 'number': - return { type: 'NUMBER', value: val }; - case 'boolean': - return { type: 'BOOLEAN', value: val }; - default: - // Fallback: convert to string - return { type: 'STRING', value: String(val) }; - } -} diff --git a/src/export/valueNormalization.ts b/src/export/valueNormalization.ts new file mode 100644 index 0000000..e01ead5 --- /dev/null +++ b/src/export/valueNormalization.ts @@ -0,0 +1,44 @@ +import type { VariableModeValue } from '../util/dtcgUtils'; + +/** + * Normalizes a variable's mode value into a structured format shared across + * export builders. + */ +export function normalizeModeValue(raw: unknown): VariableModeValue { + if (!raw || typeof raw !== 'object') { + return inferPrimitive(raw); + } + + const obj = raw as Record; + + if (obj.type === 'VARIABLE_ALIAS' && typeof obj.id === 'string') { + return { type: 'ALIAS', refVariableId: obj.id }; + } + + if (typeof obj.r === 'number' && typeof obj.g === 'number' && typeof obj.b === 'number') { + return { + type: 'COLOR', + value: { + r: obj.r, + g: obj.g, + b: obj.b, + a: typeof obj.a === 'number' ? obj.a : 1, + }, + }; + } + + return inferPrimitive(raw); +} + +function inferPrimitive(val: unknown): VariableModeValue { + switch (typeof val) { + case 'string': + return { type: 'STRING', value: val }; + case 'number': + return { type: 'NUMBER', value: val }; + case 'boolean': + return { type: 'BOOLEAN', value: val }; + default: + return { type: 'STRING', value: String(val) }; + } +} diff --git a/src/github/githubClient.ts b/src/github/githubClient.ts index 0fe0ae1..8834ca4 100644 --- a/src/github/githubClient.ts +++ b/src/github/githubClient.ts @@ -43,6 +43,32 @@ export interface UpsertResult { commitSha?: string; } +export interface FileCommitPayload { + /** File path within the repository */ + path: string; + /** Raw string content of the file */ + content: string; + /** Deterministic content hash embedded in the JSON */ + contentHash: string; +} + +export interface CommitFilesOptions { + owner: string; + repo: string; + branch: string; + token: string; + commitMessage: string; + files: FileCommitPayload[]; +} + +export interface CommitFilesResult { + updated: boolean; + skipped: boolean; + updatedPaths: string[]; + url?: string; + commitSha?: string; +} + /** * Options for upserting a file to GitHub. */ @@ -332,6 +358,31 @@ export function fromBase64(str: string): string { return result; } +function extractEmbeddedHashFromJsonContent(content: string): string | undefined { + try { + const parsed = JSON.parse(content) as Record; + if (parsed && typeof parsed === 'object') { + if (typeof parsed.contentHash === 'string') { + return parsed.contentHash; + } + const meta = parsed.meta as Record | undefined; + if (meta && typeof meta.contentHash === 'string') { + return meta.contentHash as string; + } + const extensions = parsed.$extensions as Record | undefined; + if (extensions && typeof extensions === 'object') { + const figmaExt = extensions['com.figma'] as Record | undefined; + if (figmaExt && typeof figmaExt['contentHash'] === 'string') { + return figmaExt['contentHash'] as string; + } + } + } + } catch { + // Ignore parse errors + } + return undefined; +} + /** * Creates or updates a file in a GitHub repository. * @@ -364,15 +415,7 @@ export async function upsertFile(options: GitHubUpsertOptions): Promise { + const { owner, repo, branch, token, commitMessage, files } = options; + + if (!files.length) { + return { updated: false, skipped: true, updatedPaths: [] }; + } + + await ensureBranch(owner, repo, branch, token); + + const filesToUpdate = await filterFilesNeedingUpdate(owner, repo, branch, token, files); + + if (!filesToUpdate.length) { + return { updated: false, skipped: true, updatedPaths: [] }; + } + + const headInfo = await getHeadInfo(owner, repo, branch, token); + + const treeEntries = [] as Array<{ path: string; mode: string; type: string; sha: string }>; + for (const file of filesToUpdate) { + const blobSha = await createBlob(owner, repo, token, file.content); + treeEntries.push({ path: file.path, mode: '100644', type: 'blob', sha: blobSha }); + } + + const treeSha = await createTree(owner, repo, token, headInfo.treeSha, treeEntries); + const commit = await createCommit(owner, repo, token, commitMessage, treeSha, headInfo.commitSha); + await updateBranchRef(owner, repo, branch, token, commit.sha); + + return { + updated: true, + skipped: false, + updatedPaths: filesToUpdate.map((file) => file.path), + url: `https://github.com/${owner}/${repo}/commit/${commit.sha}`, + commitSha: commit.sha, + }; +} + +async function filterFilesNeedingUpdate( + owner: string, + repo: string, + branch: string, + token: string, + files: FileCommitPayload[] +): Promise { + const updates: FileCommitPayload[] = []; + + for (const file of files) { + const existing = await getExistingFile(owner, repo, branch, file.path, token); + if (existing) { + try { + const decoded = fromBase64(existing.content); + const embeddedHash = extractEmbeddedHashFromJsonContent(decoded); + if (embeddedHash && embeddedHash === file.contentHash) { + continue; + } + } catch { + // Ignore parse errors and include file for update + } + } + updates.push(file); + } + + return updates; +} + +async function getHeadInfo( + owner: string, + repo: string, + branch: string, + token: string +): Promise<{ commitSha: string; treeSha: string }> { + const refRes = await ghFetch( + `https://api.github.com/repos/${owner}/${repo}/git/ref/heads/${branch}`, + token + ); + if (!refRes.ok) { + throw new Error('Failed to read branch reference'); + } + const refJson = await refRes.json(); + const commitSha = refJson.object?.sha; + if (!commitSha) { + throw new Error('Invalid branch reference response'); + } + + const commitRes = await ghFetch( + `https://api.github.com/repos/${owner}/${repo}/git/commits/${commitSha}`, + token + ); + if (!commitRes.ok) { + throw new Error('Failed to read commit metadata'); + } + const commitJson = await commitRes.json(); + const treeSha = commitJson.tree?.sha; + if (!treeSha) { + throw new Error('Commit missing tree information'); + } + + return { commitSha, treeSha }; +} + +async function createBlob( + owner: string, + repo: string, + token: string, + content: string +): Promise { + const res = await ghFetch(`https://api.github.com/repos/${owner}/${repo}/git/blobs`, token, { + method: 'POST', + body: JSON.stringify({ content, encoding: 'utf-8' }), + }); + if (!res.ok) { + throw new Error('Failed to create blob'); + } + const json = await res.json(); + return json.sha as string; +} + +async function createTree( + owner: string, + repo: string, + token: string, + baseTree: string, + entries: Array<{ path: string; mode: string; type: string; sha: string }> +): Promise { + const res = await ghFetch(`https://api.github.com/repos/${owner}/${repo}/git/trees`, token, { + method: 'POST', + body: JSON.stringify({ base_tree: baseTree, tree: entries }), + }); + if (!res.ok) { + throw new Error('Failed to create tree'); + } + const json = await res.json(); + return json.sha as string; +} + +async function createCommit( + owner: string, + repo: string, + token: string, + message: string, + treeSha: string, + parentSha: string +): Promise<{ sha: string }> { + const res = await ghFetch(`https://api.github.com/repos/${owner}/${repo}/git/commits`, token, { + method: 'POST', + body: JSON.stringify({ message, tree: treeSha, parents: [parentSha] }), + }); + if (!res.ok) { + throw new Error('Failed to create commit'); + } + return (await res.json()) as { sha: string }; +} + +async function updateBranchRef( + owner: string, + repo: string, + branch: string, + token: string, + sha: string +): Promise { + const res = await ghFetch( + `https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, + token, + { + method: 'PATCH', + body: JSON.stringify({ sha, force: false }), + } + ); + if (!res.ok) { + throw new Error('Failed to update branch reference'); + } +} diff --git a/src/messaging.ts b/src/messaging.ts index cab705c..e44bb02 100644 --- a/src/messaging.ts +++ b/src/messaging.ts @@ -10,8 +10,7 @@ * - Plugin → UI: Responses, notifications, and state updates */ -import { ExportRoot } from './shared/types'; -import { DtcgRoot } from './shared/dtcg-types'; +import type { ExportBundle, ExportFormat, ExportType } from './types/export'; /** * Messages sent from the UI to the Plugin. @@ -37,11 +36,11 @@ export type UIToPluginMessage = | { type: 'VALIDATE_TOKEN' } | { type: 'COMMIT_REQUEST'; - exportData: ExportRoot | DtcgRoot; + exportBundle: ExportBundle; dryRun: boolean; commitPrefix: string; } - | { type: 'FETCH_REMOTE_EXPORT' } // Request the current remote export JSON (if file exists) + | { type: 'FETCH_REMOTE_EXPORT'; files: string[] } | { type: 'COPY_TO_CLIPBOARD'; text: string } // Copy text to clipboard | { type: 'NOTIFY'; level: 'info' | 'error'; message: string } // Display notification in Figma UI | { type: 'PING' }; @@ -57,15 +56,21 @@ export type UIToPluginMessage = * - COMMIT_RESULT: Result of GitHub commit operation * - FETCH_REMOTE_EXPORT_RESULT: Remote file contents or null if not found */ +export interface RemoteFileResult { + path: string; + data?: unknown | null; + error?: string; +} + export type PluginToUIMessage = - | { type: 'EXPORT_RESULT'; ok: true; data: ExportRoot | DtcgRoot } + | { type: 'EXPORT_RESULT'; ok: true; data: ExportBundle } | { type: 'EXPORT_RESULT'; ok: false; error: string } | { type: 'SETTINGS_RESPONSE'; payload: PersistedSettings & { tokenPresent: boolean } } | { type: 'NOTIFY'; level: 'info' | 'error'; message: string } | { type: 'TOKEN_VALIDATION'; ok: boolean; login?: string; error?: string } | { type: 'COMMIT_RESULT'; ok: true; skipped?: boolean; url?: string } | { type: 'COMMIT_RESULT'; ok: false; error: string } - | { type: 'FETCH_REMOTE_EXPORT_RESULT'; ok: true; data: ExportRoot | DtcgRoot | null } + | { type: 'FETCH_REMOTE_EXPORT_RESULT'; ok: true; files: RemoteFileResult[] } | { type: 'FETCH_REMOTE_EXPORT_RESULT'; ok: false; error: string }; /** @@ -91,6 +96,12 @@ export interface PersistedSettings { dryRun?: boolean; /** Last known content hash for change detection */ lastHash?: string; + /** Map of repo-relative file paths to last known content hashes */ + lastHashes?: Record; + /** Preferred export format */ + exportFormat?: ExportFormat; + /** Export type for figma-native format */ + exportType?: ExportType; } /** @@ -113,5 +124,8 @@ export function defaultSettings(): PersistedSettings { filename: 'variables.json', commitPrefix: '', dryRun: false, + exportFormat: 'dtcg', + exportType: 'singleFile', + lastHashes: {}, }; } diff --git a/src/plugin.ts b/src/plugin.ts index 185763a..92aa19d 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,45 +1,24 @@ /** * FigGit - Main Plugin Logic * - * This file runs in the Figma plugin sandbox (restricted JavaScript environment). - * It handles: - * - Variable extraction from Figma files - * - Communication with the UI via postMessage - * - Settings persistence in Figma's plugin data storage - * - Secure GitHub token storage in clientStorage - * - GitHub API interactions for committing files - * - * Note: No access to browser APIs - uses pure JavaScript implementations - * for SHA-256, Base64 encoding, and UTF-8 conversion. + * Runs inside the Figma plugin sandbox and coordinates: + * - Exporting variables in the selected format + * - Persisting settings and GitHub token state + * - Handling GitHub commits and remote diffs */ /// -import { buildDtcgJson } from './export/buildDtcgJson'; -import { - SETTINGS_KEY, - defaultSettings, - UIToPluginMessage, - PluginToUIMessage, - PersistedSettings, -} from './messaging'; -import { upsertFile, fromBase64 } from './github/githubClient'; +import { buildExportBundle } from './export/buildExportBundle'; +import { SETTINGS_KEY, defaultSettings, UIToPluginMessage, PersistedSettings } from './messaging'; +import { commitFiles, fromBase64 } from './github/githubClient'; import { stableStringify } from './util/stableStringify'; +import { buildRepoPath } from './util/path'; import { validateAllSettings } from './util/validation'; -// Show UI on plugin start +type CommitRequestMessage = Extract; + figma.showUI(__html__, { width: 600, height: 750, themeColors: true }); -/** - * Retrieves plugin settings from Figma's persistent storage. - * - * Settings are stored in the root node's plugin data and include: - * - GitHub repository configuration (owner, repo, branch) - * - Output file settings (folder, filename) - * - Commit customization (prefix) - * - Last known content hash (for change detection) - * - * @returns Promise resolving to persisted settings or defaults if none exist - */ async function getStoredSettings(): Promise { try { const raw = figma.root.getPluginData(SETTINGS_KEY); @@ -50,42 +29,22 @@ async function getStoredSettings(): Promise { } } -/** - * Saves plugin settings to Figma's persistent storage. - * - * These settings persist across plugin sessions and are specific to the current - * Figma file (not shared across files or users). - * - * @param settings - Settings object to persist - */ async function saveSettings(settings: PersistedSettings) { figma.root.setPluginData(SETTINGS_KEY, JSON.stringify(settings)); } -/** - * Checks if a GitHub Personal Access Token is stored. - * - * Tokens are stored in clientStorage (secure, local-only storage) and are: - * - Never exported or sent to the UI after initial save - * - Accessible only within the plugin sandbox - * - Persistent across plugin sessions - * - * @returns Promise resolving to true if token exists, false otherwise - */ async function hasToken(): Promise { return !!(await figma.clientStorage.getAsync('github_pat')); } -/** - * Validates the stored GitHub token by calling the GitHub API. - * - * Makes a test request to https://api.github.com/user to verify: - * - Token is valid and not expired - * - Token has necessary permissions - * - Network connectivity to GitHub - * - * Sends result back to UI via TOKEN_VALIDATION message. - */ +async function postSettingsResponse(seed?: PersistedSettings) { + const settings = seed ?? (await getStoredSettings()); + figma.ui.postMessage({ + type: 'SETTINGS_RESPONSE', + payload: { ...settings, tokenPresent: await hasToken() }, + }); +} + async function validateTokenAndNotify() { try { const token = await figma.clientStorage.getAsync('github_pat'); @@ -93,292 +52,293 @@ async function validateTokenAndNotify() { figma.ui.postMessage({ type: 'TOKEN_VALIDATION', ok: false, error: 'No token stored' }); return; } + const res = await fetch('https://api.github.com/user', { headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' }, }); + if (!res.ok) { figma.ui.postMessage({ type: 'TOKEN_VALIDATION', ok: false, error: `Status ${res.status}` }); return; } - const json = (await res.json()) as { login: string }; + + const json = (await res.json()) as { login?: string }; figma.ui.postMessage({ type: 'TOKEN_VALIDATION', ok: true, login: json.login }); - } catch (e) { - const error = e instanceof Error ? e.message : 'Validation failed'; - figma.ui.postMessage({ type: 'TOKEN_VALIDATION', ok: false, error }); + } catch (err) { + const message = err instanceof Error ? err.message : 'Unknown error'; + figma.ui.postMessage({ type: 'TOKEN_VALIDATION', ok: false, error: message }); } } -/** - * Message handler for UI-to-Plugin communication. - * - * Handles all messages sent from the React UI via postMessage: - * - * REQUEST_EXPORT: Extract variables from current Figma file - * FETCH_REMOTE_EXPORT: Fetch existing JSON from GitHub repository - * REQUEST_SETTINGS: Send current settings to UI - * SAVE_SETTINGS: Persist settings to plugin data - * SAVE_TOKEN: Store GitHub token in clientStorage - * CLEAR_TOKEN: Remove GitHub token from clientStorage - * VALIDATE_TOKEN: Test GitHub token validity - * COMMIT_REQUEST: Commit exported JSON to GitHub - * - * All responses are sent back to UI via postMessage with appropriate - * message types defined in messaging.ts. - */ -figma.ui.onmessage = async (msg: UIToPluginMessage) => { - // Extract variables from the current Figma file - if (msg.type === 'REQUEST_EXPORT') { - try { - const data = await buildDtcgJson(figma); - figma.ui.postMessage({ type: 'EXPORT_RESULT', ok: true, data } as PluginToUIMessage); - } catch (e) { - figma.ui.postMessage({ - type: 'EXPORT_RESULT', - ok: false, - error: (e as Error).message, - } as PluginToUIMessage); - } +async function handleExportRequest() { + try { + const settings = await getStoredSettings(); + const bundle = await buildExportBundle(figma, settings); + figma.ui.postMessage({ type: 'EXPORT_RESULT', ok: true, data: bundle }); + } catch (err) { + const message = err instanceof Error ? err.message : 'Unknown error'; + figma.ui.postMessage({ type: 'EXPORT_RESULT', ok: false, error: message }); } +} - // Fetch existing exported JSON from GitHub for diff comparison - if (msg.type === 'FETCH_REMOTE_EXPORT') { - try { - const settings = await getStoredSettings(); - const token = await figma.clientStorage.getAsync('github_pat'); - if (!token) { - figma.ui.postMessage({ - type: 'FETCH_REMOTE_EXPORT_RESULT', - ok: false, - error: 'Missing token', - }); - return; - } - const path = - (settings.folder - ? settings.folder.replace(/\\+/g, '/').replace(/^\//, '').replace(/\/$/, '') + '/' - : '') + settings.filename; - const url = `https://api.github.com/repos/${settings.owner}/${settings.repo}/contents/${encodeURIComponent(path)}?ref=${settings.branch}`; - const res = await fetch(url, { - headers: { Accept: 'application/vnd.github+json', Authorization: `Bearer ${token}` }, +async function handleCommitRequest(msg: CommitRequestMessage) { + try { + const exportBundle = msg.exportBundle; + if (!exportBundle || !exportBundle.documents.length) { + figma.ui.postMessage({ + type: 'COMMIT_RESULT', + ok: false, + error: 'No export data to commit', }); - if (res.status === 404) { - figma.ui.postMessage({ type: 'FETCH_REMOTE_EXPORT_RESULT', ok: true, data: null }); - return; - } - if (!res.ok) { - figma.ui.postMessage({ - type: 'FETCH_REMOTE_EXPORT_RESULT', - ok: false, - error: `Status ${res.status}`, - }); - return; - } - const json = await res.json(); - try { - const decoded = fromBase64(json.content); - const parsed = JSON.parse(decoded); - figma.ui.postMessage({ type: 'FETCH_REMOTE_EXPORT_RESULT', ok: true, data: parsed }); - } catch (e) { - const error = e instanceof Error ? e.message : 'Unknown'; - figma.ui.postMessage({ - type: 'FETCH_REMOTE_EXPORT_RESULT', - ok: false, - error: 'Parse error: ' + error, - }); - } - } catch (e) { - const error = e instanceof Error ? e.message : 'Unknown error'; - figma.ui.postMessage({ type: 'FETCH_REMOTE_EXPORT_RESULT', ok: false, error }); + return; } - } - // Send current settings to UI (requested on plugin load and after changes) - if (msg.type === 'REQUEST_SETTINGS') { - const s = await getStoredSettings(); - figma.ui.postMessage({ - type: 'SETTINGS_RESPONSE', - payload: { ...s, tokenPresent: await hasToken() }, - } as PluginToUIMessage); - } + if (msg.dryRun) { + figma.ui.postMessage({ type: 'COMMIT_RESULT', ok: true, skipped: true }); + return; + } - // Save repository/export settings (not including token) - if (msg.type === 'SAVE_SETTINGS') { - await saveSettings(msg.payload); - figma.ui.postMessage({ - type: 'NOTIFY', - level: 'info', - message: 'Settings saved', - } as PluginToUIMessage); - } + const token = await figma.clientStorage.getAsync('github_pat'); + if (!token) { + figma.ui.postMessage({ type: 'COMMIT_RESULT', ok: false, error: 'Missing token' }); + return; + } - // Store GitHub Personal Access Token in secure clientStorage - if (msg.type === 'SAVE_TOKEN') { - try { - await figma.clientStorage.setAsync('github_pat', msg.token.trim()); - figma.ui.postMessage({ type: 'NOTIFY', level: 'info', message: 'Token stored locally' }); + const settings = await getStoredSettings(); + const validationResult = validateAllSettings({ + owner: settings.owner, + repo: settings.repo, + branch: settings.branch, + filename: settings.filename, + folder: settings.folder, + }); + if (!validationResult.valid) { figma.ui.postMessage({ - type: 'SETTINGS_RESPONSE', - payload: { ...(await getStoredSettings()), tokenPresent: true }, + type: 'COMMIT_RESULT', + ok: false, + error: `Validation error: ${validationResult.error}`, }); - // Auto validate after save - await validateTokenAndNotify(); - } catch { - figma.ui.postMessage({ type: 'NOTIFY', level: 'error', message: 'Failed saving token' }); + return; } - } - // Remove GitHub token from clientStorage - if (msg.type === 'CLEAR_TOKEN') { - await figma.clientStorage.deleteAsync('github_pat'); - figma.ui.postMessage({ type: 'NOTIFY', level: 'info', message: 'Token cleared' }); + const storedHashes = getLastHashMap(settings); + const pendingDocs = exportBundle.documents.filter( + (doc) => storedHashes[doc.relativePath] !== doc.contentHash + ); + + if (!pendingDocs.length) { + figma.ui.postMessage({ type: 'COMMIT_RESULT', ok: true, skipped: true }); + return; + } + + const overridePrefix = msg.commitPrefix?.trim(); + const defaultPrefix = settings.commitPrefix?.trim(); + const prefixValue = overridePrefix || defaultPrefix || ''; + const prefix = prefixValue ? `${prefixValue} ` : ''; + const timestamp = new Date().toISOString(); + const vars = exportBundle.summary.variablesCount; + const cols = exportBundle.summary.collectionsCount; + const commitMessage = `${prefix}update Figma variables (${vars} vars, ${cols} collections) - ${timestamp}`; + + const files = pendingDocs.map((doc) => ({ + path: doc.relativePath, + content: stableStringify(doc.data, 2), + contentHash: doc.contentHash, + })); + + const result = await commitFiles({ + owner: settings.owner, + repo: settings.repo, + branch: settings.branch, + token, + commitMessage, + files, + }); + + const nextHashes = { ...storedHashes }; + for (const doc of exportBundle.documents) { + nextHashes[doc.relativePath] = doc.contentHash; + } + + await saveSettings({ + ...settings, + lastHashes: nextHashes, + lastHash: exportBundle.summary.contentHash, + }); + figma.ui.postMessage({ - type: 'SETTINGS_RESPONSE', - payload: { ...(await getStoredSettings()), tokenPresent: false }, + type: 'COMMIT_RESULT', + ok: true, + skipped: result.skipped, + url: result.url, }); + } catch (err) { + const message = err instanceof Error ? err.message : 'Unknown error'; + figma.ui.postMessage({ type: 'COMMIT_RESULT', ok: false, error: formatGitHubError(message) }); } +} - // Test GitHub token validity - if (msg.type === 'VALIDATE_TOKEN') { - await validateTokenAndNotify(); +async function handleFetchRemoteExport(paths: string[]) { + if (!paths.length) { + figma.ui.postMessage({ type: 'FETCH_REMOTE_EXPORT_RESULT', ok: true, files: [] }); + return; } - // Copy text to clipboard (handled in UI, this is just for notification) - if (msg.type === 'COPY_TO_CLIPBOARD') { - // The UI handles the actual copying, we just acknowledge - // This message type exists for potential future enhancements - } + try { + const settings = await getStoredSettings(); + if (!settings.owner || !settings.repo || !settings.branch) { + throw new Error('Configure repository settings to fetch remote files'); + } - // Display notification in Figma UI - if (msg.type === 'NOTIFY') { - figma.notify(msg.message, { error: msg.level === 'error' }); - } + const token = await figma.clientStorage.getAsync('github_pat'); + if (!token) { + throw new Error('Missing token'); + } - // Commit exported variables JSON to GitHub - if (msg.type === 'COMMIT_REQUEST') { - try { - const exportData = msg.exportData; + const normalizedPaths = Array.from( + new Set(paths.map((path) => normalizeRepoPath(path)).filter((path) => path.length)) + ); - // Handle dry run mode (test without actually committing) - if (msg.dryRun) { - figma.ui.postMessage({ type: 'COMMIT_RESULT', ok: true, skipped: true }); - return; - } + const results: Array<{ path: string; data?: unknown | null; error?: string }> = []; - const token = await figma.clientStorage.getAsync('github_pat'); - if (!token) { - figma.ui.postMessage({ type: 'COMMIT_RESULT', ok: false, error: 'Missing token' }); - return; - } - const settings = await getStoredSettings(); - - // Validate settings before proceeding - const validationResult = validateAllSettings({ - owner: settings.owner, - repo: settings.repo, - branch: settings.branch, - filename: settings.filename, - folder: settings.folder, - }); - if (!validationResult.valid) { - figma.ui.postMessage({ - type: 'COMMIT_RESULT', - ok: false, - error: `Validation error: ${validationResult.error}`, + for (const path of normalizedPaths) { + try { + const encodedPath = encodeContentPath(path); + const url = `https://api.github.com/repos/${settings.owner}/${settings.repo}/contents/${encodedPath}?ref=${settings.branch}`; + const res = await fetch(url, { + headers: { + Authorization: `Bearer ${token}`, + Accept: 'application/vnd.github+json', + }, }); - return; - } - // Extract metadata (supports both legacy and DTCG formats) - let newHash = ''; - let vars = 0; - let cols = 0; - - // Check if it's DTCG format (has $extensions) or legacy format (has meta) - if ('$extensions' in exportData) { - // DTCG format - const figmaExt = exportData.$extensions?.['com.figma'] as - | Record - | undefined; - newHash = (figmaExt?.contentHash as string) || ''; - vars = (figmaExt?.variablesCount as number) || 0; - cols = (figmaExt?.collectionsCount as number) || 0; - } else if ('meta' in exportData) { - // Legacy format - const meta = exportData.meta as Record; - newHash = (meta.contentHash as string) || ''; - vars = (meta.variablesCount as number) || 0; - cols = (meta.collectionsCount as number) || 0; - } + if (res.status === 404) { + results.push({ path, data: null }); + continue; + } - // Hash comparison happens only here in the plugin (single source of truth) - // This prevents redundant commits when content hasn't actually changed - if (settings.lastHash && settings.lastHash === newHash) { - figma.ui.postMessage({ type: 'COMMIT_RESULT', ok: true, skipped: true }); - return; - } + if (!res.ok) { + results.push({ path, error: `Status ${res.status}` }); + continue; + } - // Build commit message with variable/collection counts and timestamp - const ts = new Date().toISOString(); - const prefix = - msg.commitPrefix || settings.commitPrefix - ? `${msg.commitPrefix || settings.commitPrefix} ` - : ''; - const commitMessage = `${prefix}update Figma variables (${vars} vars, ${cols} collections) - ${ts}`; - const path = - (settings.folder - ? settings.folder.replace(/\\+/g, '/').replace(/^\//, '').replace(/\/$/, '') + '/' - : '') + settings.filename; - const json = stableStringify(exportData, 2); - - // Attempt to commit to GitHub (with automatic retry on 409 conflicts) - const result = await upsertFile({ - owner: settings.owner, - repo: settings.repo, - branch: settings.branch, - path, - content: json, - token, - commitMessage, - currentHash: newHash as string, - }); + const json = await res.json(); + if (typeof json.content !== 'string') { + results.push({ path, error: 'Missing content' }); + continue; + } - // Update stored hash only if commit succeeded and wasn't skipped - if (!result.skipped && newHash) { - await saveSettings({ ...settings, lastHash: newHash }); + try { + const decoded = fromBase64(json.content); + const parsed = JSON.parse(decoded); + results.push({ path, data: parsed }); + } catch (parseError) { + const error = parseError instanceof Error ? parseError.message : 'Unknown error'; + results.push({ path, error: 'Parse error: ' + error }); + } + } catch (innerError) { + const message = innerError instanceof Error ? innerError.message : 'Unknown error'; + results.push({ path, error: message }); } + } - figma.ui.postMessage({ - type: 'COMMIT_RESULT', - ok: true, - skipped: result.skipped, - url: result.url, - }); - } catch (e) { - const error = e instanceof Error ? e : new Error(String(e)); - console.error('Commit error:', error); - let msgText = error.message || 'Unknown error'; - // Add stack trace for debugging if available - if (error.stack) { - console.error('Stack:', error.stack); - } - // Provide user-friendly error messages for common HTTP status codes - if (/401/.test(msgText)) msgText = 'Unauthorized (check token)'; - else if (/403/.test(msgText)) msgText = 'Forbidden (check permissions)'; - else if (/404/.test(msgText)) msgText = 'Not found (check repository)'; - else if (/422/.test(msgText)) msgText = 'Validation failed (check branch/file path)'; - figma.ui.postMessage({ type: 'COMMIT_RESULT', ok: false, error: msgText }); + figma.ui.postMessage({ type: 'FETCH_REMOTE_EXPORT_RESULT', ok: true, files: results }); + } catch (err) { + const message = err instanceof Error ? err.message : 'Unknown error'; + figma.ui.postMessage({ type: 'FETCH_REMOTE_EXPORT_RESULT', ok: false, error: message }); + } +} + +function normalizeRepoPath(path: string): string { + return path.trim().replace(/\\+/g, '/').replace(/^\/+/g, ''); +} + +function encodeContentPath(path: string): string { + return path + .split('/') + .filter((segment) => segment.length) + .map((segment) => encodeURIComponent(segment)) + .join('/'); +} + +function formatGitHubError(message: string): string { + if (/401/.test(message)) return 'Unauthorized (check token)'; + if (/403/.test(message)) return 'Forbidden (check permissions)'; + if (/404/.test(message)) return 'Not found (check repository)'; + if (/409/.test(message)) return 'Conflict (branch updated)'; + if (/422/.test(message)) return 'Validation failed (check branch/path)'; + return message; +} + +function getLastHashMap(settings: PersistedSettings): Record { + const map = { ...(settings.lastHashes || {}) }; + if (!Object.keys(map).length && settings.lastHash) { + const legacyPath = buildRepoPath(settings.folder, settings.filename); + map[legacyPath] = settings.lastHash; + } + return map; +} + +figma.ui.onmessage = async (msg: UIToPluginMessage) => { + switch (msg.type) { + case 'REQUEST_EXPORT': + await handleExportRequest(); + break; + case 'REQUEST_SETTINGS': + await postSettingsResponse(); + break; + case 'SAVE_SETTINGS': { + const merged = { ...defaultSettings(), ...msg.payload }; + await saveSettings(merged); + await postSettingsResponse(merged); + break; } + case 'SAVE_TOKEN': + try { + const trimmed = msg.token.trim(); + if (!trimmed) { + figma.ui.postMessage({ + type: 'NOTIFY', + level: 'error', + message: 'Token cannot be empty', + }); + return; + } + await figma.clientStorage.setAsync('github_pat', trimmed); + figma.ui.postMessage({ type: 'NOTIFY', level: 'info', message: 'Token stored locally' }); + await postSettingsResponse(); + await validateTokenAndNotify(); + } catch { + figma.ui.postMessage({ type: 'NOTIFY', level: 'error', message: 'Failed saving token' }); + } + break; + case 'CLEAR_TOKEN': + await figma.clientStorage.deleteAsync('github_pat'); + figma.ui.postMessage({ type: 'NOTIFY', level: 'info', message: 'Token cleared' }); + await postSettingsResponse(); + break; + case 'VALIDATE_TOKEN': + await validateTokenAndNotify(); + break; + case 'COMMIT_REQUEST': + await handleCommitRequest(msg); + break; + case 'FETCH_REMOTE_EXPORT': + await handleFetchRemoteExport(msg.files); + break; + case 'COPY_TO_CLIPBOARD': + break; + case 'NOTIFY': + figma.notify(msg.message, { error: msg.level === 'error' }); + break; + case 'PING': + figma.ui.postMessage({ type: 'NOTIFY', level: 'info', message: 'Plugin ready' }); + break; } }; -/** - * Optional run handler for future command integration. - * - * This allows the plugin to be manually triggered via Figma's command palette - * or other plugin entry points. - */ -figma.on('run', () => { +figma.on('run', async () => { figma.ui.show(); figma.ui.resize(600, 750); - figma.ui.postMessage({ type: 'REQUEST_SETTINGS' }); + await postSettingsResponse(); }); diff --git a/src/shared/figma-native-types.ts b/src/shared/figma-native-types.ts new file mode 100644 index 0000000..9952f96 --- /dev/null +++ b/src/shared/figma-native-types.ts @@ -0,0 +1,63 @@ +export type NativeExportType = 'singleFile' | 'perCollection'; + +export interface FigmaNativeMode { + id: string; + name: string; + description?: string; +} + +export interface CodeSyntaxEntry { + platform: string; + value: string; +} + +export interface ValueByModeEntry { + value: string | number | boolean; + referencedVariableId?: string; + referencedVariableName?: string; +} + +export interface FigmaNativeVariable { + id: string; + name: string; + path: string; + type: 'color' | 'number' | 'string' | 'boolean'; + description?: string; + scopes?: string[]; + codeSyntax?: CodeSyntaxEntry[]; + aliases?: string[]; + hiddenFromPublishing?: boolean; + valueByMode: Record; +} + +export interface FigmaNativeGroup { + id: string; + name: string; + path: string; + groups: FigmaNativeGroup[]; + variables: FigmaNativeVariable[]; +} + +export interface FigmaNativeCollection { + id: string; + name: string; + description?: string; + modes: FigmaNativeMode[]; + groups: FigmaNativeGroup[]; + variables: FigmaNativeVariable[]; + collectionVariablesCount: number; +} + +export interface FigmaNativeExportDocument { + collectionsCount: number; + variablesCount: number; + contentHash: string; + exportedAt: string; + fileName: string; + pluginVersion: string; + exportFormat: 'figma-native'; + exportType: NativeExportType; + collectionId?: string; + collectionName?: string; + collections: FigmaNativeCollection[]; +} diff --git a/src/shared/types.ts b/src/shared/types.ts deleted file mode 100644 index ed65a38..0000000 --- a/src/shared/types.ts +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Shared Type Definitions - * - * Core data structures used throughout the Figgit plugin for representing - * Figma variables and their export formats. These types are shared between - * the plugin runtime and UI code. - */ - -/** - * Represents a single value for a variable in a specific mode. - * - * A mode value can be either a concrete value (color, string, number, boolean) - * or an alias reference to another variable. - */ -export interface VariableModeValue { - /** The data type of this value */ - type: 'COLOR' | 'STRING' | 'NUMBER' | 'BOOLEAN' | 'ALIAS'; - - /** - * The actual value when type is not ALIAS. - * - For COLOR: RGB object like { r: 0.5, g: 0.5, b: 0.5, a: 1 } - * - For STRING: string value - * - For NUMBER: numeric value - * - For BOOLEAN: boolean value - * - For ALIAS: undefined (use refVariableId instead) - */ - value?: string | number | boolean | { r: number; g: number; b: number; a: number }; - - /** - * Variable ID being referenced when type is ALIAS. - * Points to another ExportedVariable's id property. - */ - refVariableId?: string; -} - -/** - * Represents a single exported Figma variable with all its properties. - * - * Each variable can have different values per mode (like light/dark themes) - * and can either contain concrete values or reference other variables via aliases. - */ -export interface ExportedVariable { - /** Unique Figma variable ID */ - id: string; - - /** Human-readable variable name (e.g., "color/primary/500") */ - name: string; - - /** Resolved type after following any alias chain */ - resolvedType: string; - - /** True if this variable references another variable (all modes are aliases) */ - isAlias: boolean; - - /** - * Scopes where this variable can be applied in Figma. - * Examples: ["ALL_SCOPES"], ["FRAME_FILL", "SHAPE_FILL"], etc. - */ - scopes: string[]; - - /** - * Map of mode ID to the variable's value in that mode. - * Each collection can have multiple modes (e.g., "Light", "Dark"). - */ - valuesByMode: Record; - - /** - * Optional code generation snippets for different platforms. - * Example: { "WEB": "var(--color-primary-500)", "ANDROID": "@color/primary_500" } - */ - codeSyntax?: Record; -} - -/** - * Represents a Figma variable collection containing related variables. - * - * Collections group variables together and define the modes (variants) - * that all variables in the collection can have values for. - */ -export interface ExportedCollection { - /** Unique Figma collection ID */ - id: string; - - /** Human-readable collection name */ - name: string; - - /** - * Available modes for this collection. - * All variables in the collection have values for each mode. - * Example: [{ id: "1:0", name: "Light" }, { id: "1:1", name: "Dark" }] - */ - modes: { id: string; name: string }[]; - - /** All variables belonging to this collection */ - variables: ExportedVariable[]; -} - -/** - * Root export format containing all collections and metadata. - * - * This is the legacy export format. The plugin also supports DTCG - * (Design Tokens Community Group) format via the DtcgRoot type. - */ -export interface ExportRoot { - /** Metadata about the export operation and source file */ - meta: { - /** ISO timestamp of when the export was generated */ - exportedAt: string; - - /** Target filename for the exported JSON */ - fileName: string; - - /** Figgit plugin version that generated this export */ - pluginVersion: string; - - /** Figma file ID where these variables came from */ - figmaFileId: string; - - /** Total number of collections exported */ - collectionsCount: number; - - /** Total number of variables exported across all collections */ - variablesCount: number; - - /** - * SHA-256 hash of the normalized export content. - * Used to skip commits when content hasn't changed. - */ - contentHash?: string; - }; - - /** All exported variable collections */ - collections: ExportedCollection[]; -} diff --git a/src/types/export.ts b/src/types/export.ts new file mode 100644 index 0000000..116df6b --- /dev/null +++ b/src/types/export.ts @@ -0,0 +1,39 @@ +import type { DtcgRoot } from '../shared/dtcg-types'; +import type { FigmaNativeExportDocument } from '../shared/figma-native-types'; + +export type ExportFormat = 'dtcg' | 'figma-native'; +export type ExportType = 'singleFile' | 'perCollection'; + +export interface ExportDocument { + id: string; + label: string; + format: ExportFormat; + exportType: ExportType; + relativePath: string; + fileName: string; + data: T; + contentHash: string; + variablesCount: number; + collectionsCount: number; + collectionId?: string; + collectionName?: string; +} + +export interface ExportSummary { + format: ExportFormat; + exportType: ExportType; + exportedAt: string; + pluginVersion: string; + fileName: string; + variablesCount: number; + collectionsCount: number; + documentsCount: number; + contentHash: string; +} + +export interface ExportBundle { + summary: ExportSummary; + documents: ExportDocument[]; +} + +export type ExportDocumentData = DtcgRoot | FigmaNativeExportDocument | Record; diff --git a/src/ui/components/export/StatusSummary.tsx b/src/ui/components/export/StatusSummary.tsx index 86343be..13891e4 100644 --- a/src/ui/components/export/StatusSummary.tsx +++ b/src/ui/components/export/StatusSummary.tsx @@ -18,8 +18,6 @@ import { IconApprovedCheckmark24, } from '@create-figma-plugin/ui'; import { usePlugin } from '../../context/PluginContext'; -import { ExportRoot } from '../../../shared/types'; -import { DtcgRoot, DtcgFigmaExtensions } from '../../../shared/dtcg-types'; export const StatusSummary: FunctionComponent = () => { const { exportState, settings } = usePlugin(); @@ -56,22 +54,12 @@ export const StatusSummary: FunctionComponent = () => { ); } - // Extract metadata from either legacy or DTCG format - let variablesCount = 0; - let collectionsCount = 0; - - if ('meta' in exportState.data) { - // Legacy format (ExportRoot) - const data = exportState.data as ExportRoot; - variablesCount = data.meta.variablesCount; - collectionsCount = data.meta.collectionsCount; - } else if ('$extensions' in exportState.data && exportState.data.$extensions?.['com.figma']) { - // DTCG format (DtcgRoot) - const data = exportState.data as DtcgRoot; - const figmaExt = data.$extensions?.['com.figma'] as DtcgFigmaExtensions | undefined; - variablesCount = figmaExt?.variablesCount ?? 0; - collectionsCount = figmaExt?.collectionsCount ?? 0; - } + const bundle = exportState.data; + const variablesCount = bundle?.summary.variablesCount ?? 0; + const collectionsCount = bundle?.summary.collectionsCount ?? 0; + const formatLabel = bundle?.summary.format === 'figma-native' ? 'Figma-native' : 'DTCG'; + const exportTypeLabel = + bundle?.summary.exportType === 'perCollection' ? 'Per collection' : 'Single file'; return ( @@ -96,6 +84,15 @@ export const StatusSummary: FunctionComponent = () => { + + Format: + + + {formatLabel} · {exportTypeLabel} + + + + Target: diff --git a/src/ui/components/preview/DiffViewer.tsx b/src/ui/components/preview/DiffViewer.tsx index bb1dd66..beb823f 100644 --- a/src/ui/components/preview/DiffViewer.tsx +++ b/src/ui/components/preview/DiffViewer.tsx @@ -1,18 +1,3 @@ -/** - * DiffViewer Component - * - * Displays a token-level diff between the local DTCG export and the remote - * file stored in GitHub. Helps users understand what changes will be committed - * before they commit. - * - * Features: - * - Automatically fetches remote data when opened - * - Recursively traverses nested DTCG token structure - * - Categorizes changes as added, removed, or changed - * - Shows counts and first 10 items per category - * - Handles loading, error, and "no remote file" states - */ - import { h, FunctionComponent } from 'preact'; import { useEffect, useMemo } from 'preact/hooks'; import { @@ -23,134 +8,50 @@ import { Banner, LoadingIndicator, IconInfoSmall24, - VerticalSpace, } from '@create-figma-plugin/ui'; import { usePlugin } from '../../context/PluginContext'; import { stableStringify } from '../../../util/stableStringify'; +import type { ExportDocument } from '../../../types/export'; +import type { FigmaNativeExportDocument } from '../../../shared/figma-native-types'; import styles from './DiffViewer.module.css'; -/** - * Summary counts of changes detected in the diff. - */ interface DiffCounts { - /** Number of tokens added (present in local but not remote) */ added: number; - /** Number of tokens removed (present in remote but not local) */ removed: number; - /** Number of tokens changed (present in both but with different values) */ changed: number; } -/** - * Represents a single token change in the diff. - */ -interface TokenChange { - /** Dot-separated path to the token (e.g., "color.bg.primary") */ - path: string; - /** Type of change detected */ - type: 'added' | 'removed' | 'changed'; - /** Token value before change (only present for 'changed' and 'removed') */ - before?: unknown; - /** Token value after change (only present for 'changed' and 'added') */ - after?: unknown; +interface DocumentDiffResult { + doc: ExportDocument; + counts: DiffCounts; + added: string[]; + removed: string[]; + changed: string[]; + remoteMissing: boolean; + error?: string; } export const DiffViewer: FunctionComponent = () => { const { exportState, remoteDataState, fetchRemoteData, settings } = usePlugin(); - // Fetch remote data on mount if not already fetched useEffect(() => { - if (settings && !remoteDataState.loading && !remoteDataState.fetched) { - fetchRemoteData(); + if ( + settings && + exportState.data && + exportState.data.documents.length && + !remoteDataState.loading && + !remoteDataState.fetched + ) { + const paths = exportState.data.documents.map((doc) => doc.relativePath); + fetchRemoteData(paths); } - }, [settings, remoteDataState.loading, remoteDataState.fetched, fetchRemoteData]); - - /** - * Computes the diff between local and remote DTCG data. - * - * This memoized calculation: - * 1. Recursively collects all token paths from both local and remote data - * 2. Compares tokens using stable JSON serialization - * 3. Categorizes changes as added, removed, or changed - * 4. Returns structured diff data for rendering - * - * Re-runs only when local or remote data changes. - */ - const diff = useMemo(() => { - if (!exportState.data || !remoteDataState.data) return null; - - const changes: TokenChange[] = []; - - /** - * Recursively walks the DTCG nested structure and collects all token paths. - * - * DTCG tokens are identified by the presence of a `$value` property. - * Groups are identified by nested objects without `$value`. - * - * @param obj - Object to traverse (token group or root) - * @param prefix - Current path prefix for nested traversal - * @returns Map of token path → token object - */ - const collectTokenPaths = (obj: Record, prefix = ''): Map => { - const tokens = new Map(); - - for (const [key, value] of Object.entries(obj)) { - // Skip DTCG metadata keys ($extensions, $type, etc.) - if (key.startsWith('$')) continue; - - const path = prefix ? `${prefix}.${key}` : key; - - if (value && typeof value === 'object') { - // Check if this is a token (has $value) or a group - if ('$value' in value) { - tokens.set(path, value); - } else { - // It's a group, recurse into children - - const nested = collectTokenPaths(value as Record, path); - nested.forEach((v, k) => tokens.set(k, v)); - } - } - } - - return tokens; - }; - - // Collect all token paths from both local and remote data - const localTokens = collectTokenPaths(exportState.data as Record); - const remoteTokens = collectTokenPaths(remoteDataState.data as Record); - - // Find added tokens (present in local but not in remote) - localTokens.forEach((value, path) => { - if (!remoteTokens.has(path)) { - changes.push({ path, type: 'added', after: value }); - } else { - // Token exists in both - check if value changed - const remoteValue = remoteTokens.get(path); - const localStr = stableStringify(value); - const remoteStr = stableStringify(remoteValue); - if (localStr !== remoteStr) { - changes.push({ path, type: 'changed', before: remoteValue, after: value }); - } - } - }); - - // Find removed tokens (present in remote but not in local) - remoteTokens.forEach((value, path) => { - if (!localTokens.has(path)) { - changes.push({ path, type: 'removed', before: value }); - } - }); - - // Calculate summary counts by type - const counts: DiffCounts = { - added: changes.filter((c) => c.type === 'added').length, - removed: changes.filter((c) => c.type === 'removed').length, - changed: changes.filter((c) => c.type === 'changed').length, - }; - - return { changes, counts }; - }, [exportState.data, remoteDataState.data]); + }, [ + settings, + exportState.data, + remoteDataState.loading, + remoteDataState.fetched, + fetchRemoteData, + ]); if (!exportState.data) { return ( @@ -161,7 +62,7 @@ export const DiffViewer: FunctionComponent = () => { } if (remoteDataState.loading) { - return Fetching remote file...; + return Fetching remote files...; } if (remoteDataState.error) { @@ -172,117 +73,287 @@ export const DiffViewer: FunctionComponent = () => { ); } - if (!remoteDataState.data) { - return ( - }> - Variables haven't been committed yet. All exported variables will be additions. - - ); - } + const diffs = useMemo(() => { + if (!exportState.data) return []; + return exportState.data.documents.map((doc) => { + const remote = remoteDataState.files.find((file) => file.path === doc.relativePath); + return computeDocumentDiff(doc, remote); + }); + }, [exportState.data, remoteDataState.files]); - if ( - !diff || - (diff.counts.added === 0 && diff.counts.removed === 0 && diff.counts.changed === 0) - ) { + const hasAnyChanges = diffs.some( + (diff) => + diff.remoteMissing || + diff.error || + diff.counts.added || + diff.counts.removed || + diff.counts.changed + ); + + if (!hasAnyChanges) { return ( }> - No changes - local and remote are identical + No changes - local and remote files are identical ); } - const addedTokens = diff.changes.filter((c) => c.type === 'added'); - const removedTokens = diff.changes.filter((c) => c.type === 'removed'); - const changedTokens = diff.changes.filter((c) => c.type === 'changed'); + return ( + + {diffs.map((diff) => ( + + ))} + + ); +}; +function DocumentDiffSection({ diff }: { diff: DocumentDiffResult }) { return ( - -
+
+ - Diff Preview + {diff.doc.label} - - - {diff.counts.added} added · {diff.counts.removed} removed · {diff.counts.changed}{' '} - changed - + {diff.doc.relativePath} -
- -
- {addedTokens.length > 0 && ( -
- - + Added ({addedTokens.length}) - -
    - {addedTokens.slice(0, 10).map((change) => ( -
  • - - + {change.path} - -
  • - ))} - {addedTokens.length > 10 && ( -
  • - - ... and {addedTokens.length - 10} more - -
  • - )} -
-
- )} + - {changedTokens.length > 0 && ( -
- - ~ Changed ({changedTokens.length}) - -
    - {changedTokens.slice(0, 10).map((change) => ( -
  • - - ~ {change.path} - -
  • - ))} - {changedTokens.length > 10 && ( -
  • - - ... and {changedTokens.length - 10} more - -
  • - )} -
-
- )} + {diff.error && ( + }> + {diff.error} + + )} + + {!diff.error && diff.remoteMissing && ( + }> + Remote file not found. Committing will create this file. + + )} - {removedTokens.length > 0 && ( -
- - - Removed ({removedTokens.length}) - -
    - {removedTokens.slice(0, 10).map((change) => ( -
  • - - - {change.path} - -
  • - ))} - {removedTokens.length > 10 && ( -
  • - - ... and {removedTokens.length - 10} more - -
  • - )} -
-
+ {!diff.error && + !diff.remoteMissing && + diff.counts.added === 0 && + diff.counts.removed === 0 && + diff.counts.changed === 0 && ( + }> + No changes detected for this document + )} -
- + + {!diff.error && (diff.counts.added || diff.counts.removed || diff.counts.changed) ? ( +
+ {diff.added.length > 0 && ( +
+ + + Added ({diff.added.length}) + +
    + {diff.added.slice(0, 10).map((path) => ( +
  • + + + {path} + +
  • + ))} + {diff.added.length > 10 && ( +
  • + + ... and {diff.added.length - 10} more + +
  • + )} +
+
+ )} + + {diff.changed.length > 0 && ( +
+ + ~ Changed ({diff.changed.length}) + +
    + {diff.changed.slice(0, 10).map((path) => ( +
  • + + ~ {path} + +
  • + ))} + {diff.changed.length > 10 && ( +
  • + + ... and {diff.changed.length - 10} more + +
  • + )} +
+
+ )} + + {diff.removed.length > 0 && ( +
+ + - Removed ({diff.removed.length}) + +
    + {diff.removed.slice(0, 10).map((path) => ( +
  • + + - {path} + +
  • + ))} + {diff.removed.length > 10 && ( +
  • + + ... and {diff.removed.length - 10} more + +
  • + )} +
+
+ )} +
+ ) : null} +
); -}; +} + +function computeDocumentDiff( + doc: ExportDocument, + remote?: { path: string; data?: unknown | null; error?: string } +): DocumentDiffResult { + if (remote?.error) { + return { + doc, + counts: { added: 0, removed: 0, changed: 0 }, + added: [], + removed: [], + changed: [], + remoteMissing: false, + error: `Failed to load remote file (${remote.error})`, + }; + } + + const remoteMissing = !remote || remote.data === null; + if (doc.format === 'dtcg') { + const localMap = flattenDtcgTokens(doc.data as Record); + const remoteMap = + !remoteMissing && isObject(remote?.data) + ? flattenDtcgTokens(remote?.data as Record) + : new Map(); + const diff = diffMaps(localMap, remoteMap); + return { + ...diff, + doc, + remoteMissing: remoteMissing && remote?.error === undefined, + error: undefined, + }; + } + + const localNative = doc.data as FigmaNativeExportDocument; + const localMap = flattenNativeVariables(localNative); + const remoteMap = + !remoteMissing && isFigmaNativeExportDocument(remote?.data) + ? flattenNativeVariables(remote?.data) + : new Map(); + const diff = diffMaps(localMap, remoteMap); + return { + ...diff, + doc, + remoteMissing: remoteMissing && remote?.error === undefined, + error: undefined, + }; +} + +function diffMaps( + localMap: Map, + remoteMap: Map +): Pick { + const added: string[] = []; + const removed: string[] = []; + const changed: string[] = []; + + localMap.forEach((value, path) => { + if (!remoteMap.has(path)) { + added.push(path); + return; + } + const remoteValue = remoteMap.get(path); + if (stableStringify(value) !== stableStringify(remoteValue)) { + changed.push(path); + } + }); + + remoteMap.forEach((_, path) => { + if (!localMap.has(path)) { + removed.push(path); + } + }); + + return { + counts: { added: added.length, removed: removed.length, changed: changed.length }, + added, + removed, + changed, + }; +} + +function flattenDtcgTokens(obj: Record, prefix = ''): Map { + const tokens = new Map(); + for (const [key, value] of Object.entries(obj)) { + if (key.startsWith('$')) continue; + const path = prefix ? `${prefix}.${key}` : key; + if (value && typeof value === 'object') { + if ('$value' in (value as Record)) { + tokens.set(path, value); + } else { + const nested = flattenDtcgTokens(value as Record, path); + nested.forEach((tokenValue, tokenPath) => tokens.set(tokenPath, tokenValue)); + } + } + } + return tokens; +} + +function flattenNativeVariables(doc: FigmaNativeExportDocument): Map { + const map = new Map(); + for (const collection of doc.collections) { + const base = collection.name; + collection.variables.forEach((variable) => { + const relativePath = variable.path || variable.name; + map.set(`${base}/${relativePath}`, variable.valueByMode); + }); + } + return map; +} + +function isFigmaNativeExportDocument(value: unknown): value is FigmaNativeExportDocument { + if (!isObject(value)) { + return false; + } + + const maybeDoc = value as Partial; + if (!Array.isArray(maybeDoc.collections)) { + return false; + } + + const countsAreNumbers = + typeof maybeDoc.collectionsCount === 'number' && typeof maybeDoc.variablesCount === 'number'; + if (!countsAreNumbers) { + return false; + } + + return maybeDoc.collections.every((collection) => + Boolean( + isObject(collection) && + typeof collection.name === 'string' && + Array.isArray(collection.variables) + ) + ); +} + +function isObject(value: unknown): value is Record { + return Boolean(value && typeof value === 'object'); +} diff --git a/src/ui/components/preview/JsonViewer.tsx b/src/ui/components/preview/JsonViewer.tsx index 786b461..076bcd5 100644 --- a/src/ui/components/preview/JsonViewer.tsx +++ b/src/ui/components/preview/JsonViewer.tsx @@ -6,16 +6,7 @@ import { h, FunctionComponent } from 'preact'; import { useCallback } from 'preact/hooks'; -import { - Stack, - Text, - Muted, - Bold, - Button, - Banner, - VerticalSpace, - IconInfoSmall24, -} from '@create-figma-plugin/ui'; +import { Stack, Text, Muted, Bold, Button, Banner, IconInfoSmall24 } from '@create-figma-plugin/ui'; import { usePlugin } from '../../context/PluginContext'; import { stableStringify } from '../../../util/stableStringify'; import styles from './JsonViewer.module.css'; @@ -23,56 +14,46 @@ import styles from './JsonViewer.module.css'; export const JsonViewer: FunctionComponent = () => { const { exportState, sendMessage } = usePlugin(); - const handleCopyJson = useCallback(() => { - if (!exportState.data) return; - - const text = stableStringify(exportState.data, 2); - - // Create a temporary textarea element for reliable clipboard copying - // This is the recommended approach for iframe environments like Figma plugins - const textarea = document.createElement('textarea'); - textarea.value = text; - textarea.style.position = 'fixed'; // Prevent scrolling to bottom - textarea.style.opacity = '0'; - textarea.style.pointerEvents = 'none'; - document.body.appendChild(textarea); - - try { - // Select the text - textarea.select(); - textarea.setSelectionRange(0, text.length); - - // Execute copy command - const successful = document.execCommand('copy'); - - if (successful) { - // Send success notification to Figma (green toast) - sendMessage({ - type: 'NOTIFY', - level: 'info', - message: 'JSON copied to clipboard', - }); - } else { - // Fallback: notify user to manually copy + const handleCopyJson = useCallback( + (text: string, fileLabel: string) => { + const textarea = document.createElement('textarea'); + textarea.value = text; + textarea.style.position = 'fixed'; + textarea.style.opacity = '0'; + textarea.style.pointerEvents = 'none'; + document.body.appendChild(textarea); + + try { + textarea.select(); + textarea.setSelectionRange(0, text.length); + + const successful = document.execCommand('copy'); + if (successful) { + sendMessage({ + type: 'NOTIFY', + level: 'info', + message: `${fileLabel} copied to clipboard`, + }); + } else { + sendMessage({ + type: 'NOTIFY', + level: 'error', + message: 'Copy failed - please select and copy manually', + }); + } + } catch (err) { + const errorMsg = err instanceof Error ? err.message : 'Unknown error'; sendMessage({ type: 'NOTIFY', level: 'error', - message: 'Copy failed - please select and copy manually', + message: `Copy failed: ${errorMsg}`, }); + } finally { + document.body.removeChild(textarea); } - } catch (err) { - // Error case: show red toast with error message - const errorMsg = err instanceof Error ? err.message : 'Unknown error'; - sendMessage({ - type: 'NOTIFY', - level: 'error', - message: `Copy failed: ${errorMsg}`, - }); - } finally { - // Clean up the temporary textarea - document.body.removeChild(textarea); - } - }, [exportState.data, sendMessage]); + }, + [sendMessage] + ); if (!exportState.data) { return ( @@ -81,51 +62,55 @@ export const JsonViewer: FunctionComponent = () => { ); } - - const jsonText = stableStringify(exportState.data, 2); - - // Extract metadata based on format - let variablesCount = 0; - let collectionsCount = 0; - - if ('meta' in exportState.data) { - // Legacy format - const meta = exportState.data.meta as { variablesCount?: number; collectionsCount?: number }; - variablesCount = meta.variablesCount || 0; - collectionsCount = meta.collectionsCount || 0; - } else if ('$extensions' in exportState.data) { - // DTCG format - const figmaExt = exportState.data.$extensions?.['com.figma'] as - | Record - | undefined; - variablesCount = (figmaExt?.variablesCount as number) || 0; - collectionsCount = (figmaExt?.collectionsCount as number) || 0; - } + const bundle = exportState.data; + const summary = bundle.summary; + const documents = bundle.documents; + const variablesCount = summary.variablesCount; + const collectionsCount = summary.collectionsCount; + const formatLabel = summary.format === 'figma-native' ? 'Figma-native' : 'DTCG'; + const exportTypeLabel = summary.exportType === 'perCollection' ? 'Per collection' : 'Single file'; return ( - - - Export Data - - - - {variablesCount} variables • {collectionsCount} collections - - - - - -
-
-          {jsonText}
-        
-
- - - - + + + + Export Summary + + + + {variablesCount} variables • {collectionsCount} collections + + + + + {formatLabel} · {exportTypeLabel} + + + + + {documents.map((doc) => { + const jsonText = stableStringify(doc.data, 2); + return ( + + + + {doc.label} + + + {doc.relativePath} + + +
+
+                {jsonText}
+              
+
+ +
+ ); + })}
); }; diff --git a/src/ui/components/settings/ExportOptionsDisclosure.tsx b/src/ui/components/settings/ExportOptionsDisclosure.tsx new file mode 100644 index 0000000..51bb35c --- /dev/null +++ b/src/ui/components/settings/ExportOptionsDisclosure.tsx @@ -0,0 +1,94 @@ +import { h, FunctionComponent } from 'preact'; +import { useState } from 'preact/hooks'; +import { Disclosure, Stack, Text, Muted, Button, Inline, Bold } from '@create-figma-plugin/ui'; +import { usePlugin } from '../../context/PluginContext'; + +export const ExportOptionsDisclosure: FunctionComponent = () => { + const { settings, updateSettings, sendMessage } = usePlugin(); + const [isOpen, setIsOpen] = useState(false); + + if (!settings) return null; + + const format = settings.exportFormat || 'dtcg'; + const exportType = settings.exportType || 'singleFile'; + + const handleFormatChange = (nextFormat: 'dtcg' | 'figma-native') => { + if (!settings || nextFormat === format) return; + const nextExportType = nextFormat === 'dtcg' ? 'singleFile' : exportType; + const nextSettings = { + ...settings, + exportFormat: nextFormat, + exportType: nextExportType, + }; + updateSettings(nextSettings); + sendMessage({ type: 'SAVE_SETTINGS', payload: nextSettings }); + }; + + const handleExportTypeChange = (nextType: 'singleFile' | 'perCollection') => { + if (!settings || nextType === exportType) return; + if (perCollectionDisabled && nextType === 'perCollection') return; + const nextSettings = { ...settings, exportType: nextType }; + updateSettings(nextSettings); + sendMessage({ type: 'SAVE_SETTINGS', payload: nextSettings }); + }; + + const perCollectionDisabled = format === 'dtcg'; + + return ( + setIsOpen(!isOpen)} open={isOpen} title="Export Options"> + + + + Format + + Select the structure for exported files. + + + + + + + + + Document Output + + Choose how many JSON files to generate. + + + + + {perCollectionDisabled && ( + + DTCG format always exports as a single file. + + )} + + + + ); +}; diff --git a/src/ui/components/settings/SettingsTab.tsx b/src/ui/components/settings/SettingsTab.tsx index 32f1c15..36d5ae0 100644 --- a/src/ui/components/settings/SettingsTab.tsx +++ b/src/ui/components/settings/SettingsTab.tsx @@ -9,6 +9,7 @@ import { h, FunctionComponent } from 'preact'; import { Container, VerticalSpace } from '@create-figma-plugin/ui'; import { RepositoryDisclosure } from './RepositoryDisclosure'; import { TokenDisclosure } from './TokenDisclosure'; +import { ExportOptionsDisclosure } from './ExportOptionsDisclosure'; import { AdvancedDisclosure } from './AdvancedDisclosure'; export const SettingsTab: FunctionComponent = () => { @@ -24,6 +25,10 @@ export const SettingsTab: FunctionComponent = () => { + + + + diff --git a/src/ui/context/PluginContext.tsx b/src/ui/context/PluginContext.tsx index 1033ecc..577111b 100644 --- a/src/ui/context/PluginContext.tsx +++ b/src/ui/context/PluginContext.tsx @@ -8,14 +8,13 @@ import { h, FunctionComponent, createContext } from 'preact'; import { useContext, useState, useEffect, useCallback } from 'preact/hooks'; import { UIToPluginMessage, PluginToUIMessage, PersistedSettings } from '../../messaging'; -import { ExportRoot } from '../../shared/types'; -import { DtcgRoot } from '../../shared/dtcg-types'; +import { ExportBundle } from '../../types/export'; export type NotificationType = 'success' | 'error' | 'warning' | 'info'; export interface ExportState { loading: boolean; - data?: ExportRoot | DtcgRoot; + data?: ExportBundle; error?: string; } @@ -27,13 +26,17 @@ export interface CommitState { error?: string; } +export interface RemoteFileState { + path: string; + data?: unknown | null; + error?: string; +} + export interface RemoteDataState { loading: boolean; - /** Data from remote file. undefined = not fetched, null = fetched but doesn't exist, object = exists */ - data?: ExportRoot | DtcgRoot | null; - error?: string; - /** Whether a fetch has been attempted (helps distinguish undefined from never-fetched) */ + files: RemoteFileState[]; fetched?: boolean; + error?: string; } interface PluginContextType { @@ -49,12 +52,12 @@ interface PluginContextType { // Commit state commitState: CommitState; - startCommit: (exportData: ExportRoot | DtcgRoot, dryRun?: boolean, commitPrefix?: string) => void; + startCommit: (exportData: ExportBundle, dryRun?: boolean, commitPrefix?: string) => void; resetCommit: () => void; // Remote data state remoteDataState: RemoteDataState; - fetchRemoteData: () => void; + fetchRemoteData: (paths: string[]) => void; } const PluginContext = createContext(undefined); @@ -90,7 +93,10 @@ export const PluginProvider: FunctionComponent = ({ childre success: false, skipped: false, }); - const [remoteDataState, setRemoteDataState] = useState({ loading: false }); + const [remoteDataState, setRemoteDataState] = useState({ + loading: false, + files: [], + }); const sendMessage = (message: UIToPluginMessage) => { parent.postMessage({ pluginMessage: message }, '*'); @@ -125,26 +131,27 @@ export const PluginProvider: FunctionComponent = ({ childre sendMessage({ type: 'REQUEST_EXPORT' }); }, []); - const startCommit = useCallback( - (exportData: ExportRoot | DtcgRoot, dryRun = false, commitPrefix = '') => { - setCommitState({ inProgress: true, success: false, skipped: false }); - sendMessage({ - type: 'COMMIT_REQUEST', - exportData, - dryRun, - commitPrefix, - }); - }, - [] - ); + const startCommit = useCallback((exportData: ExportBundle, dryRun = false, commitPrefix = '') => { + setCommitState({ inProgress: true, success: false, skipped: false }); + sendMessage({ + type: 'COMMIT_REQUEST', + exportBundle: exportData, + dryRun, + commitPrefix, + }); + }, []); const resetCommit = useCallback(() => { setCommitState({ inProgress: false, success: false, skipped: false }); }, []); - const fetchRemoteData = useCallback(() => { - setRemoteDataState({ loading: true }); - sendMessage({ type: 'FETCH_REMOTE_EXPORT' }); + const fetchRemoteData = useCallback((paths: string[]) => { + if (!paths.length) { + setRemoteDataState({ loading: false, files: [], fetched: true }); + return; + } + setRemoteDataState({ loading: true, files: [] }); + sendMessage({ type: 'FETCH_REMOTE_EXPORT', files: paths }); }, []); // Listen for messages from plugin @@ -161,6 +168,7 @@ export const PluginProvider: FunctionComponent = ({ childre if (msg.type === 'EXPORT_RESULT') { if (msg.ok) { setExportState({ loading: false, data: msg.data }); + setRemoteDataState({ loading: false, files: [], fetched: false }); } else { setExportState({ loading: false, error: msg.error }); notify('error', `Export failed: ${msg.error}`); @@ -181,7 +189,7 @@ export const PluginProvider: FunctionComponent = ({ childre notify('success', 'Successfully committed to GitHub!'); // Invalidate remote data cache after successful commit // This ensures the diff viewer will re-fetch and show correct state - setRemoteDataState({ loading: false, fetched: false }); + setRemoteDataState({ loading: false, files: [], fetched: false }); } } else { setCommitState({ @@ -196,10 +204,9 @@ export const PluginProvider: FunctionComponent = ({ childre if (msg.type === 'FETCH_REMOTE_EXPORT_RESULT') { if (msg.ok) { - setRemoteDataState({ loading: false, data: msg.data, fetched: true }); + setRemoteDataState({ loading: false, files: msg.files, fetched: true }); } else { - // Don't show notification for fetch errors - let the component handle display - setRemoteDataState({ loading: false, error: msg.error, fetched: true }); + setRemoteDataState({ loading: false, files: [], error: msg.error, fetched: true }); } } }; diff --git a/src/util/dtcgUtils.ts b/src/util/dtcgUtils.ts index dc39f06..3a9265d 100644 --- a/src/util/dtcgUtils.ts +++ b/src/util/dtcgUtils.ts @@ -6,7 +6,15 @@ import { DtcgTokenType, DtcgTokenValue, DtcgDimensionValue } from '../shared/dtcg-types'; import { convertColorToDtcg } from './colorUtils'; -import { VariableModeValue } from '../shared/types'; + +/** + * Normalized representation of a Figma variable mode value used by DTCG utilities. + */ +export interface VariableModeValue { + type: 'COLOR' | 'STRING' | 'NUMBER' | 'BOOLEAN' | 'ALIAS'; + value?: string | number | boolean | { r: number; g: number; b: number; a: number }; + refVariableId?: string; +} /** * Map of Figma variable IDs to DTCG token paths diff --git a/src/util/path.ts b/src/util/path.ts new file mode 100644 index 0000000..a547fab --- /dev/null +++ b/src/util/path.ts @@ -0,0 +1,9 @@ +export function normalizeFolder(folder?: string): string { + if (!folder) return ''; + return folder.replace(/\\+/g, '/').replace(/^\/+/, '').replace(/\/+$/, '').trim(); +} + +export function buildRepoPath(folder: string | undefined, filename: string): string { + const normalizedFolder = normalizeFolder(folder); + return normalizedFolder ? `${normalizedFolder}/${filename}` : filename; +} diff --git a/src/util/slugify.ts b/src/util/slugify.ts new file mode 100644 index 0000000..c296b47 --- /dev/null +++ b/src/util/slugify.ts @@ -0,0 +1,11 @@ +export function slugify(value: string, fallback: string): string { + const base = value + .trim() + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-'), + cleaned = base.replace(/^-+|-+$/g, ''); + if (cleaned) { + return cleaned; + } + return fallback; +} diff --git a/tests/dtcgUtils.test.ts b/tests/dtcgUtils.test.ts index 12f0197..8efbe11 100644 --- a/tests/dtcgUtils.test.ts +++ b/tests/dtcgUtils.test.ts @@ -17,7 +17,7 @@ import { convertValue, type VariablePathMap, } from '../src/util/dtcgUtils'; -import type { VariableModeValue } from '../src/shared/types'; +import type { VariableModeValue } from '../src/util/dtcgUtils'; describe('shouldBeDimension', () => { it('returns true for FLOAT type with "width" in name', () => { diff --git a/tests/figmaNativeExport.test.ts b/tests/figmaNativeExport.test.ts new file mode 100644 index 0000000..7316ea5 --- /dev/null +++ b/tests/figmaNativeExport.test.ts @@ -0,0 +1,132 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { buildFigmaNativeExport } from '../src/export/buildFigmaNativeExport'; +import type { ExportBundle } from '../src/types/export'; +import type { FigmaNativeExportDocument } from '../src/shared/figma-native-types'; + +interface MockFigmaApi { + root: { name: string }; + variables: { + getLocalVariableCollectionsAsync: ReturnType; + getLocalVariablesAsync: ReturnType; + getVariableByIdAsync: ReturnType; + }; +} + +const createMockFigma = (): MockFigmaApi => { + const collections = [ + { + id: 'collection-1', + name: 'Global Colors', + modes: [ + { modeId: 'mode-light', name: 'Light' }, + { modeId: 'mode-dark', name: 'Dark' }, + ], + }, + { + id: 'collection-2', + name: 'Spacing', + modes: [{ modeId: 'mode-default', name: 'Default' }], + }, + ]; + + const variables = [ + { + id: 'var-color', + name: 'Brand / Primary', + variableCollectionId: 'collection-1', + resolvedType: 'COLOR', + valuesByMode: { + 'mode-light': { r: 1, g: 0, b: 0, a: 1 }, + 'mode-dark': { r: 0.2, g: 0, b: 0, a: 1 }, + }, + description: 'Primary brand color', + scopes: ['FILL'], + codeSyntax: { WEB: 'brand-primary' }, + hiddenFromPublishing: false, + }, + { + id: 'var-spacing', + name: 'Base', + variableCollectionId: 'collection-2', + resolvedType: 'FLOAT', + valuesByMode: { + 'mode-default': 8, + }, + description: 'Base spacing unit', + scopes: ['ALL_SCOPES'], + codeSyntax: {}, + hiddenFromPublishing: false, + }, + ]; + + return { + root: { name: 'Mock File' }, + variables: { + getLocalVariableCollectionsAsync: vi.fn().mockResolvedValue(collections), + getLocalVariablesAsync: vi.fn().mockResolvedValue(variables), + getVariableByIdAsync: vi.fn(), + }, + }; +}; + +describe('buildFigmaNativeExport', () => { + let mockFigma: MockFigmaApi; + + beforeEach(() => { + mockFigma = createMockFigma(); + (global as unknown as { figma: MockFigmaApi }).figma = mockFigma; + }); + + it('builds a single-file export bundle', async () => { + const bundle = (await buildFigmaNativeExport(mockFigma as unknown as PluginAPI, { + exportType: 'singleFile', + folder: 'tokens', + singleFileName: 'variables.json', + })) as ExportBundle; + + expect(bundle.summary.format).toBe('figma-native'); + expect(bundle.summary.exportType).toBe('singleFile'); + expect(bundle.documents).toHaveLength(1); + expect(bundle.documents[0].relativePath).toBe('tokens/variables.json'); + expect(bundle.documents[0].collectionsCount).toBe(2); + expect(bundle.documents[0].variablesCount).toBe(2); + + const document = bundle.documents[0].data as FigmaNativeExportDocument; + expect(document).toHaveProperty('collections'); + const firstCollection = document.collections[0]; + expect(firstCollection.variables[0].name).toBe('Primary'); + expect(firstCollection.variables[0].path).toBe('Brand/Primary'); + expect(firstCollection.variables[0].valueByMode['mode-light'].value).toBe('#ff0000'); + + const spacingCollection = document.collections.find((col) => col.id === 'collection-2'); + expect(spacingCollection?.variables[0].name).toBe('Base'); + expect(spacingCollection?.variables[0].path).toBe('Base'); + }); + + it('splits documents per collection when requested', async () => { + const bundle = (await buildFigmaNativeExport(mockFigma as unknown as PluginAPI, { + exportType: 'perCollection', + folder: '', + singleFileName: 'ignored.json', + })) as ExportBundle; + + expect(bundle.summary.exportType).toBe('perCollection'); + expect(bundle.documents).toHaveLength(2); + const paths = bundle.documents.map((doc) => doc.relativePath); + expect(paths.some((path) => path.endsWith('global-colors.json'))).toBe(true); + expect(paths.some((path) => path.endsWith('spacing.json'))).toBe(true); + }); + + it('includes metadata and hashes on each document', async () => { + const bundle = (await buildFigmaNativeExport(mockFigma as unknown as PluginAPI, { + exportType: 'singleFile', + folder: '', + singleFileName: 'variables.json', + })) as ExportBundle; + + const document = bundle.documents[0].data as FigmaNativeExportDocument; + expect(document.exportFormat).toBe('figma-native'); + expect(typeof document.contentHash).toBe('string'); + expect(document.contentHash).toHaveLength(64); + }); +});