diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..515ef54
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,2 @@
+github: dev-five-git
+patreon: JeongMinOh
diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
new file mode 100644
index 0000000..07b2e18
--- /dev/null
+++ b/.github/workflows/CI.yml
@@ -0,0 +1,40 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - main
+ paths-ignore:
+ - '**/*.md'
+ - LICENSE
+ - '**/*.gitignore'
+ - .editorconfig
+ pull_request:
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ test:
+ name: Test
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+ - uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: latest
+ - name: Install dependencies
+ run: bun install
+ - name: Lint
+ run: bun run lint
+ - name: Test
+ run: bun test
+ - name: Upload to codecov.io
+ uses: codecov/codecov-action@v5
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ fail_ci_if_error: true
+ files: coverage/lcov.info
+ if: github.ref_name == 'main'
\ No newline at end of file
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 0000000..692f7c8
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,4 @@
+#!/usr/bin/env sh
+
+bun run lint
+bun run test
diff --git a/README.md b/README.md
index 9d195aa..0ec66c1 100644
--- a/README.md
+++ b/README.md
@@ -1,40 +1,186 @@
-Below are the steps to get your plugin running. You can also find instructions at:
+# Devup Figma Plugin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-https://www.figma.com/plugin-docs/plugin-quickstart-guide/
+A powerful Figma plugin that generates React/TypeScript code from Figma designs and manages Devup design system configurations. This plugin enables seamless conversion of Figma components to production-ready React code and facilitates design system synchronization.
-This plugin template uses Typescript and NPM, two standard tools in creating JavaScript applications.
+## Features
-First, download Node.js which comes with NPM. This will allow you to install TypeScript and other
-libraries. You can find the download link here:
+### π¨ Code Generation
+- **React Component Generation**: Automatically converts Figma designs to React/TypeScript components using the Devup-UI format
+- **Codegen Support**: Works with Figma's Dev Mode codegen feature for real-time code preview
+- **Component Extraction**: Extracts and generates code for nested components
+- **CLI Export**: Generates bash commands for easy file creation
-https://nodejs.org/en/download/
+### π¦ Design System Management
+- **Export Devup Config**: Export your design system (colors, typography, components) in JSON or Excel format
+- **Import Devup Config**: Import design system configurations back into Figma
+- **Treeshaking Support**: Optimize exports by removing unused design tokens
+- **Variable Support**: Handles Figma variables and color collections with multiple modes
-Next, install TypeScript using the command:
+### π Component & Asset Export
+- **Component Export**: Export selected components as a ZIP file containing individual component files
+- **Asset Export**: Export design assets (currently in development)
-npm install -g typescript
+## Installation
-Finally, in the directory of your plugin, get the latest type definitions for the plugin API by running:
+### Prerequisites
+- [Bun](https://bun.sh/) (recommended) or Node.js
+- Figma Desktop App (for plugin development)
-npm install --save-dev @figma/plugin-typings
+### Setup
-If you are familiar with JavaScript, TypeScript will look very familiar. In fact, valid JavaScript code
-is already valid Typescript code.
+1. Clone the repository:
+```bash
+git clone
+cd devup-figma-plugin
+```
-TypeScript adds type annotations to variables. This allows code editors such as Visual Studio Code
-to provide information about the Figma API while you are writing code, as well as help catch bugs
-you previously didn't notice.
+2. Install dependencies:
+```bash
+bun install
+```
-For more information, visit https://www.typescriptlang.org/
+3. Build the plugin:
+```bash
+bun run build
+```
-Using TypeScript requires a compiler to convert TypeScript (code.ts) into JavaScript (code.js)
-for the browser to run.
+4. Load the plugin in Figma:
+ - Open Figma Desktop
+ - Go to `Plugins` β `Development` β `Import plugin from manifest...`
+ - Select the `manifest.json` file from this project
-We recommend writing TypeScript code using Visual Studio code:
+## Development
-1. Download Visual Studio Code if you haven't already: https://code.visualstudio.com/.
-2. Open this directory in Visual Studio Code.
-3. Compile TypeScript to JavaScript: Run the "Terminal > Run Build Task..." menu item,
- then select "npm: watch". You will have to do this again every time
- you reopen Visual Studio Code.
+### Available Scripts
-That's it! Visual Studio Code will regenerate the JavaScript file every time you save.
+- `bun run dev` - Start development server with hot reload
+- `bun run build` - Build the plugin for production
+- `bun run watch` - Build the plugin in watch mode
+- `bun run test` - Run tests with coverage
+- `bun run lint` - Check code for linting errors
+- `bun run lint:fix` - Fix linting errors automatically
+
+### Project Structure
+
+```
+src/
+βββ code.ts # Main plugin entry point
+βββ codegen/ # Code generation logic
+β βββ Codegen.ts # Main codegen class
+β βββ props/ # Property generators (layout, colors, etc.)
+β βββ render/ # Component rendering logic
+β βββ utils/ # Codegen utilities
+βββ commands/ # Plugin commands
+β βββ devup/ # Devup export/import functionality
+β βββ exportAssets.ts # Asset export command
+β βββ exportComponents.ts # Component export command
+βββ utils/ # Shared utilities
+```
+
+## Usage
+
+### Code Generation (Dev Mode)
+
+1. Open Figma in Dev Mode
+2. Select a design element (frame, component, etc.)
+3. The plugin will automatically generate React/TypeScript code in the code panel
+4. You can copy the generated code or use the provided CLI commands
+
+### Export Devup Configuration
+
+1. Select elements in your Figma file
+2. Go to `Plugins` β `Devup` β `Export Devup` (or `Export Devup Excel`)
+3. Choose whether to use treeshaking (removes unused tokens)
+4. The configuration file will be downloaded
+
+### Import Devup Configuration
+
+1. Go to `Plugins` β `Devup` β `Import Devup` (or `Import Devup Excel`)
+2. Select your Devup configuration file
+3. The design system will be imported into Figma
+
+### Export Components
+
+1. Select the components you want to export
+2. Go to `Plugins` β `Devup` β `Export Components`
+3. A ZIP file containing all component files will be downloaded
+
+## Technical Details
+
+### Code Generation
+
+The plugin converts Figma nodes to React components by:
+- Analyzing layout properties (auto-layout, padding, spacing)
+- Converting styles (colors, typography, effects)
+- Handling component variants and instances
+- Generating proper TypeScript types
+- Optimizing CSS properties
+
+### Supported Figma Features
+
+- β
Auto Layout
+- β
Components & Variants
+- β
Text Styles & Typography
+- β
Color Variables & Collections
+- β
Effects (shadows, blurs)
+- β
Borders & Strokes
+- β
Grid Layouts
+- β
Transform properties
+
+### Build Configuration
+
+- **Bundler**: Rspack
+- **Language**: TypeScript
+- **Linter**: Biome
+- **Test Runner**: Bun
+- **Package Manager**: Bun
+
+## Testing
+
+Run tests with coverage:
+```bash
+bun run test
+```
+
+Test coverage reports are generated in the `coverage/` directory.
+
+## Contributing
+
+1. Follow the existing code style (enforced by Biome)
+2. Write tests for new features
+3. Ensure all tests pass and linting checks succeed
+4. Update documentation as needed
+
+## License
+
+[Add your license information here]
+
+## Support
+
+For issues, questions, or contributions, please [open an issue](link-to-issues) or contact the maintainers.
diff --git a/biome.json b/biome.json
new file mode 100644
index 0000000..b68f99a
--- /dev/null
+++ b/biome.json
@@ -0,0 +1,25 @@
+{
+ "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
+ "vcs": {
+ "enabled": true,
+ "clientKind": "git",
+ "useIgnoreFile": true
+ },
+ "files": {
+ "includes": ["**", "!**/dist"]
+ },
+ "javascript": {
+ "formatter": {
+ "semicolons": "asNeeded",
+ "quoteStyle": "single",
+ "indentStyle": "space",
+ "indentWidth": 2
+ }
+ },
+ "json": {
+ "formatter": {
+ "indentStyle": "space",
+ "indentWidth": 2
+ }
+ }
+}
diff --git a/bun.lock b/bun.lock
index 357a99c..52d0e48 100644
--- a/bun.lock
+++ b/bun.lock
@@ -5,58 +5,37 @@
"": {
"name": "devup-figma-plugin",
"dependencies": {
- "jszip": "^3.10.1",
+ "jszip": "^3.10",
},
"devDependencies": {
- "@figma/plugin-typings": "^1.121.0",
- "@rspack/cli": "^1.6.6",
- "@rspack/core": "^1.6.6",
- "@typescript-eslint/eslint-plugin": "^8.49.0",
- "@typescript-eslint/parser": "^8.49.0",
- "@vitest/coverage-v8": "^4.0.15",
- "eslint": "^9.39.1",
- "eslint-plugin-devup": "^2.0.11",
- "typescript": "^5.9.3",
- "vitest": "^4.0.15",
+ "@biomejs/biome": "^2.3",
+ "@figma/plugin-typings": "^1.121",
+ "@rspack/cli": "^1.6.7",
+ "@rspack/core": "^1.6.7",
+ "@types/bun": "^1.3",
+ "husky": "^9.1",
+ "typescript": "^5.9",
},
},
},
"packages": {
- "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
+ "@biomejs/biome": ["@biomejs/biome@2.3.8", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.8", "@biomejs/cli-darwin-x64": "2.3.8", "@biomejs/cli-linux-arm64": "2.3.8", "@biomejs/cli-linux-arm64-musl": "2.3.8", "@biomejs/cli-linux-x64": "2.3.8", "@biomejs/cli-linux-x64-musl": "2.3.8", "@biomejs/cli-win32-arm64": "2.3.8", "@biomejs/cli-win32-x64": "2.3.8" }, "bin": { "biome": "bin/biome" } }, "sha512-Qjsgoe6FEBxWAUzwFGFrB+1+M8y/y5kwmg5CHac+GSVOdmOIqsAiXM5QMVGZJ1eCUCLlPZtq4aFAQ0eawEUuUA=="],
- "@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="],
+ "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HM4Zg9CGQ3txTPflxD19n8MFPrmUAjaC7PQdLkugeeC0cQ+PiVrd7i09gaBS/11QKsTDBJhVg85CEIK9f50Qww=="],
- "@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="],
+ "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-lUDQ03D7y/qEao7RgdjWVGCu+BLYadhKTm40HkpJIi6kn8LSv5PAwRlew/DmwP4YZ9ke9XXoTIQDO1vAnbRZlA=="],
- "@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
+ "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-Uo1OJnIkJgSgF+USx970fsM/drtPcQ39I+JO+Fjsaa9ZdCN1oysQmy6oAGbyESlouz+rzEckLTF6DS7cWse95g=="],
- "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="],
+ "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-PShR4mM0sjksUMyxbyPNMxoKFPVF48fU8Qe8Sfx6w6F42verbwRLbz+QiKNiDPRJwUoMG1nPM50OBL3aOnTevA=="],
- "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
+ "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.8", "", { "os": "linux", "cpu": "x64" }, "sha512-QDPMD5bQz6qOVb3kiBui0zKZXASLo0NIQ9JVJio5RveBEFgDgsvJFUvZIbMbUZT3T00M/1wdzwWXk4GIh0KaAw=="],
- "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
+ "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.8", "", { "os": "linux", "cpu": "x64" }, "sha512-YGLkqU91r1276uwSjiUD/xaVikdxgV1QpsicT0bIA1TaieM6E5ibMZeSyjQ/izBn4tKQthUSsVZacmoJfa3pDA=="],
- "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="],
+ "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-H4IoCHvL1fXKDrTALeTKMiE7GGWFAraDwBYFquE/L/5r1927Te0mYIGseXi4F+lrrwhSWbSGt5qPFswNoBaCxg=="],
- "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
-
- "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
-
- "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
-
- "@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="],
-
- "@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
-
- "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
-
- "@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
-
- "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
-
- "@bcoe/v8-coverage": ["@bcoe/v8-coverage@1.0.2", "", {}, "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA=="],
-
- "@devup-ui/eslint-plugin": ["@devup-ui/eslint-plugin@1.0.5", "", { "dependencies": { "@typescript-eslint/utils": "^8.48", "typescript-eslint": "^8.48" } }, "sha512-0Rf0uQOHVIkzdCi2W9jW4bSab81DSmcPc6lAr/SoBTt9Fbnv3up2qRprwjsE3utPKQiIypylIBhx5czxUm+RZg=="],
+ "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.8", "", { "os": "win32", "cpu": "x64" }, "sha512-RguzimPoZWtBapfKhKjcWXBVI91tiSprqdBYu7tWhgN8pKRZhw24rFeNZTNf6UiBfjCYCi9eFQs/JzJZIhuK4w=="],
"@discoveryjs/json-ext": ["@discoveryjs/json-ext@0.5.7", "", {}, "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw=="],
@@ -66,98 +45,8 @@
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
- "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
-
- "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
-
- "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
-
- "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
-
- "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
-
- "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
-
- "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
-
- "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
-
- "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
-
- "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
-
- "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
-
- "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
-
- "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
-
- "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
-
- "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
-
- "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
-
- "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
-
- "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
-
- "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
-
- "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
-
- "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
-
- "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
-
- "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
-
- "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
-
- "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
-
- "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
-
- "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="],
-
- "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
-
- "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="],
-
- "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
-
- "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
-
- "@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="],
-
- "@eslint/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="],
-
- "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
-
- "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
-
"@figma/plugin-typings": ["@figma/plugin-typings@1.121.0", "", {}, "sha512-590qfxc5QtGs/AqdAEegNZUWzwEuaA9whl6T8LxscBaGjmqU3Z5jJHgHfYXissy1S5IBsXEZFmudKibpLcxGdQ=="],
- "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
-
- "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="],
-
- "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
-
- "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
-
- "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
-
- "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
-
- "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
-
- "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
-
- "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
-
- "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
-
"@jsonjoy.com/base64": ["@jsonjoy.com/base64@1.1.2", "", { "peerDependencies": { "tslib": "2" } }, "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA=="],
"@jsonjoy.com/buffers": ["@jsonjoy.com/buffers@1.2.1", "", { "peerDependencies": { "tslib": "2" } }, "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA=="],
@@ -186,145 +75,63 @@
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" } }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="],
- "@npmcli/config": ["@npmcli/config@8.3.4", "", { "dependencies": { "@npmcli/map-workspaces": "^3.0.2", "@npmcli/package-json": "^5.1.1", "ci-info": "^4.0.0", "ini": "^4.1.2", "nopt": "^7.2.1", "proc-log": "^4.2.0", "semver": "^7.3.5", "walk-up-path": "^3.0.1" } }, "sha512-01rtHedemDNhUXdicU7s+QYz/3JyV5Naj84cvdXGH4mgCdL+agmSYaLF4LUG4vMCLzhBO8YtS0gPpH1FGvbgAw=="],
-
- "@npmcli/git": ["@npmcli/git@5.0.8", "", { "dependencies": { "@npmcli/promise-spawn": "^7.0.0", "ini": "^4.1.3", "lru-cache": "^10.0.1", "npm-pick-manifest": "^9.0.0", "proc-log": "^4.0.0", "promise-inflight": "^1.0.1", "promise-retry": "^2.0.1", "semver": "^7.3.5", "which": "^4.0.0" } }, "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ=="],
-
- "@npmcli/map-workspaces": ["@npmcli/map-workspaces@3.0.6", "", { "dependencies": { "@npmcli/name-from-folder": "^2.0.0", "glob": "^10.2.2", "minimatch": "^9.0.0", "read-package-json-fast": "^3.0.0" } }, "sha512-tkYs0OYnzQm6iIRdfy+LcLBjcKuQCeE5YLb8KnrIlutJfheNaPvPpgoFEyEFgbjzl5PLZ3IA/BWAwRU0eHuQDA=="],
-
- "@npmcli/name-from-folder": ["@npmcli/name-from-folder@2.0.0", "", {}, "sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg=="],
-
- "@npmcli/package-json": ["@npmcli/package-json@5.2.1", "", { "dependencies": { "@npmcli/git": "^5.0.0", "glob": "^10.2.2", "hosted-git-info": "^7.0.0", "json-parse-even-better-errors": "^3.0.0", "normalize-package-data": "^6.0.0", "proc-log": "^4.0.0", "semver": "^7.5.3" } }, "sha512-f7zYC6kQautXHvNbLEWgD/uGu1+xCn9izgqBfgItWSx22U0ZDekxN08A1vM8cTxj/cRVe0Q94Ode+tdoYmIOOQ=="],
-
- "@npmcli/promise-spawn": ["@npmcli/promise-spawn@7.0.2", "", { "dependencies": { "which": "^4.0.0" } }, "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ=="],
-
- "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
-
- "@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="],
-
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
- "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.53.3", "", { "os": "android", "cpu": "arm" }, "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w=="],
-
- "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.53.3", "", { "os": "android", "cpu": "arm64" }, "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w=="],
-
- "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.53.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA=="],
+ "@rspack/binding": ["@rspack/binding@1.6.7", "", { "optionalDependencies": { "@rspack/binding-darwin-arm64": "1.6.7", "@rspack/binding-darwin-x64": "1.6.7", "@rspack/binding-linux-arm64-gnu": "1.6.7", "@rspack/binding-linux-arm64-musl": "1.6.7", "@rspack/binding-linux-x64-gnu": "1.6.7", "@rspack/binding-linux-x64-musl": "1.6.7", "@rspack/binding-wasm32-wasi": "1.6.7", "@rspack/binding-win32-arm64-msvc": "1.6.7", "@rspack/binding-win32-ia32-msvc": "1.6.7", "@rspack/binding-win32-x64-msvc": "1.6.7" } }, "sha512-7ICabuBN3gHc6PPN52+m1kruz3ogiJjg1C0gSWdLRk18m/4jlcM2aAy6wfXjgODJdB0Yh2ro/lIpBbj+AYWUGA=="],
- "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.53.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ=="],
+ "@rspack/binding-darwin-arm64": ["@rspack/binding-darwin-arm64@1.6.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QiIAP8JTAtht0j8/xZZEQTJRB9e+KrOm9c7JJm73CewVg55rDWRrwopiVfBNlTu1coem1ztUHJYdQhg2uXfqww=="],
- "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.53.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w=="],
+ "@rspack/binding-darwin-x64": ["@rspack/binding-darwin-x64@1.6.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-DpQRxxTXkMMNPmBXeJBaAB8HmWKxH2IfvHv7vU+kBhJ3xdPtXU4/xBv1W3biluoNRG11gc1WLIgjzeGgaLCxmw=="],
- "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.53.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q=="],
+ "@rspack/binding-linux-arm64-gnu": ["@rspack/binding-linux-arm64-gnu@1.6.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-211/XoBiooGGgUo/NxNpsrzGUXtH1d7g/4+UTtjYtfc8QHwu7ZMHcsqg0wss53fXzn/yyxd0DZ56vBHq52BiFw=="],
- "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.53.3", "", { "os": "linux", "cpu": "arm" }, "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw=="],
+ "@rspack/binding-linux-arm64-musl": ["@rspack/binding-linux-arm64-musl@1.6.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-0WnqAWz3WPDsXGvOOA++or7cHpoidVsH3FlqNaAfRu6ni6n7ig/s0/jKUB+C5FtXOgmGjAGkZHfFgNHsvZ0FWw=="],
- "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.53.3", "", { "os": "linux", "cpu": "arm" }, "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg=="],
+ "@rspack/binding-linux-x64-gnu": ["@rspack/binding-linux-x64-gnu@1.6.7", "", { "os": "linux", "cpu": "x64" }, "sha512-iMrE0Q4IuYpkE0MjpaOVaUDYbQFiCRI9D3EPoXzlXJj4kJSdNheODpHTBVRlWt8Xp7UAoWuIFXCvKFKcSMm3aQ=="],
- "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.53.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w=="],
+ "@rspack/binding-linux-x64-musl": ["@rspack/binding-linux-x64-musl@1.6.7", "", { "os": "linux", "cpu": "x64" }, "sha512-e7gKFxpdEQwYGk7lTC/hukTgNtaoAstBXehnZNk4k3kuU6+86WDrkn18Cd949iNqfIPtIG/wIsFNGbkHsH69hQ=="],
- "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.53.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A=="],
+ "@rspack/binding-wasm32-wasi": ["@rspack/binding-wasm32-wasi@1.6.7", "", { "dependencies": { "@napi-rs/wasm-runtime": "1.0.7" }, "cpu": "none" }, "sha512-yx88EFdE9RP3hh7VhjjW6uc6wGU0KcpOcZp8T8E/a+X8L98fX0aVrtM1IDbndhmdluIMqGbfJNap2+QqOCY9Mw=="],
- "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g=="],
+ "@rspack/binding-win32-arm64-msvc": ["@rspack/binding-win32-arm64-msvc@1.6.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-vgxVYpFK8P5ulSXQQA+EbX78R/SUU+WIf0JIY+LoUoP89gZOsise/lKAJMAybzpeTJ1t0ndLchFznDYnzq+l4Q=="],
- "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.53.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw=="],
+ "@rspack/binding-win32-ia32-msvc": ["@rspack/binding-win32-ia32-msvc@1.6.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-bV5RTW0Va0UQKJm9HWLt7fWNBPaBBBxCJOA2pJT3nGGm6CCXKnZSyEiVbFUk4jI/uiwBfqenlLkzaGoMRbeDhA=="],
- "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g=="],
+ "@rspack/binding-win32-x64-msvc": ["@rspack/binding-win32-x64-msvc@1.6.7", "", { "os": "win32", "cpu": "x64" }, "sha512-8xlbuJQtYktlBjZupOHlO8FeZqSIhsV3ih7xBSiOYar6LI6uQzA7XiO3I5kaPSDirBMMMKv1Z4rKCxWx10a3TQ=="],
- "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.53.3", "", { "os": "linux", "cpu": "none" }, "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A=="],
+ "@rspack/cli": ["@rspack/cli@1.6.7", "", { "dependencies": { "@discoveryjs/json-ext": "^0.5.7", "@rspack/dev-server": "~1.1.4", "exit-hook": "^4.0.0", "webpack-bundle-analyzer": "4.10.2" }, "peerDependencies": { "@rspack/core": "^1.0.0-alpha || ^1.x" }, "bin": { "rspack": "bin/rspack.js" } }, "sha512-lWU4Gfw3HIysXnBuN1Ta43BeQHewoHd3MAzRlxmEYsUPxau26h6oTpL5WnLsT/Eh4G7XqnW1NJ8COujnn9EGfg=="],
- "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.53.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg=="],
-
- "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.53.3", "", { "os": "linux", "cpu": "x64" }, "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w=="],
-
- "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.53.3", "", { "os": "linux", "cpu": "x64" }, "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q=="],
-
- "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.53.3", "", { "os": "none", "cpu": "arm64" }, "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw=="],
-
- "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.53.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw=="],
-
- "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.53.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA=="],
-
- "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.53.3", "", { "os": "win32", "cpu": "x64" }, "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg=="],
-
- "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.53.3", "", { "os": "win32", "cpu": "x64" }, "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ=="],
-
- "@rspack/binding": ["@rspack/binding@1.6.6", "", { "optionalDependencies": { "@rspack/binding-darwin-arm64": "1.6.6", "@rspack/binding-darwin-x64": "1.6.6", "@rspack/binding-linux-arm64-gnu": "1.6.6", "@rspack/binding-linux-arm64-musl": "1.6.6", "@rspack/binding-linux-x64-gnu": "1.6.6", "@rspack/binding-linux-x64-musl": "1.6.6", "@rspack/binding-wasm32-wasi": "1.6.6", "@rspack/binding-win32-arm64-msvc": "1.6.6", "@rspack/binding-win32-ia32-msvc": "1.6.6", "@rspack/binding-win32-x64-msvc": "1.6.6" } }, "sha512-noiV+qhyBTVpvG2M4bnOwKk2Ynl6G47Wf7wpCjPCFr87qr3txNwTTnhkEJEU59yj+VvIhbRD2rf5+9TLoT0Wxg=="],
-
- "@rspack/binding-darwin-arm64": ["@rspack/binding-darwin-arm64@1.6.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vGVDP0rlWa2w/gLba/sncVfkCah0HmhdmK5vGj/7sSX0iViwQneA2xjxDHyCNSQrvfq9GJmj4Kmdq/9tGh0KuA=="],
-
- "@rspack/binding-darwin-x64": ["@rspack/binding-darwin-x64@1.6.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-IcdEG2kOmbPPO70Zl7gDnowDjK7d7C1hWew2vU7dPltr2t1JalRIMnS051lhiur0ULkSxV3cW1zXqv0Oi8AnOg=="],
-
- "@rspack/binding-linux-arm64-gnu": ["@rspack/binding-linux-arm64-gnu@1.6.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-rIguCCtlTcwoFlwheDiUgdImk27spuCRn43zGJogARpM/ZYRFKIuSwFDGUtJT2g0TSLUAHUhWAUqC36NwvrbMQ=="],
-
- "@rspack/binding-linux-arm64-musl": ["@rspack/binding-linux-arm64-musl@1.6.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-x6X6Gr0fUw6qrJGxZt3Rb6oIX+jd9pdcyp0VbtofcLaqGVQbzustYsYnuLATPOys0q4J/4kWnmEhkjLJHwkhpQ=="],
-
- "@rspack/binding-linux-x64-gnu": ["@rspack/binding-linux-x64-gnu@1.6.6", "", { "os": "linux", "cpu": "x64" }, "sha512-gSlVdASszWHosQKn+nzYOInBijdQboUnmNMGgW9/PijVg3433IvQjzviUuJFno8CMGgrACV9yw+ZFDuK0J57VA=="],
-
- "@rspack/binding-linux-x64-musl": ["@rspack/binding-linux-x64-musl@1.6.6", "", { "os": "linux", "cpu": "x64" }, "sha512-TZaqVkh7memsTK/hxkOBrbpdzbmBUMea1YnYt++7QjMgco1kWFvAQ+YhAWtIaOaEg8s6C07Lt0Zp8izM2Dja0g=="],
-
- "@rspack/binding-wasm32-wasi": ["@rspack/binding-wasm32-wasi@1.6.6", "", { "dependencies": { "@napi-rs/wasm-runtime": "1.0.7" }, "cpu": "none" }, "sha512-W4mWdlLnYrbUaktyHOGNfATblxMTbgF7CBfDw8PhbDtjd2l8e/TnaHgIDkwITHXAOMEF/QEKfo9FtusbcQJNKw=="],
-
- "@rspack/binding-win32-arm64-msvc": ["@rspack/binding-win32-arm64-msvc@1.6.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-cw5OgxqoDwjoZlk0L3vGEwcjPZsOVFYLwr2ssiC05rsTbhBwxj8coLpAJdvUvbf6C2TTmCB7iPe2sPq1KWD37g=="],
-
- "@rspack/binding-win32-ia32-msvc": ["@rspack/binding-win32-ia32-msvc@1.6.6", "", { "os": "win32", "cpu": "ia32" }, "sha512-M4ruR+VZ59iy+mPjy6FQPT27cOgeytf3wFBrt7e0suKeNLYGxrNyI9YhgpCTY++SMJsAMgRLGDHoI3ZgWulw1Q=="],
-
- "@rspack/binding-win32-x64-msvc": ["@rspack/binding-win32-x64-msvc@1.6.6", "", { "os": "win32", "cpu": "x64" }, "sha512-q5QTvdhPUh+CA93cQG5zWKRIHMIWPzw+ftFDEwBw52zYdvNAoLniqD8o5Mi8CT0pndhulXgR5aw0Sjd3eMah+A=="],
-
- "@rspack/cli": ["@rspack/cli@1.6.6", "", { "dependencies": { "@discoveryjs/json-ext": "^0.5.7", "@rspack/dev-server": "~1.1.4", "exit-hook": "^4.0.0", "webpack-bundle-analyzer": "4.10.2" }, "peerDependencies": { "@rspack/core": "^1.0.0-alpha || ^1.x" }, "bin": { "rspack": "bin/rspack.js" } }, "sha512-CzKPQ00Ym1jEzU1i7Wem2JWbNGX4hnndnBi5f3FMM8gEUUD+SQQVm8fYeMlFIMbHIpYan+x+nxcnjxT5hiQ4kA=="],
-
- "@rspack/core": ["@rspack/core@1.6.6", "", { "dependencies": { "@module-federation/runtime-tools": "0.21.6", "@rspack/binding": "1.6.6", "@rspack/lite-tapable": "1.1.0" }, "peerDependencies": { "@swc/helpers": ">=0.5.1" }, "optionalPeers": ["@swc/helpers"] }, "sha512-2mR+2YBydlgZ7Q0Rpd6bCC3MBnV9TS0x857K0zIhbDj4BQOqaWVy1n7fx/B3MrS8TR0QCuzKfyDAjNz+XTyJVQ=="],
+ "@rspack/core": ["@rspack/core@1.6.7", "", { "dependencies": { "@module-federation/runtime-tools": "0.21.6", "@rspack/binding": "1.6.7", "@rspack/lite-tapable": "1.1.0" }, "peerDependencies": { "@swc/helpers": ">=0.5.1" }, "optionalPeers": ["@swc/helpers"] }, "sha512-tkd4nSzTf+pDa9OAE4INi/JEa93HNszjWy5C9+trf4ZCXLLHsHxHQFbzoreuz4Vv2PlCWajgvAdiPMV1vGIkuw=="],
"@rspack/dev-server": ["@rspack/dev-server@1.1.4", "", { "dependencies": { "chokidar": "^3.6.0", "http-proxy-middleware": "^2.0.9", "p-retry": "^6.2.0", "webpack-dev-server": "5.2.2", "ws": "^8.18.0" }, "peerDependencies": { "@rspack/core": "*" } }, "sha512-kGHYX2jYf3ZiHwVl0aUEPBOBEIG1aWleCDCAi+Jg32KUu3qr/zDUpCEd0wPuHfLEgk0X0xAEYCS6JMO7nBStNQ=="],
"@rspack/lite-tapable": ["@rspack/lite-tapable@1.1.0", "", {}, "sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw=="],
- "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
-
- "@tanstack/eslint-plugin-query": ["@tanstack/eslint-plugin-query@5.91.2", "", { "dependencies": { "@typescript-eslint/utils": "^8.44.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" } }, "sha512-UPeWKl/Acu1IuuHJlsN+eITUHqAaa9/04geHHPedY8siVarSaWprY0SVMKrkpKfk5ehRT7+/MZ5QwWuEtkWrFw=="],
-
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
"@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="],
"@types/bonjour": ["@types/bonjour@3.5.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ=="],
- "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
-
- "@types/concat-stream": ["@types/concat-stream@2.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-3qe4oQAPNwVNwK4C9c8u+VJqv9kez+2MR4qJpoPFfXtgxxif1QbFusvXzK0/Wra2VX07smostI2VMmJNSpZjuQ=="],
+ "@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
"@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
"@types/connect-history-api-fallback": ["@types/connect-history-api-fallback@1.5.4", "", { "dependencies": { "@types/express-serve-static-core": "*", "@types/node": "*" } }, "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw=="],
- "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
-
- "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
-
- "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
-
- "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
-
"@types/express": ["@types/express@4.17.25", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "^1" } }, "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw=="],
"@types/express-serve-static-core": ["@types/express-serve-static-core@4.19.7", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg=="],
- "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
-
"@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="],
"@types/http-proxy": ["@types/http-proxy@1.17.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw=="],
- "@types/is-empty": ["@types/is-empty@1.2.3", "", {}, "sha512-4J1l5d79hoIvsrKh5VUKVRA1aIdsOb10Hu5j3J2VfP/msDnfTdGPmNp2E1Wg+vs97Bktzo+MZePFFXSGoykYJw=="],
-
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
- "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
-
"@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="],
- "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
-
- "@types/node": ["@types/node@22.19.2", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw=="],
+ "@types/node": ["@types/node@24.10.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA=="],
"@types/node-forge": ["@types/node-forge@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw=="],
@@ -334,7 +141,7 @@
"@types/retry": ["@types/retry@0.12.2", "", {}, "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow=="],
- "@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
+ "@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="],
"@types/serve-index": ["@types/serve-index@1.9.4", "", { "dependencies": { "@types/express": "*" } }, "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug=="],
@@ -342,59 +149,15 @@
"@types/sockjs": ["@types/sockjs@0.3.36", "", { "dependencies": { "@types/node": "*" } }, "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q=="],
- "@types/supports-color": ["@types/supports-color@8.1.3", "", {}, "sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg=="],
-
- "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
-
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
- "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.49.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.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" }, "peerDependencies": { "@typescript-eslint/parser": "^8.49.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A=="],
-
- "@typescript-eslint/parser": ["@typescript-eslint/parser@8.49.0", "", { "dependencies": { "@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" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA=="],
-
- "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.49.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.49.0", "@typescript-eslint/types": "^8.49.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g=="],
-
- "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0" } }, "sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg=="],
-
- "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.49.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA=="],
-
- "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.49.0", "", { "dependencies": { "@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" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg=="],
-
- "@typescript-eslint/types": ["@typescript-eslint/types@8.49.0", "", {}, "sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ=="],
-
- "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.49.0", "", { "dependencies": { "@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", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA=="],
-
- "@typescript-eslint/utils": ["@typescript-eslint/utils@8.49.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA=="],
-
- "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA=="],
-
- "@vitest/coverage-v8": ["@vitest/coverage-v8@4.0.15", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@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.5.1", "obug": "^2.1.1", "std-env": "^3.10.0", "tinyrainbow": "^3.0.3" }, "peerDependencies": { "@vitest/browser": "4.0.15", "vitest": "4.0.15" }, "optionalPeers": ["@vitest/browser"] }, "sha512-FUJ+1RkpTFW7rQITdgTi93qOCWJobWhBirEPCeXh2SW2wsTlFxy51apDz5gzG+ZEYt/THvWeNmhdAoS9DTwpCw=="],
-
- "@vitest/expect": ["@vitest/expect@4.0.15", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.15", "@vitest/utils": "4.0.15", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-Gfyva9/GxPAWXIWjyGDli9O+waHDC0Q0jaLdFP1qPAUUfo1FEXPXUfUkp3eZA0sSq340vPycSyOlYUeM15Ft1w=="],
-
- "@vitest/mocker": ["@vitest/mocker@4.0.15", "", { "dependencies": { "@vitest/spy": "4.0.15", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-CZ28GLfOEIFkvCFngN8Sfx5h+Se0zN+h4B7yOsPVCcgtiO7t5jt9xQh2E1UkFep+eb9fjyMfuC5gBypwb07fvQ=="],
-
- "@vitest/pretty-format": ["@vitest/pretty-format@4.0.15", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A=="],
-
- "@vitest/runner": ["@vitest/runner@4.0.15", "", { "dependencies": { "@vitest/utils": "4.0.15", "pathe": "^2.0.3" } }, "sha512-+A+yMY8dGixUhHmNdPUxOh0la6uVzun86vAbuMT3hIDxMrAOmn5ILBHm8ajrqHE0t8R9T1dGnde1A5DTnmi3qw=="],
-
- "@vitest/snapshot": ["@vitest/snapshot@4.0.15", "", { "dependencies": { "@vitest/pretty-format": "4.0.15", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-A7Ob8EdFZJIBjLjeO0DZF4lqR6U7Ydi5/5LIZ0xcI+23lYlsYJAfGn8PrIWTYdZQRNnSRlzhg0zyGu37mVdy5g=="],
-
- "@vitest/spy": ["@vitest/spy@4.0.15", "", {}, "sha512-+EIjOJmnY6mIfdXtE/bnozKEvTC4Uczg19yeZ2vtCz5Yyb0QQ31QWVQ8hswJ3Ysx/K2EqaNsVanjr//2+P3FHw=="],
-
- "@vitest/utils": ["@vitest/utils@4.0.15", "", { "dependencies": { "@vitest/pretty-format": "4.0.15", "tinyrainbow": "^3.0.3" } }, "sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA=="],
-
- "abbrev": ["abbrev@2.0.0", "", {}, "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ=="],
-
"accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
- "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
-
"acorn-walk": ["acorn-walk@8.3.4", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g=="],
- "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
+ "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
"ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="],
@@ -402,44 +165,10 @@
"ansi-html-community": ["ansi-html-community@0.0.8", "", { "bin": { "ansi-html": "bin/ansi-html" } }, "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw=="],
- "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
-
- "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
-
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
- "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
-
- "array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="],
-
"array-flatten": ["array-flatten@1.1.1", "", {}, "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="],
- "array-includes": ["array-includes@3.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.0", "es-object-atoms": "^1.1.1", "get-intrinsic": "^1.3.0", "is-string": "^1.1.1", "math-intrinsics": "^1.1.0" } }, "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ=="],
-
- "array.prototype.findlast": ["array.prototype.findlast@1.2.5", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ=="],
-
- "array.prototype.flat": ["array.prototype.flat@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg=="],
-
- "array.prototype.flatmap": ["array.prototype.flatmap@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg=="],
-
- "array.prototype.tosorted": ["array.prototype.tosorted@1.1.4", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3", "es-errors": "^1.3.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA=="],
-
- "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="],
-
- "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
-
- "ast-v8-to-istanbul": ["ast-v8-to-istanbul@0.3.8", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^9.0.1" } }, "sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ=="],
-
- "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
-
- "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
-
- "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
-
- "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
-
- "baseline-browser-mapping": ["baseline-browser-mapping@2.9.5", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-D5vIoztZOq1XM54LUdttJVc96ggEsIfju2JBvht06pSzpckp3C7HReun67Bghzrtdsq9XdMGbSSB3v3GhMNmAA=="],
-
"batch": ["batch@0.6.1", "", {}, "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw=="],
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
@@ -448,50 +177,20 @@
"bonjour-service": ["bonjour-service@1.3.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } }, "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA=="],
- "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
-
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
- "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "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" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
-
- "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
+ "bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="],
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
- "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
-
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
- "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
-
- "caniuse-lite": ["caniuse-lite@1.0.30001760", "", {}, "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw=="],
-
- "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
-
- "chai": ["chai@6.2.1", "", {}, "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg=="],
-
- "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
-
- "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
-
- "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
-
- "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
-
- "character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="],
-
"chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
- "ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="],
-
- "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
-
- "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
-
"colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="],
"commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
@@ -500,194 +199,74 @@
"compression": ["compression@1.8.1", "", { "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" } }, "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w=="],
- "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
-
- "concat-stream": ["concat-stream@2.0.0", "", { "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.0.2", "typedarray": "^0.0.6" } }, "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A=="],
-
"connect-history-api-fallback": ["connect-history-api-fallback@2.0.0", "", {}, "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA=="],
"content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
- "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
-
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="],
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
- "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
-
- "data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
-
- "data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="],
-
- "data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="],
-
"debounce": ["debounce@1.2.1", "", {}, "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug=="],
- "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
-
- "decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="],
-
- "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
+ "debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"default-browser": ["default-browser@5.4.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg=="],
"default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="],
- "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
-
"define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="],
- "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
-
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
- "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
-
"destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="],
"detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="],
- "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
-
- "diff": ["diff@5.2.0", "", {}, "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="],
-
"dns-packet": ["dns-packet@5.6.1", "", { "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" } }, "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw=="],
- "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
-
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"duplexer": ["duplexer@0.1.2", "", {}, "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="],
- "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
-
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
- "electron-to-chromium": ["electron-to-chromium@1.5.267", "", {}, "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw=="],
-
- "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
-
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
- "err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="],
-
- "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="],
-
- "es-abstract": ["es-abstract@1.24.0", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg=="],
-
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
- "es-iterator-helpers": ["es-iterator-helpers@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.0.3", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.6", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.4", "safe-array-concat": "^1.1.3" } }, "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w=="],
-
- "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
-
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
- "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
-
- "es-shim-unscopables": ["es-shim-unscopables@1.1.0", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw=="],
-
- "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="],
-
- "esbuild": ["esbuild@0.25.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" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
-
- "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
-
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
- "eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="],
-
- "eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="],
-
- "eslint-mdx": ["eslint-mdx@3.6.2", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "espree": "^9.6.1 || ^10.4.0", "estree-util-visit": "^2.0.0", "remark-mdx": "^3.1.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "synckit": "^0.11.8", "unified": "^11.0.5", "unified-engine": "^11.2.2", "unist-util-visit": "^5.0.0", "uvu": "^0.5.6", "vfile": "^6.0.3" }, "peerDependencies": { "eslint": ">=8.0.0", "remark-lint-file-extension": "*" }, "optionalPeers": ["remark-lint-file-extension"] }, "sha512-5hczn5iSSEcwtNtVXFwCKIk6iLEDaZpwc3vjYDl/B779OzaAAK/ou16J2xVdO6ecOLEO1WZqp7MRCQ/WsKDUig=="],
-
- "eslint-plugin-devup": ["eslint-plugin-devup@2.0.11", "", { "dependencies": { "@devup-ui/eslint-plugin": "^1.0", "@eslint/js": "^9.37", "@tanstack/eslint-plugin-query": "^5", "eslint": "^9.37", "eslint-config-prettier": "^10", "eslint-plugin-mdx": "^3", "eslint-plugin-prettier": "^5", "eslint-plugin-react": "^7", "eslint-plugin-react-hooks": "^7", "eslint-plugin-simple-import-sort": "^12", "eslint-plugin-unused-imports": "^4", "prettier": "^3", "typescript-eslint": "^8.46" } }, "sha512-vc38xQWpMcUpFgTwwDxcRRq5i4fv69F6pkiOKuxgAOhGTywxNcS5qmMhIAKzthwv3l6CGlERzm0/0JaiVXetPQ=="],
-
- "eslint-plugin-mdx": ["eslint-plugin-mdx@3.6.2", "", { "dependencies": { "eslint-mdx": "^3.6.2", "mdast-util-from-markdown": "^2.0.2", "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0", "remark-mdx": "^3.1.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "synckit": "^0.11.8", "unified": "^11.0.5", "vfile": "^6.0.3" }, "peerDependencies": { "eslint": ">=8.0.0" } }, "sha512-RfMd5HYD/9+cqANhVWJbuBRg3huWUsAoGJNGmPsyiRD2X6BaG6bvt1omyk1ORlg81GK8ST7Ojt5fNAuwWhWU8A=="],
-
- "eslint-plugin-prettier": ["eslint-plugin-prettier@5.5.4", "", { "dependencies": { "prettier-linter-helpers": "^1.0.0", "synckit": "^0.11.7" }, "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "optionalPeers": ["@types/eslint", "eslint-config-prettier"] }, "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg=="],
-
- "eslint-plugin-react": ["eslint-plugin-react@7.37.5", "", { "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA=="],
-
- "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="],
-
- "eslint-plugin-simple-import-sort": ["eslint-plugin-simple-import-sort@12.1.1", "", { "peerDependencies": { "eslint": ">=5.0.0" } }, "sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA=="],
-
- "eslint-plugin-unused-imports": ["eslint-plugin-unused-imports@4.3.0", "", { "peerDependencies": { "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", "eslint": "^9.0.0 || ^8.0.0" }, "optionalPeers": ["@typescript-eslint/eslint-plugin"] }, "sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA=="],
-
- "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
-
- "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
-
- "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
-
- "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
-
- "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
-
- "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
-
- "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="],
-
- "estree-util-visit": ["estree-util-visit@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/unist": "^3.0.0" } }, "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww=="],
-
- "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
-
- "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
-
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
"eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
"exit-hook": ["exit-hook@4.0.0", "", {}, "sha512-Fqs7ChZm72y40wKjOFXBKg7nJZvQJmewP5/7LtePDdnah/+FH9Hp5sgMujSCMPXlxOAW2//1jrW9pnsY7o20vQ=="],
- "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
-
"express": ["express@4.22.1", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.3", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g=="],
- "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
-
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
- "fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="],
-
- "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
-
- "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
-
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
"faye-websocket": ["faye-websocket@0.11.4", "", { "dependencies": { "websocket-driver": ">=0.5.1" } }, "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g=="],
- "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
-
- "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
-
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="],
- "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
-
- "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
-
- "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
-
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
- "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
-
- "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
-
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
"fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
@@ -696,30 +275,14 @@
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
- "function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="],
-
- "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="],
-
- "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="],
-
- "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
-
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
- "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
-
- "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
-
- "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
+ "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"glob-to-regex.js": ["glob-to-regex.js@1.2.0", "", { "peerDependencies": { "tslib": "2" } }, "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ=="],
- "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
-
- "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
-
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
@@ -728,26 +291,10 @@
"handle-thing": ["handle-thing@2.0.1", "", {}, "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg=="],
- "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
-
- "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
-
- "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
-
- "has-proto": ["has-proto@1.2.0", "", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="],
-
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
- "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
-
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
- "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
-
- "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
-
- "hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="],
-
"hpack.js": ["hpack.js@2.1.6", "", { "dependencies": { "inherits": "^2.0.1", "obuf": "^1.0.0", "readable-stream": "^2.0.1", "wbuf": "^1.1.0" } }, "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ=="],
"html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="],
@@ -762,188 +309,48 @@
"http-proxy-middleware": ["http-proxy-middleware@2.0.9", "", { "dependencies": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", "is-glob": "^4.0.1", "is-plain-obj": "^3.0.0", "micromatch": "^4.0.2" }, "peerDependencies": { "@types/express": "^4.17.13" }, "optionalPeers": ["@types/express"] }, "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q=="],
+ "husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="],
+
"hyperdyperid": ["hyperdyperid@1.2.0", "", {}, "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A=="],
"iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
- "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
-
"immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="],
- "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
-
- "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="],
-
- "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
-
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
- "ini": ["ini@4.1.3", "", {}, "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg=="],
-
- "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
-
"ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="],
- "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="],
-
- "is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="],
-
- "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
-
- "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
-
- "is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="],
-
- "is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="],
-
"is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
- "is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="],
-
- "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="],
-
- "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
-
- "is-data-view": ["is-data-view@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" } }, "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw=="],
-
- "is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="],
-
- "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="],
-
"is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
- "is-empty": ["is-empty@1.2.0", "", {}, "sha512-F2FnH/otLNJv0J6wc73A5Xo7oHLNnqplYqZhUu01tD54DIPvxIRSTSLkrUB/M0nHO4vo1O9PDfN4KoTxCzLh/w=="],
-
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
- "is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="],
-
- "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
-
- "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="],
-
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
- "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="],
-
"is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
- "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
-
- "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="],
-
"is-network-error": ["is-network-error@1.3.0", "", {}, "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
- "is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="],
-
"is-plain-obj": ["is-plain-obj@3.0.0", "", {}, "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA=="],
- "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
+ "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="],
- "is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="],
+ "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
- "is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="],
+ "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
- "is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="],
+ "jszip": ["jszip@3.10.1", "", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" } }, "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g=="],
- "is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="],
-
- "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="],
-
- "is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="],
-
- "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="],
-
- "is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="],
-
- "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="],
-
- "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
-
- "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
-
- "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="],
-
- "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="],
-
- "istanbul-lib-source-maps": ["istanbul-lib-source-maps@5.0.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0" } }, "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A=="],
-
- "istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="],
-
- "iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="],
-
- "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
-
- "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
-
- "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
-
- "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
-
- "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
-
- "json-parse-even-better-errors": ["json-parse-even-better-errors@3.0.2", "", {}, "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ=="],
-
- "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
-
- "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
-
- "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
-
- "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="],
-
- "jszip": ["jszip@3.10.1", "", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" } }, "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g=="],
-
- "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
-
- "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
-
- "launch-editor": ["launch-editor@2.12.0", "", { "dependencies": { "picocolors": "^1.1.1", "shell-quote": "^1.8.3" } }, "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg=="],
-
- "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
+ "launch-editor": ["launch-editor@2.12.0", "", { "dependencies": { "picocolors": "^1.1.1", "shell-quote": "^1.8.3" } }, "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg=="],
"lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="],
- "lines-and-columns": ["lines-and-columns@2.0.4", "", {}, "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A=="],
-
- "load-plugin": ["load-plugin@6.0.3", "", { "dependencies": { "@npmcli/config": "^8.0.0", "import-meta-resolve": "^4.0.0" } }, "sha512-kc0X2FEUZr145odl68frm+lMJuQ23+rTXYmR6TImqPtbpmXC4vVXbWKDQ9IzndA0HfyQamWfKLhzsqGSTxE63w=="],
-
- "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
-
- "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
-
- "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
-
- "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
-
- "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
-
- "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
-
- "magicast": ["magicast@0.5.1", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "source-map-js": "^1.2.1" } }, "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw=="],
-
- "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="],
-
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
- "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="],
-
- "mdast-util-mdx": ["mdast-util-mdx@3.0.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w=="],
-
- "mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="],
-
- "mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.2.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q=="],
-
- "mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="],
-
- "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="],
-
- "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="],
-
- "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
-
"media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="],
"memfs": ["memfs@4.51.1", "", { "dependencies": { "@jsonjoy.com/json-pack": "^1.11.0", "@jsonjoy.com/util": "^1.9.0", "glob-to-regex.js": "^1.0.1", "thingies": "^2.5.0", "tree-dump": "^1.0.3", "tslib": "^2.0.0" } }, "sha512-Eyt3XrufitN2ZL9c/uIRMyDwXanLI88h/L3MoWqNY747ha3dMR9dWqp8cRT5ntjZ0U1TNuq4U91ZXK0sMBjYOQ=="],
@@ -952,62 +359,6 @@
"methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="],
- "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="],
-
- "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="],
-
- "micromark-extension-mdx-expression": ["micromark-extension-mdx-expression@3.0.1", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q=="],
-
- "micromark-extension-mdx-jsx": ["micromark-extension-mdx-jsx@3.0.2", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ=="],
-
- "micromark-extension-mdx-md": ["micromark-extension-mdx-md@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ=="],
-
- "micromark-extension-mdxjs": ["micromark-extension-mdxjs@3.0.0", "", { "dependencies": { "acorn": "^8.0.0", "acorn-jsx": "^5.0.0", "micromark-extension-mdx-expression": "^3.0.0", "micromark-extension-mdx-jsx": "^3.0.0", "micromark-extension-mdx-md": "^2.0.0", "micromark-extension-mdxjs-esm": "^3.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ=="],
-
- "micromark-extension-mdxjs-esm": ["micromark-extension-mdxjs-esm@3.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-position-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A=="],
-
- "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="],
-
- "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="],
-
- "micromark-factory-mdx-expression": ["micromark-factory-mdx-expression@2.0.3", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-position-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ=="],
-
- "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="],
-
- "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="],
-
- "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="],
-
- "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
-
- "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="],
-
- "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="],
-
- "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="],
-
- "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="],
-
- "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="],
-
- "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="],
-
- "micromark-util-events-to-acorn": ["micromark-util-events-to-acorn@2.0.3", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg=="],
-
- "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="],
-
- "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="],
-
- "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="],
-
- "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="],
-
- "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="],
-
- "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="],
-
- "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="],
-
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
"mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
@@ -1018,60 +369,22 @@
"minimalistic-assert": ["minimalistic-assert@1.0.1", "", {}, "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="],
- "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
-
- "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
-
- "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
-
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
- "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+ "ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"multicast-dns": ["multicast-dns@7.2.5", "", { "dependencies": { "dns-packet": "^5.2.2", "thunky": "^1.0.2" }, "bin": { "multicast-dns": "cli.js" } }, "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg=="],
- "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
-
- "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
-
"negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="],
"node-forge": ["node-forge@1.3.3", "", {}, "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg=="],
- "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
-
- "nopt": ["nopt@7.2.1", "", { "dependencies": { "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w=="],
-
- "normalize-package-data": ["normalize-package-data@6.0.2", "", { "dependencies": { "hosted-git-info": "^7.0.0", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" } }, "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g=="],
-
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
- "npm-install-checks": ["npm-install-checks@6.3.0", "", { "dependencies": { "semver": "^7.1.1" } }, "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw=="],
-
- "npm-normalize-package-bin": ["npm-normalize-package-bin@3.0.1", "", {}, "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ=="],
-
- "npm-package-arg": ["npm-package-arg@11.0.3", "", { "dependencies": { "hosted-git-info": "^7.0.0", "proc-log": "^4.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" } }, "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw=="],
-
- "npm-pick-manifest": ["npm-pick-manifest@9.1.0", "", { "dependencies": { "npm-install-checks": "^6.0.0", "npm-normalize-package-bin": "^3.0.0", "npm-package-arg": "^11.0.0", "semver": "^7.3.5" } }, "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA=="],
-
- "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
-
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
- "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
-
- "object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
-
- "object.entries": ["object.entries@1.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.1" } }, "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw=="],
-
- "object.fromentries": ["object.fromentries@2.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0" } }, "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ=="],
-
- "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
-
"obuf": ["obuf@1.1.2", "", {}, "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="],
- "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
-
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
"on-headers": ["on-headers@1.1.0", "", {}, "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A=="],
@@ -1080,116 +393,42 @@
"opener": ["opener@1.5.2", "", { "bin": { "opener": "bin/opener-bin.js" } }, "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A=="],
- "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
-
- "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
-
- "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
-
- "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
-
"p-retry": ["p-retry@6.2.1", "", { "dependencies": { "@types/retry": "0.12.2", "is-network-error": "^1.0.0", "retry": "^0.13.1" } }, "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ=="],
- "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
-
"pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
- "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
-
- "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="],
-
- "parse-json": ["parse-json@7.1.1", "", { "dependencies": { "@babel/code-frame": "^7.21.4", "error-ex": "^1.3.2", "json-parse-even-better-errors": "^3.0.0", "lines-and-columns": "^2.0.3", "type-fest": "^3.8.0" } }, "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw=="],
-
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
- "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
-
- "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
-
- "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
-
- "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
-
"path-to-regexp": ["path-to-regexp@0.1.12", "", {}, "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="],
- "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
-
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
- "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
-
- "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
-
- "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
-
- "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
-
- "prettier": ["prettier@3.7.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA=="],
-
- "prettier-linter-helpers": ["prettier-linter-helpers@1.0.0", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w=="],
-
- "proc-log": ["proc-log@4.2.0", "", {}, "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA=="],
+ "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
- "promise-inflight": ["promise-inflight@1.0.1", "", {}, "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g=="],
-
- "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="],
-
- "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
-
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
- "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
-
"qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
"raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="],
- "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
-
- "read-package-json-fast": ["read-package-json-fast@3.0.2", "", { "dependencies": { "json-parse-even-better-errors": "^3.0.0", "npm-normalize-package-bin": "^3.0.0" } }, "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw=="],
-
"readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
- "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
-
- "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
-
- "remark-mdx": ["remark-mdx@3.1.1", "", { "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" } }, "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg=="],
-
- "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="],
-
- "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="],
-
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
"requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="],
- "resolve": ["resolve@2.0.0-next.5", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA=="],
-
- "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
-
"retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
- "rollup": ["rollup@4.53.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@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" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA=="],
-
"run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="],
- "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
-
- "safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="],
-
"safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
- "safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="],
-
- "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
-
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"schema-utils": ["schema-utils@4.3.3", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA=="],
@@ -1198,28 +437,16 @@
"selfsigned": ["selfsigned@2.4.1", "", { "dependencies": { "@types/node-forge": "^1.3.0", "node-forge": "^1" } }, "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q=="],
- "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
-
"send": ["send@0.19.1", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg=="],
"serve-index": ["serve-index@1.9.1", "", { "dependencies": { "accepts": "~1.3.4", "batch": "0.6.1", "debug": "2.6.9", "escape-html": "~1.0.3", "http-errors": "~1.6.2", "mime-types": "~2.1.17", "parseurl": "~1.3.2" } }, "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw=="],
"serve-static": ["serve-static@1.16.2", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "0.19.0" } }, "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw=="],
- "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
-
- "set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
-
- "set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="],
-
"setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="],
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
- "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
-
- "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
-
"shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
@@ -1230,78 +457,22 @@
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
- "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
-
- "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
-
"sirv": ["sirv@2.0.4", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ=="],
"sockjs": ["sockjs@0.3.24", "", { "dependencies": { "faye-websocket": "^0.11.3", "uuid": "^8.3.2", "websocket-driver": "^0.7.4" } }, "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ=="],
- "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
-
- "spdx-correct": ["spdx-correct@3.2.0", "", { "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA=="],
-
- "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="],
-
- "spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="],
-
- "spdx-license-ids": ["spdx-license-ids@3.0.22", "", {}, "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ=="],
-
"spdy": ["spdy@4.0.2", "", { "dependencies": { "debug": "^4.1.0", "handle-thing": "^2.0.0", "http-deceiver": "^1.2.7", "select-hose": "^2.0.0", "spdy-transport": "^3.0.0" } }, "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA=="],
"spdy-transport": ["spdy-transport@3.0.0", "", { "dependencies": { "debug": "^4.1.0", "detect-node": "^2.0.4", "hpack.js": "^2.1.6", "obuf": "^1.1.2", "readable-stream": "^3.0.6", "wbuf": "^1.7.3" } }, "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw=="],
- "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
-
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
- "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
-
- "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
-
- "string-width": ["string-width@6.1.0", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^10.2.1", "strip-ansi": "^7.0.1" } }, "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ=="],
-
- "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
-
- "string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="],
-
- "string.prototype.repeat": ["string.prototype.repeat@1.0.0", "", { "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" } }, "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w=="],
-
- "string.prototype.trim": ["string.prototype.trim@1.2.10", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-object-atoms": "^1.0.0", "has-property-descriptors": "^1.0.2" } }, "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA=="],
-
- "string.prototype.trimend": ["string.prototype.trimend@1.0.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ=="],
-
- "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="],
-
"string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
- "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
-
- "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
-
- "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
-
- "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
-
- "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
-
- "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
-
- "synckit": ["synckit@0.11.11", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw=="],
-
"thingies": ["thingies@2.5.0", "", { "peerDependencies": { "tslib": "^2" } }, "sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw=="],
"thunky": ["thunky@1.1.0", "", {}, "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="],
- "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
-
- "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
-
- "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
-
- "tinyrainbow": ["tinyrainbow@3.0.3", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="],
-
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
@@ -1310,88 +481,24 @@
"tree-dump": ["tree-dump@1.1.0", "", { "peerDependencies": { "tslib": "2" } }, "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA=="],
- "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
-
- "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
-
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
- "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
-
- "type-fest": ["type-fest@3.13.1", "", {}, "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="],
-
"type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
- "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
-
- "typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="],
-
- "typed-array-byte-offset": ["typed-array-byte-offset@1.0.4", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.15", "reflect.getprototypeof": "^1.0.9" } }, "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ=="],
-
- "typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="],
-
- "typedarray": ["typedarray@0.0.6", "", {}, "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="],
-
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
- "typescript-eslint": ["typescript-eslint@8.49.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.49.0", "@typescript-eslint/parser": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0", "@typescript-eslint/utils": "8.49.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg=="],
-
- "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
-
- "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
-
- "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
-
- "unified-engine": ["unified-engine@11.2.2", "", { "dependencies": { "@types/concat-stream": "^2.0.0", "@types/debug": "^4.0.0", "@types/is-empty": "^1.0.0", "@types/node": "^22.0.0", "@types/unist": "^3.0.0", "concat-stream": "^2.0.0", "debug": "^4.0.0", "extend": "^3.0.0", "glob": "^10.0.0", "ignore": "^6.0.0", "is-empty": "^1.0.0", "is-plain-obj": "^4.0.0", "load-plugin": "^6.0.0", "parse-json": "^7.0.0", "trough": "^2.0.0", "unist-util-inspect": "^8.0.0", "vfile": "^6.0.0", "vfile-message": "^4.0.0", "vfile-reporter": "^8.0.0", "vfile-statistics": "^3.0.0", "yaml": "^2.0.0" } }, "sha512-15g/gWE7qQl9tQ3nAEbMd5h9HV1EACtFs6N9xaRBZICoCwnNGbal1kOs++ICf4aiTdItZxU2s/kYWhW7htlqJg=="],
-
- "unist-util-inspect": ["unist-util-inspect@8.1.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-mOlg8Mp33pR0eeFpo5d2902ojqFFOKMMG2hF8bmH7ZlhnmjFgh0NI3/ZDwdaBJNbvrS7LZFVrBVtIE9KZ9s7vQ=="],
-
- "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="],
-
- "unist-util-position-from-estree": ["unist-util-position-from-estree@2.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ=="],
-
- "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
-
- "unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="],
-
- "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="],
+ "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
- "update-browserslist-db": ["update-browserslist-db@1.2.2", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA=="],
-
- "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
-
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
"uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
- "uvu": ["uvu@0.5.6", "", { "dependencies": { "dequal": "^2.0.0", "diff": "^5.0.0", "kleur": "^4.0.3", "sade": "^1.7.3" }, "bin": { "uvu": "bin.js" } }, "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA=="],
-
- "validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="],
-
- "validate-npm-package-name": ["validate-npm-package-name@5.0.1", "", {}, "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ=="],
-
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
- "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
-
- "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],
-
- "vfile-reporter": ["vfile-reporter@8.1.1", "", { "dependencies": { "@types/supports-color": "^8.0.0", "string-width": "^6.0.0", "supports-color": "^9.0.0", "unist-util-stringify-position": "^4.0.0", "vfile": "^6.0.0", "vfile-message": "^4.0.0", "vfile-sort": "^4.0.0", "vfile-statistics": "^3.0.0" } }, "sha512-qxRZcnFSQt6pWKn3PAk81yLK2rO2i7CDXpy8v8ZquiEOMLSnPw6BMSi9Y1sUCwGGl7a9b3CJT1CKpnRF7pp66g=="],
-
- "vfile-sort": ["vfile-sort@4.0.0", "", { "dependencies": { "vfile": "^6.0.0", "vfile-message": "^4.0.0" } }, "sha512-lffPI1JrbHDTToJwcq0rl6rBmkjQmMuXkAxsZPRS9DXbaJQvc642eCg6EGxcX2i1L+esbuhq+2l9tBll5v8AeQ=="],
-
- "vfile-statistics": ["vfile-statistics@3.0.0", "", { "dependencies": { "vfile": "^6.0.0", "vfile-message": "^4.0.0" } }, "sha512-/qlwqwWBWFOmpXujL/20P+Iuydil0rZZNglR+VNm6J0gpLHwuVM5s7g2TfVoswbXjZ4HuIhLMySEyIw5i7/D8w=="],
-
- "vite": ["vite@7.2.7", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ=="],
-
- "vitest": ["vitest@4.0.15", "", { "dependencies": { "@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.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@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": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA=="],
-
- "walk-up-path": ["walk-up-path@3.0.1", "", {}, "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA=="],
-
"wbuf": ["wbuf@1.7.3", "", { "dependencies": { "minimalistic-assert": "^1.0.0" } }, "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA=="],
"webpack-bundle-analyzer": ["webpack-bundle-analyzer@4.10.2", "", { "dependencies": { "@discoveryjs/json-ext": "0.5.7", "acorn": "^8.0.4", "acorn-walk": "^8.0.0", "commander": "^7.2.0", "debounce": "^1.2.1", "escape-string-regexp": "^4.0.0", "gzip-size": "^6.0.0", "html-escaper": "^2.0.2", "opener": "^1.5.2", "picocolors": "^1.0.0", "sirv": "^2.0.3", "ws": "^7.3.1" }, "bin": { "webpack-bundle-analyzer": "lib/bin/analyzer.js" } }, "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw=="],
@@ -1404,147 +511,37 @@
"websocket-extensions": ["websocket-extensions@0.1.4", "", {}, "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg=="],
- "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
-
- "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
-
- "which-builtin-type": ["which-builtin-type@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.1.0", "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", "which-typed-array": "^1.1.16" } }, "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q=="],
-
- "which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="],
-
- "which-typed-array": ["which-typed-array@1.1.19", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="],
-
- "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
-
- "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
-
- "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
-
- "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
-
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
- "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
-
- "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
-
- "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
-
- "zod": ["zod@4.1.13", "", {}, "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig=="],
-
- "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
-
- "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
-
- "@babel/code-frame/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
-
- "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
-
- "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
-
- "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
-
- "@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
-
- "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
-
- "@npmcli/git/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
-
- "@npmcli/git/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
-
- "@npmcli/map-workspaces/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
-
- "@npmcli/promise-spawn/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
-
- "@types/serve-static/@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="],
-
- "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
-
"accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
- "ajv-formats/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
-
- "ajv-keywords/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
-
- "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
-
- "body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
-
- "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
-
- "compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
-
"compression/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
- "concat-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
-
"content-disposition/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
- "eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
-
- "eslint-plugin-react/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
-
- "express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
-
"express/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
- "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
-
- "glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
-
- "hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
-
- "loose-envify/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
-
- "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
-
"mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
- "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
-
- "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
-
- "promise-retry/retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
-
"proxy-addr/ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
- "readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
-
- "safe-array-concat/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
-
- "safe-push-apply/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
-
- "schema-utils/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
-
- "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
-
"send/http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
- "send/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
+ "send/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
- "serve-index/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
+ "send/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
"serve-index/http-errors": ["http-errors@1.6.3", "", { "dependencies": { "depd": "~1.1.2", "inherits": "2.0.3", "setprototypeof": "1.1.0", "statuses": ">= 1.4.0 < 2" } }, "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A=="],
"serve-static/send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="],
- "spdy-transport/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
-
- "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
-
- "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
-
- "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
-
- "unified/is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
-
- "unified-engine/ignore": ["ignore@6.0.2", "", {}, "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A=="],
+ "spdy/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
- "unified-engine/is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
+ "spdy-transport/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
- "vfile-reporter/supports-color": ["supports-color@9.4.0", "", {}, "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw=="],
+ "spdy-transport/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
"webpack-bundle-analyzer/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
@@ -1552,46 +549,6 @@
"websocket-driver/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
- "which-builtin-type/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
-
- "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
-
- "wrap-ansi/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
-
- "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
-
- "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
-
- "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
-
- "@npmcli/git/which/isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
-
- "@npmcli/map-workspaces/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
-
- "@npmcli/promise-spawn/which/isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
-
- "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
-
- "ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
-
- "ajv-keywords/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
-
- "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
-
- "compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
-
- "express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
-
- "finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
-
- "glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
-
- "schema-utils/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
-
- "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
-
- "serve-index/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
-
"serve-index/http-errors/depd": ["depd@1.1.2", "", {}, "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ=="],
"serve-index/http-errors/inherits": ["inherits@2.0.3", "", {}, "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw=="],
@@ -1600,22 +557,16 @@
"serve-index/http-errors/statuses": ["statuses@1.5.0", "", {}, "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA=="],
- "serve-static/send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
-
"serve-static/send/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
"serve-static/send/http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
- "serve-static/send/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
-
- "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+ "serve-static/send/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
- "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
-
- "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+ "serve-static/send/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
- "wrap-ansi/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
+ "spdy-transport/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
- "serve-static/send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+ "spdy/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
}
}
diff --git a/bunfig.toml b/bunfig.toml
new file mode 100644
index 0000000..076742e
--- /dev/null
+++ b/bunfig.toml
@@ -0,0 +1,4 @@
+[test]
+coverage = true
+coverageSkipTestFiles = true
+coveragePathIgnorePatterns = ["dist/**"]
diff --git a/eslint.config.mjs b/eslint.config.mjs
deleted file mode 100644
index c9f9713..0000000
--- a/eslint.config.mjs
+++ /dev/null
@@ -1,3 +0,0 @@
-import { configs } from 'eslint-plugin-devup'
-
-export default configs.recommended
diff --git a/manifest.json b/manifest.json
index fd1c64b..be2515f 100644
--- a/manifest.json
+++ b/manifest.json
@@ -3,20 +3,12 @@
"id": "1412341601954480694",
"api": "1.0.0",
"main": "./dist/code.js",
- "capabilities": [
- "codegen",
- "inspect"
- ],
+ "capabilities": ["codegen", "inspect"],
"enableProposedApi": false,
"documentAccess": "dynamic-page",
- "editorType": [
- "dev",
- "figma"
- ],
+ "editorType": ["dev", "figma"],
"networkAccess": {
- "allowedDomains": [
- "https://cdn.sheetjs.com"
- ]
+ "allowedDomains": ["https://cdn.sheetjs.com"]
},
"codegenLanguages": [
{
@@ -58,4 +50,4 @@
"command": "export-components"
}
]
-}
\ No newline at end of file
+}
diff --git a/package.json b/package.json
index 9e8a039..947688c 100644
--- a/package.json
+++ b/package.json
@@ -7,26 +7,24 @@
"dev": "rspack dev",
"build": "rspack build",
"watch": "rspack build --watch",
- "test": "vitest test --run --coverage",
- "preview": "rsbuild preview",
- "lint": "eslint",
- "lint:fix": "eslint --ext .ts,.tsx --ignore-pattern node_modules --fix ."
+ "test": "bun test --coverage",
+ "lint": "biome check --error-on-warnings",
+ "lint:fix": "biome check --fix",
+ "prepare": "husky"
},
"author": "",
"license": "",
"devDependencies": {
- "@figma/plugin-typings": "^1.121.0",
- "@rspack/cli": "^1.6.6",
- "@rspack/core": "^1.6.6",
- "@typescript-eslint/eslint-plugin": "^8.49.0",
- "@typescript-eslint/parser": "^8.49.0",
- "@vitest/coverage-v8": "^4.0.15",
- "eslint": "^9.39.1",
- "eslint-plugin-devup": "^2.0.11",
- "typescript": "^5.9.3",
- "vitest": "^4.0.15"
+ "@figma/plugin-typings": "^1.121",
+ "@rspack/cli": "^1.6.7",
+ "@rspack/core": "^1.6.7",
+
+ "husky": "^9.1",
+ "typescript": "^5.9",
+ "@biomejs/biome": "^2.3",
+ "@types/bun": "^1.3"
},
"dependencies": {
- "jszip": "^3.10.1"
+ "jszip": "^3.10"
}
-}
\ No newline at end of file
+}
diff --git a/rspack.config.js b/rspack.config.js
index ceb1bf6..130d3ef 100644
--- a/rspack.config.js
+++ b/rspack.config.js
@@ -1,4 +1,3 @@
-// eslint-disable-next-line no-undef
module.exports = {
entry: {
code: './src/code.ts',
diff --git a/src/__tests__/__snapshots__/code.test.ts.snap b/src/__tests__/__snapshots__/code.test.ts.snap
new file mode 100644
index 0000000..e8f7da4
--- /dev/null
+++ b/src/__tests__/__snapshots__/code.test.ts.snap
@@ -0,0 +1,36 @@
+// Bun Snapshot v1, https://bun.sh/docs/test/snapshots
+
+exports[`registerCodegen should register codegen 1`] = `
+[
+ {
+ "code":
+"export function Test() {
+ return
+ }"
+,
+ "language": "TYPESCRIPT",
+ "title": "Test - Components",
+ },
+ {
+ "code":
+"echo 'export function Test() {
+ return
+ }' > Test.tsx"
+,
+ "language": "BASH",
+ "title": "Test - Components CLI",
+ },
+]
+`;
+
+exports[`registerCodegen should register codegen 2`] = `
+[
+ {
+ "code": "",
+ "language": "TYPESCRIPT",
+ "title": "Main",
+ },
+]
+`;
+
+exports[`registerCodegen should register codegen 3`] = `[]`;
diff --git a/src/__tests__/code.test.ts b/src/__tests__/code.test.ts
index c8b32db..74c74e7 100644
--- a/src/__tests__/code.test.ts
+++ b/src/__tests__/code.test.ts
@@ -1,123 +1,167 @@
-import { exportDevup, importDevup } from '../commands/devup'
-import { exportAssets } from '../commands/exportAssets'
-import { exportComponents } from '../commands/exportComponents'
+import {
+ afterEach,
+ beforeAll,
+ beforeEach,
+ describe,
+ expect,
+ it,
+ mock,
+ spyOn,
+} from 'bun:test'
+import { registerCodegen, run, runCommand } from '../code'
+import * as devupModule from '../commands/devup'
+import * as exportAssetsModule from '../commands/exportAssets'
+import * as exportComponentsModule from '../commands/exportComponents'
-vi.mock('../commands/devup')
-vi.mock('../commands/exportAssets')
-vi.mock('../commands/exportComponents')
+beforeAll(() => {
+ ;(globalThis as { figma?: unknown }).figma = {
+ editorType: 'dev',
+ mode: 'codegen',
+ command: 'noop',
+ codegen: { on: mock(() => {}) },
+ closePlugin: mock(() => {}),
+ } as unknown as typeof figma
+})
beforeEach(() => {
- vi.resetModules()
+ spyOn(devupModule, 'exportDevup').mockImplementation(
+ mock(() => Promise.resolve()),
+ )
+ spyOn(devupModule, 'importDevup').mockImplementation(
+ mock(() => Promise.resolve()),
+ )
+ spyOn(exportAssetsModule, 'exportAssets').mockImplementation(
+ mock(() => Promise.resolve()),
+ )
+ spyOn(exportComponentsModule, 'exportComponents').mockImplementation(
+ mock(() => Promise.resolve()),
+ )
+})
+
+afterEach(() => {
+ ;(globalThis as { figma?: unknown }).figma = undefined
+ mock.restore()
})
-describe('figma', () => {
- it('should export devup', async () => {
- const closePlugin = vi.fn()
- ;(globalThis as any).figma = {
+describe('runCommand', () => {
+ it.each([
+ ['export-devup', ['json'], 'exportDevup'],
+ ['export-devup-without-treeshaking', ['json', false], 'exportDevup'],
+ ['export-devup-excel', ['excel'], 'exportDevup'],
+ ['export-devup-excel-without-treeshaking', ['excel', false], 'exportDevup'],
+ ['import-devup', ['json'], 'importDevup'],
+ ['import-devup-excel', ['excel'], 'importDevup'],
+ ['export-assets', [], 'exportAssets'],
+ ['export-components', [], 'exportComponents'],
+ ] as const)('dispatches %s', async (command, args, fn) => {
+ const closePlugin = mock(() => {})
+ const figmaMock = {
editorType: 'figma',
- command: 'export-devup',
+ command,
closePlugin,
+ } as unknown as typeof figma
+
+ await runCommand(figmaMock as typeof figma)
+
+ switch (fn) {
+ case 'exportDevup':
+ expect(devupModule.exportDevup).toHaveBeenCalledWith(...args)
+ break
+ case 'importDevup':
+ expect(devupModule.importDevup).toHaveBeenCalledWith(...args)
+ break
+ case 'exportAssets':
+ expect(exportAssetsModule.exportAssets).toHaveBeenCalled()
+ break
+ case 'exportComponents':
+ expect(exportComponentsModule.exportComponents).toHaveBeenCalled()
+ break
}
- vi.mocked(exportDevup).mockResolvedValueOnce(0)
- await import('../code')
- expect(exportDevup).toBeCalledTimes(1)
- expect(closePlugin).toBeCalledTimes(1)
+ expect(closePlugin).toHaveBeenCalled()
})
})
-it('should import devup', async () => {
- const closePlugin = vi.fn()
- ;(globalThis as any).figma = {
- editorType: 'figma',
- command: 'import-devup',
- closePlugin,
- }
- vi.mocked(importDevup).mockResolvedValueOnce()
- await import('../code')
- expect(importDevup).toBeCalledTimes(1)
- expect(closePlugin).toBeCalledTimes(1)
-})
-it('should export assets', async () => {
- const closePlugin = vi.fn()
- ;(globalThis as any).figma = {
- editorType: 'figma',
- command: 'export-assets',
- closePlugin,
- }
- vi.mocked(exportAssets).mockResolvedValueOnce()
- await import('../code')
- expect(exportAssets).toBeCalledTimes(1)
- expect(closePlugin).toBeCalledTimes(1)
-})
-it('should export components', async () => {
- const closePlugin = vi.fn()
- ;(globalThis as any).figma = {
- editorType: 'figma',
- command: 'export-components',
- closePlugin,
- }
- vi.mocked(exportComponents).mockResolvedValueOnce()
- await import('../code')
- expect(exportComponents).toBeCalledTimes(1)
- expect(closePlugin).toBeCalledTimes(1)
-})
-describe('codegen', () => {
- it('should generate code', async () => {
- const closePlugin = vi.fn()
- const on = vi.fn()
- ;(globalThis as any).figma = {
- editorType: 'dev',
- mode: 'codegen',
- closePlugin,
- codegen: {
- on,
+describe('registerCodegen', () => {
+ it.each([
+ [
+ {
+ editorType: 'dev',
+ mode: 'codegen',
+ command: 'noop',
},
- }
- await import('../code')
- expect(closePlugin).toBeCalledTimes(0)
- expect(on).toHaveBeenCalledExactlyOnceWith('generate', expect.any(Function))
- const callback = vi.mocked(on).mock.calls[0][1]
-
- expect(
- await callback({
+ {
node: {
- type: 'code',
- code: 'code',
- getCSSAsync: async () => {
- return {
- background: 'red',
- }
- },
- visible: true,
+ type: 'COMPONENT',
+ name: 'Test',
},
- }),
- ).toEqual([
+ language: 'devup-ui',
+ },
+ ],
+ [
{
- code: '',
- language: 'JAVASCRIPT',
- title: undefined,
+ editorType: 'dev',
+ mode: 'codegen',
+ command: 'noop',
},
- ])
-
- expect(
- await callback({
+ {
node: {
- type: 'code',
- code: 'code',
- getCSSAsync: async () => {
- return {
- background: 'red',
- }
- },
- visible: false,
+ type: 'FRAME',
+ name: 'Main',
},
- }),
- ).toEqual([
+ language: 'devup-ui',
+ },
+ ],
+ [
+ {
+ editorType: 'dev',
+ mode: 'codegen',
+ command: 'noop',
+ },
{
- code: '',
- language: 'JAVASCRIPT',
- title: undefined,
+ node: {
+ type: 'FRAME',
+ name: 'Other',
+ },
+ language: 'other',
},
- ])
+ ],
+ ] as const)('should register codegen', async (figmaInfo, event) => {
+ const figmaMock = {
+ ...figmaInfo,
+ codegen: { on: mock(() => {}) },
+ closePlugin: mock(() => {}),
+ } as unknown as typeof figma
+ registerCodegen(figmaMock)
+ expect(figmaMock.codegen.on).toHaveBeenCalledWith(
+ 'generate',
+ expect.any(Function),
+ )
+
+ expect(
+ await (figmaMock.codegen.on as ReturnType).mock.calls[0][1](
+ event,
+ ),
+ ).toMatchSnapshot()
})
})
+
+it('should not register codegen if figma is not defined', () => {
+ run(undefined as unknown as typeof figma)
+ expect(devupModule.exportDevup).not.toHaveBeenCalled()
+ expect(devupModule.importDevup).not.toHaveBeenCalled()
+ expect(exportAssetsModule.exportAssets).not.toHaveBeenCalled()
+ expect(exportComponentsModule.exportComponents).not.toHaveBeenCalled()
+})
+
+it('should run command', () => {
+ const figmaMock = {
+ editorType: 'figma',
+ command: 'export-devup',
+ closePlugin: mock(() => {}),
+ } as unknown as typeof figma
+ run(figmaMock as typeof figma)
+ expect(devupModule.exportDevup).toHaveBeenCalledWith('json')
+ expect(devupModule.importDevup).not.toHaveBeenCalled()
+ expect(exportAssetsModule.exportAssets).not.toHaveBeenCalled()
+ expect(exportComponentsModule.exportComponents).not.toHaveBeenCalled()
+})
diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts
index 6f0f2b1..297c206 100644
--- a/src/__tests__/utils.test.ts
+++ b/src/__tests__/utils.test.ts
@@ -1,166 +1,5 @@
-import {
- cssToProps,
- fixChildrenText,
- formatSvg,
- organizeProps,
- space,
-} from '../utils'
-
-describe('organizeProps', () => {
- it('should organize props', () => {
- expect(organizeProps({})).toEqual({})
-
- expect(organizeProps({ aspectRatio: '1/1' })).toEqual({
- aspectRatio: '1',
- })
- })
- it.each(['p', 'm'])('should organize space props', (pro) => {
- expect(organizeProps({ [pro]: '10px 10px 10px 10px' })).toEqual({
- [pro]: '10px',
- })
- expect(organizeProps({ [pro]: '10px 10px 10px' })).toEqual({
- [pro]: '10px',
- })
- expect(organizeProps({ [pro]: '10px 10px' })).toEqual({
- [pro]: '10px',
- })
- expect(organizeProps({ [pro]: '10px' })).toEqual({
- [pro]: '10px',
- })
- expect(organizeProps({ [pro]: '10px 20px 30px 40px' })).toEqual({
- [pro]: '10px 20px 30px 40px',
- })
- expect(organizeProps({ [pro]: '10px 0px 10px 0px' })).toEqual({
- [pro + 'y']: '10px',
- })
- expect(organizeProps({ [pro]: '0px 10px 0px 10px' })).toEqual({
- [pro + 'x']: '10px',
- })
- expect(organizeProps({ [pro]: '10px 20px 20px 20px' })).toEqual({
- [pro + 'x']: '20px',
- [pro + 't']: '10px',
- [pro + 'b']: '20px',
- })
- expect(organizeProps({ [pro]: '10px 20px 10px 20px' })).toEqual({
- [pro + 'x']: '20px',
- [pro + 'y']: '10px',
- })
- expect(organizeProps({ [pro]: '10px 10px' })).toEqual({
- [pro]: '10px',
- })
- expect(organizeProps({ [pro]: '1px' })).toEqual({
- [pro]: '1px',
- })
- expect(organizeProps({ [pro]: '1px 2px' })).toEqual({
- [pro + 'x']: '2px',
- [pro + 'y']: '1px',
- })
- expect(organizeProps({ [pro]: '0px 2px' })).toEqual({
- [pro + 'x']: '2px',
- })
- expect(organizeProps({ [pro]: '1px 2px 30px' })).toEqual({
- [pro + 't']: '1px',
- [pro + 'x']: '2px',
- [pro + 'b']: '30px',
- })
- expect(organizeProps({ [pro]: '0px 2px 0px' })).toEqual({
- [pro + 'x']: '2px',
- })
- expect(organizeProps({ [pro]: '30px 2px 30px 4px' })).toEqual({
- [pro + 'y']: '30px',
- [pro + 'r']: '2px',
- [pro + 'l']: '4px',
- })
- expect(organizeProps({ [pro]: '0px' })).toEqual({})
- expect(organizeProps({ [pro]: '0px 0px' })).toEqual({})
- expect(organizeProps({ [pro]: '0px 0px 0px' })).toEqual({})
- expect(organizeProps({ [pro]: '0px 0px 0px 0px' })).toEqual({})
- expect(organizeProps({ [pro]: '10px 8px 10px 6px' })).toEqual({
- [pro + 'y']: '10px',
- [pro + 'r']: '8px',
- [pro + 'l']: '6px',
- })
- expect(organizeProps({ [pro]: '10px 9px 8px 9px' })).toEqual({
- [pro + 't']: '10px',
- [pro + 'x']: '9px',
- [pro + 'b']: '8px',
- })
- })
-
- it('should organize space props 2', () => {
- expect(
- organizeProps({ p: '10px 20px 10px 20px', m: '10px 20px 10px 20px' }),
- ).toEqual({
- px: '20px',
- py: '10px',
- mx: '20px',
- my: '10px',
- })
- })
-
- it('should change image url', () => {
- expect(organizeProps({ bg: 'url()' })).toEqual({
- bg: 'url(/path/to/image)',
- })
- })
-
- it('should change props when value is default value', () => {
- expect(organizeProps({ p: '0px' })).toEqual({})
- expect(organizeProps({ m: '0px' })).toEqual({})
- expect(organizeProps({ flex: '1 0 0' })).toEqual({ flex: '1' })
- })
-
- it('should split comment', () => {
- expect(organizeProps({ p: '10px /* comment */' })).toEqual({
- p: '10px',
- })
- })
-
- it('should disattach "', () => {
- expect(organizeProps({ p: '"10px"' })).toEqual({
- p: '10px',
- })
- })
-
- it('should delete empty props', () => {
- expect(organizeProps({ p: '' })).toEqual({})
- expect(organizeProps({ some: '' })).toEqual({})
- })
-
- it('should extract variable props', () => {
- expect(organizeProps({ bg: 'var(--primary)' })).toEqual({
- bg: '$primary',
- })
-
- expect(organizeProps({ bg: 'var(--PASCAL_CASE)' })).toEqual({
- bg: '$pascalCase',
- })
-
- expect(
- organizeProps({
- bg: 'linear-gradient(202deg, var(--primary, #5B34F7) 3.96%, #6D7EDC 85.94%)',
- }),
- ).toEqual({
- bg: 'linear-gradient(202deg, $primary 3.96%, #6D7EDC 85.94%)',
- })
-
- expect(
- organizeProps({
- bg: '0px 0px 15px 0px var(--clientShadow, rgba(0, 0, 0, 0.07))',
- }),
- ).toEqual({
- bg: '0 0 15px 0 $clientShadow',
- })
-
- expect(
- organizeProps({
- bg: '0px 0px 15px 0px var(--clientShadow, var(--primary, rgba(0, 0, 0, 0.07)))',
- }),
- ).toEqual({
- bg: '0 0 15px 0 $clientShadow',
- })
- })
-})
+import { describe, expect, it, test } from 'bun:test'
+import { getComponentName, space } from '../utils'
describe('space', () => {
it('should create space', () => {
@@ -169,109 +8,65 @@ describe('space', () => {
expect(space(2)).toEqual(' ')
})
})
-describe('cssToProps', () => {
- it('should transform css to props', () => {
- expect(
- cssToProps({
- color: 'red',
- fontSize: '16px',
- fontFamily: 'Arial',
- }),
- ).toEqual({
- color: 'red',
- fontSize: '16px',
- fontFamily: 'Arial',
- })
- })
- it('should transform css to props with shorthand', () => {
- expect(
- cssToProps({
- margin: '10px',
- padding: '20px',
- position: 'absolute',
- }),
- ).toEqual({
- m: '10px',
- p: '20px',
- pos: 'absolute',
- })
- })
- it('should merge css to props with shorthand', () => {
- expect(
- cssToProps({
- 'margin-top': '10px',
- 'margin-bottom': '10px',
- }),
- ).toEqual({
- my: '10px',
- })
- expect(
- cssToProps({
- 'margin-left': '10px',
- 'margin-right': '10px',
- }),
- ).toEqual({
- mx: '10px',
- })
- expect(
- cssToProps({
- 'margin-top': '10px',
- 'margin-bottom': '10px',
- 'margin-right': '10px',
- 'margin-left': '10px',
- }),
- ).toEqual({
- m: '10px',
- })
- expect(
- cssToProps({
- width: '100px',
- height: '100px',
- }),
- ).toEqual({
- boxSize: '100px',
- })
- })
-})
-
-describe('formatSvg', () => {
- it('should format svg', () => {
- expect(formatSvg('')).toEqual('')
- expect(formatSvg('')).toEqual(
- '',
- )
- expect(
- formatSvg(''),
- ).toEqual('')
- expect(
- formatSvg('', 1),
- ).toEqual(
- ' ',
- )
- expect(formatSvg('', 1)).toEqual(' ')
- })
-})
-describe('fixChildrenText', () => {
- it.each([
- ['aa', 'aa'],
- [' aa', '{" "}aa'],
- ['aa ', 'aa{" "}'],
- [' aa ', '{" "}aa{" "}'],
- [' aa ', '{" "}aa{" "}'],
- ['{', '{"{"}'],
- ['}', '{"}"}'],
- ['&', '{"&"}'],
- ['>', '{">"}'],
- ['<', '{"<"}'],
- ['{wow', '{"{"}wow'],
- ['{wow{', '{"{"}wow{"{"}'],
- ['{wow{wow', '{"{"}wow{"{"}wow'],
- ['{wow{wow}', '{"{"}wow{"{"}wow{"}"}'],
- ['{wow{wow{', '{"{"}wow{"{"}wow{"{"}'],
- ['{wow{wow{wow', '{"{"}wow{"{"}wow{"{"}wow'],
- ['{wow{wow{wow}', '{"{"}wow{"{"}wow{"{"}wow{"}"}'],
- ])('should fix children text with special characters', (input, output) => {
- expect(fixChildrenText(input)).toEqual(output)
+describe('getComponentName', () => {
+ test.each([
+ {
+ description: 'should return pascal case name for COMPONENT_SET',
+ node: {
+ type: 'COMPONENT_SET',
+ name: 'button-component',
+ } as unknown as SceneNode,
+ expected: 'ButtonComponent',
+ },
+ {
+ description:
+ 'should return parent name for COMPONENT with COMPONENT_SET parent',
+ node: {
+ type: 'COMPONENT',
+ name: 'button-variant',
+ parent: {
+ type: 'COMPONENT_SET',
+ name: 'button-set',
+ } as unknown as SceneNode,
+ } as unknown as SceneNode,
+ expected: 'ButtonSet',
+ },
+ {
+ description:
+ 'should return node name for COMPONENT without COMPONENT_SET parent',
+ node: {
+ type: 'COMPONENT',
+ name: 'button-component',
+ parent: null,
+ } as unknown as SceneNode,
+ expected: 'ButtonComponent',
+ },
+ {
+ description: 'should return pascal case name for FRAME',
+ node: {
+ type: 'FRAME',
+ name: 'my-frame',
+ } as unknown as SceneNode,
+ expected: 'MyFrame',
+ },
+ {
+ description: 'should return pascal case name for RECTANGLE',
+ node: {
+ type: 'RECTANGLE',
+ name: 'my-rectangle',
+ } as unknown as SceneNode,
+ expected: 'MyRectangle',
+ },
+ {
+ description: 'should return pascal case name for TEXT',
+ node: {
+ type: 'TEXT',
+ name: 'my-text',
+ } as unknown as SceneNode,
+ expected: 'MyText',
+ },
+ ])('$description', ({ node, expected }) => {
+ expect(getComponentName(node)).toBe(expected)
})
})
diff --git a/src/code.ts b/src/code.ts
index 2762944..6a6cf50 100644
--- a/src/code.ts
+++ b/src/code.ts
@@ -3,75 +3,89 @@ import { exportDevup, importDevup } from './commands/devup'
import { exportAssets } from './commands/exportAssets'
import { exportComponents } from './commands/exportComponents'
-if (figma.editorType === 'dev' && figma.mode === 'codegen') {
- figma.codegen.on('generate', async ({ node, language }) => {
- switch (language) {
- case 'devup-ui': {
- const time = Date.now()
- const codegen = new Codegen(node)
- await codegen.run()
- const componentsCodes = codegen.getComponentsCodes()
- console.info(`[benchmark] devup-ui end ${Date.now() - time}ms`)
- return [
- ...(node.type === 'COMPONENT' ||
- node.type === 'COMPONENT_SET' ||
- node.type === 'INSTANCE'
- ? []
- : [
- {
- title: node.name,
- language: 'TYPESCRIPT',
- code: codegen.getCode(),
- } as const,
- ]),
- ...(componentsCodes.length > 0
- ? ([
- {
- title: node.name + ' - Components',
- language: 'TYPESCRIPT',
- code: componentsCodes.map((code) => code[1]).join('\n\n'),
- },
- {
- title: node.name + ' - Components CLI',
- language: 'BASH',
- code: componentsCodes
- .map(
- ([componentName, code]) =>
- `echo '${code}' > ${componentName}.tsx`,
- )
- .join('\n'),
- },
- ] as const)
- : []),
- ]
+export function registerCodegen(ctx: typeof figma = figma) {
+ if (ctx.editorType === 'dev' && ctx.mode === 'codegen') {
+ ctx.codegen.on('generate', async ({ node, language }) => {
+ switch (language) {
+ case 'devup-ui': {
+ const time = Date.now()
+ const codegen = new Codegen(node)
+ await codegen.run()
+ const componentsCodes = codegen.getComponentsCodes()
+ console.info(`[benchmark] devup-ui end ${Date.now() - time}ms`)
+ return [
+ ...(node.type === 'COMPONENT' ||
+ node.type === 'COMPONENT_SET' ||
+ node.type === 'INSTANCE'
+ ? []
+ : [
+ {
+ title: node.name,
+ language: 'TYPESCRIPT',
+ code: codegen.getCode(),
+ } as const,
+ ]),
+ ...(componentsCodes.length > 0
+ ? ([
+ {
+ title: `${node.name} - Components`,
+ language: 'TYPESCRIPT',
+ code: componentsCodes.map((code) => code[1]).join('\n\n'),
+ },
+ {
+ title: `${node.name} - Components CLI`,
+ language: 'BASH',
+ code: componentsCodes
+ .map(
+ ([componentName, code]) =>
+ `echo '${code}' > ${componentName}.tsx`,
+ )
+ .join('\n'),
+ },
+ ] as const)
+ : []),
+ ]
+ }
}
- }
- return []
- })
+ return []
+ })
+ }
}
-switch (figma.command) {
- case 'export-devup':
- exportDevup('json').finally(() => figma.closePlugin())
- break
- case 'export-devup-without-treeshaking':
- exportDevup('json', false).finally(() => figma.closePlugin())
- break
- case 'export-devup-excel':
- exportDevup('excel').finally(() => figma.closePlugin())
- break
- case 'export-devup-excel-without-treeshaking':
- exportDevup('excel', false).finally(() => figma.closePlugin())
- break
- case 'import-devup':
- importDevup('json').finally(() => figma.closePlugin())
- break
- case 'import-devup-excel':
- importDevup('excel').finally(() => figma.closePlugin())
- break
- case 'export-assets':
- exportAssets().finally(() => figma.closePlugin())
- break
- case 'export-components':
- exportComponents().finally(() => figma.closePlugin())
- break
+
+export function runCommand(ctx: typeof figma = figma) {
+ switch (ctx.command) {
+ case 'export-devup':
+ exportDevup('json').finally(() => ctx.closePlugin())
+ break
+ case 'export-devup-without-treeshaking':
+ exportDevup('json', false).finally(() => ctx.closePlugin())
+ break
+ case 'export-devup-excel':
+ exportDevup('excel').finally(() => ctx.closePlugin())
+ break
+ case 'export-devup-excel-without-treeshaking':
+ exportDevup('excel', false).finally(() => ctx.closePlugin())
+ break
+ case 'import-devup':
+ importDevup('json').finally(() => ctx.closePlugin())
+ break
+ case 'import-devup-excel':
+ importDevup('excel').finally(() => ctx.closePlugin())
+ break
+ case 'export-assets':
+ exportAssets().finally(() => ctx.closePlugin())
+ break
+ case 'export-components':
+ exportComponents().finally(() => ctx.closePlugin())
+ break
+ }
+}
+
+export function run(ctx: typeof figma) {
+ if (typeof ctx !== 'undefined') {
+ registerCodegen(ctx)
+ runCommand(ctx)
+ }
}
+
+run((globalThis as { figma?: unknown }).figma as typeof figma)
diff --git a/src/codegen/Codegen.ts b/src/codegen/Codegen.ts
index 473b117..c39732f 100644
--- a/src/codegen/Codegen.ts
+++ b/src/codegen/Codegen.ts
@@ -75,7 +75,7 @@ export class Codegen {
const assetNode = checkAssetNode(node)
if (assetNode) {
const props = await getProps(node)
- props.src = '/icons/' + node.name + '.' + assetNode
+ props.src = `/icons/${node.name}.${assetNode}`
if (assetNode === 'svg') {
const maskColor = await checkSameColor(node)
if (maskColor) {
diff --git a/src/codegen/__tests__/codegen.test.ts b/src/codegen/__tests__/codegen.test.ts
new file mode 100644
index 0000000..955259a
--- /dev/null
+++ b/src/codegen/__tests__/codegen.test.ts
@@ -0,0 +1,3366 @@
+import { afterAll, describe, expect, test } from 'bun:test'
+import { Codegen } from '../Codegen'
+
+;(globalThis as { figma?: unknown }).figma = {
+ mixed: Symbol('mixed'),
+ util: {
+ // * ```ts
+ // * const rgba = figma.util.rgba
+ // * const color = rgba('rgb(25% 25% 25% / 0.5)')
+ // * ```
+ // *
+ // * @param color - A CSS color string, `RGB` object, or `RGBA` object.
+ // */
+ // rgba(color: string | RGB | RGBA): RGBA
+ rgba: (color: string | RGB | RGBA): RGBA => {
+ if (typeof color === 'string') {
+ // κ°λ¨ν CSS color string νμ±
+ const rgbMatch = color.match(/rgb\(([^)]+)\)/)
+ if (rgbMatch) {
+ const values = rgbMatch[1].split(/[,\s/]+/).filter(Boolean)
+ const r = values[0]?.includes('%')
+ ? parseFloat(values[0]) / 100
+ : parseFloat(values[0] || '0') / 255
+ const g = values[1]?.includes('%')
+ ? parseFloat(values[1]) / 100
+ : parseFloat(values[1] || '0') / 255
+ const b = values[2]?.includes('%')
+ ? parseFloat(values[2]) / 100
+ : parseFloat(values[2] || '0') / 255
+ const a = values[3] ? parseFloat(values[3]) : 1
+ return { r, g, b, a }
+ }
+ return { r: 0, g: 0, b: 0, a: 1 }
+ }
+ if (typeof color === 'object') {
+ // RGB κ°μ²΄μΈ κ²½μ° alpha μΆκ°
+ if ('a' in color) {
+ return color
+ }
+ return { ...color, a: 1 }
+ }
+ return { r: 0, g: 0, b: 0, a: 1 }
+ },
+ },
+ getLocalTextStylesAsync: () => [
+ {
+ id: 'text-style-1',
+ name: 'Typography/Heading',
+ type: 'TEXT',
+ } as unknown as TextStyle,
+ ],
+ getStyleByIdAsync: async (id: string) => {
+ if (id === 'text-style-1') {
+ return {
+ id: 'text-style-1',
+ name: 'Typography/Heading',
+ type: 'TEXT',
+ } as unknown as TextStyle
+ }
+ return null
+ },
+ getNodeByIdAsync: async (id: string) => {
+ if (id === 'pattern-node-id') {
+ return {
+ type: 'VECTOR',
+ name: 'PatternIcon',
+ children: [],
+ isAsset: true,
+ } as unknown as SceneNode
+ }
+ return null
+ },
+ variables: {
+ getVariableByIdAsync: async (id: string) => {
+ if (id === 'var1') {
+ return {
+ name: 'Primary Color',
+ id: 'var1',
+ } as unknown as Variable
+ }
+ return null
+ },
+ },
+} as unknown as typeof figma
+afterAll(() => {
+
+ ;(globalThis as { figma?: unknown }).figma = undefined
+})
+
+function createTextSegment(characters: string): StyledTextSegment {
+ return {
+ characters,
+ textStyleId: 'style1',
+ fills: [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }],
+ start: 0,
+ end: characters.length,
+ fontSize: 16,
+ fontName: { family: 'Arial', style: 'Regular' },
+ fontWeight: 400,
+ lineHeight: { unit: 'PIXELS', value: 1.5 },
+ letterSpacing: { unit: 'PIXELS', value: 0 },
+ textDecoration: 'NONE',
+ textCase: 'ORIGINAL',
+ listOptions: {
+ type: 'NONE',
+ },
+ } as unknown as StyledTextSegment
+}
+
+function addParent(parent: SceneNode) {
+ if ('children' in parent) {
+ for (const child of parent.children) {
+ ;(child as unknown as { parent: SceneNode }).parent = parent
+ addParent(child)
+ }
+ }
+}
+
+describe('Codegen', () => {
+ type TestCase = {
+ title: string
+ node: SceneNode
+ expected: string
+ }
+
+ test.each([
+ {
+ title: 'renders simple component without variants',
+ node: {
+ type: 'FRAME',
+ name: 'Frame',
+ children: [],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders overflow hidden when clipsContent is true',
+ node: {
+ type: 'FRAME',
+ name: 'ClippedFrame',
+ children: [],
+ clipsContent: true,
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders objectFit contain for image asset',
+ node: {
+ type: 'RECTANGLE',
+ name: 'ObjectFitContain',
+ children: [],
+ isAsset: true,
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 80,
+ fills: [
+ {
+ type: 'IMAGE',
+ visible: true,
+ scaleMode: 'FIT',
+ },
+ ],
+ } as unknown as RectangleNode,
+ expected: ``,
+ },
+ {
+ title: 'renders objectFit cover for image asset',
+ node: {
+ type: 'RECTANGLE',
+ name: 'ObjectFitCover',
+ children: [],
+ isAsset: true,
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 120,
+ height: 90,
+ fills: [
+ {
+ type: 'IMAGE',
+ visible: true,
+ scaleMode: 'CROP',
+ },
+ ],
+ } as unknown as RectangleNode,
+ expected: ``,
+ },
+ {
+ title: 'omits objectFit when image scale mode is FILL',
+ node: {
+ type: 'RECTANGLE',
+ name: 'ObjectFitFill',
+ children: [],
+ isAsset: true,
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 110,
+ height: 70,
+ fills: [
+ {
+ type: 'IMAGE',
+ visible: true,
+ scaleMode: 'FILL',
+ },
+ ],
+ } as unknown as RectangleNode,
+ expected: ``,
+ },
+ {
+ title: 'renders svg asset with vector node',
+ node: {
+ type: 'VECTOR',
+ name: 'VectorIcon',
+ children: [],
+ isAsset: true,
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 24,
+ height: 24,
+ } as unknown as VectorNode,
+ expected: ``,
+ },
+ {
+ title: 'renders svg asset with star node',
+ node: {
+ type: 'STAR',
+ name: 'StarIcon',
+ children: [],
+ isAsset: true,
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 24,
+ height: 24,
+ } as unknown as StarNode,
+ expected: ``,
+ },
+ {
+ title: 'renders svg asset with polygon node',
+ node: {
+ type: 'POLYGON',
+ name: 'PolygonIcon',
+ children: [],
+ isAsset: true,
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 24,
+ height: 24,
+ } as unknown as PolygonNode,
+ expected: ``,
+ },
+ {
+ title: 'renders svg asset with ellipse inner radius',
+ node: {
+ type: 'ELLIPSE',
+ name: 'EllipseIcon',
+ children: [],
+ isAsset: true,
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 24,
+ height: 24,
+ arcData: {
+ innerRadius: 0.5,
+ },
+ } as unknown as EllipseNode,
+ expected: ``,
+ },
+ {
+ title: 'renders svg asset with non-solid fills',
+ node: {
+ type: 'RECTANGLE',
+ name: 'GradientAsset',
+ children: [],
+ isAsset: true,
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 24,
+ height: 24,
+ fills: [
+ {
+ type: 'GRADIENT_LINEAR',
+ visible: true,
+ opacity: 1,
+ gradientStops: [
+ { position: 0, color: { r: 1, g: 0, b: 0, a: 1 } },
+ { position: 1, color: { r: 0, g: 0, b: 1, a: 1 } },
+ ],
+ gradientTransform: [
+ [1, 0, 0],
+ [0, 1, 0],
+ ],
+ },
+ ],
+ } as unknown as RectangleNode,
+ expected: ``,
+ },
+ {
+ title: 'renders asset with single child and fills',
+ node: {
+ type: 'FRAME',
+ name: 'ParentAsset',
+ children: [
+ {
+ type: 'VECTOR',
+ name: 'ChildVector',
+ children: [],
+ isAsset: true,
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 24,
+ height: 24,
+ },
+ ],
+ isAsset: true,
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 24,
+ height: 24,
+ fills: [
+ {
+ type: 'SOLID',
+ visible: true,
+ color: { r: 1, g: 0, b: 0 },
+ opacity: 1,
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: `
+
+`,
+ },
+ {
+ title: 'renders null for asset with only solid fills',
+ node: {
+ type: 'RECTANGLE',
+ name: 'SolidAsset',
+ children: [],
+ isAsset: true,
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 24,
+ height: 24,
+ fills: [
+ {
+ type: 'SOLID',
+ visible: true,
+ color: { r: 1, g: 0, b: 0 },
+ opacity: 1,
+ },
+ ],
+ } as unknown as RectangleNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with bound variable color',
+ node: {
+ type: 'FRAME',
+ name: 'VariableFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ fills: [
+ {
+ type: 'SOLID',
+ visible: true,
+ color: { r: 1, g: 0, b: 0 },
+ opacity: 1,
+ boundVariables: {
+ color: {
+ id: 'var1',
+ type: 'COLOR',
+ },
+ },
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders svg asset with same color mask',
+ node: {
+ type: 'VECTOR',
+ name: 'MaskIcon',
+ children: [],
+ isAsset: true,
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 24,
+ height: 24,
+ fills: [
+ {
+ type: 'SOLID',
+ visible: true,
+ color: { r: 1, g: 0, b: 0 },
+ opacity: 1,
+ },
+ ],
+ } as unknown as VectorNode,
+ expected: ``,
+ },
+ {
+ title: 'renders svg asset with children same color',
+ node: {
+ type: 'FRAME',
+ name: 'GroupIcon',
+ children: [
+ {
+ type: 'VECTOR',
+ name: 'Vector1',
+ children: [],
+ visible: true,
+ fills: [
+ {
+ type: 'SOLID',
+ visible: true,
+ color: { r: 1, g: 0, b: 0 },
+ opacity: 1,
+ },
+ ],
+ },
+ {
+ type: 'VECTOR',
+ name: 'Vector2',
+ children: [],
+ visible: true,
+ fills: [
+ {
+ type: 'SOLID',
+ visible: true,
+ color: { r: 1, g: 0, b: 0 },
+ opacity: 1,
+ },
+ ],
+ },
+ ],
+ isAsset: true,
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 24,
+ height: 24,
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders svg asset with different color children',
+ node: {
+ type: 'FRAME',
+ name: 'MixedIcon',
+ children: [
+ {
+ type: 'VECTOR',
+ name: 'Vector1',
+ children: [],
+ visible: true,
+ fills: [
+ {
+ type: 'SOLID',
+ visible: true,
+ color: { r: 1, g: 0, b: 0 },
+ opacity: 1,
+ },
+ ],
+ },
+ {
+ type: 'VECTOR',
+ name: 'Vector2',
+ children: [],
+ visible: true,
+ fills: [
+ {
+ type: 'SOLID',
+ visible: true,
+ color: { r: 0, g: 0, b: 1 },
+ opacity: 1,
+ },
+ ],
+ },
+ ],
+ isAsset: true,
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 24,
+ height: 24,
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders svg asset with non-solid fill',
+ node: {
+ type: 'VECTOR',
+ name: 'GradientIcon',
+ children: [],
+ isAsset: true,
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 24,
+ height: 24,
+ fills: [
+ {
+ type: 'GRADIENT_LINEAR',
+ visible: true,
+ opacity: 1,
+ gradientStops: [
+ { position: 0, color: { r: 1, g: 0, b: 0, a: 1 } },
+ { position: 1, color: { r: 0, g: 0, b: 1, a: 1 } },
+ ],
+ gradientTransform: [
+ [1, 0, 0],
+ [0, 1, 0],
+ ],
+ },
+ ],
+ } as unknown as VectorNode,
+ expected: ``,
+ },
+ {
+ title: 'renders svg asset with invisible fill',
+ node: {
+ type: 'VECTOR',
+ name: 'InvisibleIcon',
+ children: [],
+ isAsset: true,
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 24,
+ height: 24,
+ fills: [
+ {
+ type: 'SOLID',
+ visible: false,
+ color: { r: 1, g: 0, b: 0 },
+ opacity: 1,
+ },
+ ],
+ } as unknown as VectorNode,
+ expected: ``,
+ },
+ {
+ title: 'renders layout for absolute child same size as parent',
+ node: {
+ type: 'FRAME',
+ name: 'Parent',
+ width: 300,
+ height: 200,
+ layoutPositioning: 'AUTO',
+ children: [
+ {
+ type: 'RECTANGLE',
+ name: 'AbsoluteChild',
+ layoutPositioning: 'ABSOLUTE',
+ width: 300,
+ height: 200,
+ constraints: {
+ horizontal: 'MAX',
+ vertical: 'MAX',
+ },
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: `
+
+`,
+ },
+ {
+ title: 'renders absolute child with horizontal MAX constraint',
+ node: {
+ type: 'FRAME',
+ name: 'ParentWithAutoLayout',
+ width: 400,
+ height: 300,
+ inferredAutoLayout: {
+ layoutMode: 'HORIZONTAL',
+ },
+ children: [
+ {
+ type: 'RECTANGLE',
+ name: 'MaxRightChild',
+ layoutPositioning: 'ABSOLUTE',
+ x: 350,
+ y: 50,
+ width: 50,
+ height: 100,
+ constraints: {
+ horizontal: 'MAX',
+ vertical: 'MIN',
+ },
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: `
+
+`,
+ },
+ {
+ title: 'renders absolute child with horizontal CENTER constraint',
+ node: {
+ type: 'FRAME',
+ name: 'ParentWithAutoLayout2',
+ width: 500,
+ height: 400,
+ inferredAutoLayout: {
+ layoutMode: 'VERTICAL',
+ },
+ children: [
+ {
+ type: 'RECTANGLE',
+ name: 'CenterChild',
+ layoutPositioning: 'ABSOLUTE',
+ x: 200,
+ y: 150,
+ width: 100,
+ height: 100,
+ constraints: {
+ horizontal: 'CENTER',
+ vertical: 'CENTER',
+ },
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: `
+
+`,
+ },
+ {
+ title: 'renders absolute child with vertical MAX constraint',
+ node: {
+ type: 'FRAME',
+ name: 'ParentWithAutoLayout3',
+ width: 600,
+ height: 500,
+ inferredAutoLayout: {
+ layoutMode: 'HORIZONTAL',
+ },
+ children: [
+ {
+ type: 'RECTANGLE',
+ name: 'MaxBottomChild',
+ layoutPositioning: 'ABSOLUTE',
+ x: 50,
+ y: 450,
+ width: 100,
+ height: 50,
+ constraints: {
+ horizontal: 'MIN',
+ vertical: 'MAX',
+ },
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: `
+
+`,
+ },
+ {
+ title: 'renders absolute child with vertical CENTER constraint',
+ node: {
+ type: 'FRAME',
+ name: 'ParentWithAutoLayout4',
+ width: 700,
+ height: 600,
+ inferredAutoLayout: {
+ layoutMode: 'VERTICAL',
+ },
+ children: [
+ {
+ type: 'RECTANGLE',
+ name: 'VerticalCenterChild',
+ layoutPositioning: 'ABSOLUTE',
+ x: 300,
+ y: 250,
+ width: 100,
+ height: 100,
+ constraints: {
+ horizontal: 'MIN',
+ vertical: 'CENTER',
+ },
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: `
+
+`,
+ },
+ {
+ title: 'renders flex=1 when parent is horizontal auto layout',
+ node: {
+ type: 'FRAME',
+ name: 'HorizontalParent',
+ layoutMode: 'HORIZONTAL',
+ children: [
+ {
+ type: 'RECTANGLE',
+ name: 'FlexChild',
+ layoutSizingHorizontal: 'FILL',
+ layoutSizingVertical: 'FIXED',
+ height: 50,
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: `
+
+`,
+ },
+ {
+ title: 'renders aspect ratio when provided',
+ node: {
+ type: 'FRAME',
+ name: 'AspectRatioFrame',
+ children: [],
+ targetAspectRatio: { x: 4, y: 3 },
+ layoutSizingHorizontal: 'FILL',
+ layoutSizingVertical: 'FILL',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title:
+ 'renders text with width_and_height auto resize returning no size props',
+ node: {
+ type: 'TEXT',
+ name: 'TextWidthHeight',
+ children: [],
+ textAutoResize: 'WIDTH_AND_HEIGHT',
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 120,
+ height: 40,
+ strokes: [],
+ effects: [],
+ getStyledTextSegments: () => [
+ {
+ ...createTextSegment('AutoSize'),
+ characters: 'AutoSize',
+ textStyleId: 'style1',
+ fills: [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }],
+ },
+ ],
+ textTruncation: 'DISABLED',
+ characters: 'AutoSize',
+ } as unknown as TextNode,
+ expected: `
+ AutoSize
+`,
+ },
+ {
+ title: 'renders text with special characters',
+ node: {
+ type: 'TEXT',
+ name: 'SpecialText',
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 20,
+ textAutoResize: 'NONE',
+ strokes: [],
+ effects: [],
+ getStyledTextSegments: () => [
+ {
+ ...createTextSegment('Text with {braces} & '),
+ characters: 'Text with {braces} & ',
+ textStyleId: 'style1',
+ fills: [{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } }],
+ },
+ ],
+ textTruncation: 'DISABLED',
+ characters: 'Text with {braces} & ',
+ } as unknown as TextNode,
+ expected: `
+ Text with {"{"}braces{"}"} {"&"} {"<"}tags{">"}
+`,
+ },
+ {
+ title: 'renders text with leading and trailing spaces',
+ node: {
+ type: 'TEXT',
+ name: 'SpacedText',
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 20,
+ textAutoResize: 'NONE',
+ strokes: [],
+ effects: [],
+ getStyledTextSegments: () => [
+ {
+ ...createTextSegment(' spaced '),
+ characters: ' spaced ',
+ textStyleId: 'style1',
+ fills: [{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } }],
+ },
+ ],
+ textTruncation: 'DISABLED',
+ characters: ' spaced ',
+ } as unknown as TextNode,
+ expected: `
+ {" "}spaced{" "}
+`,
+ },
+ {
+ title: 'renders single-line max line props',
+ node: {
+ type: 'TEXT',
+ name: 'MaxLineOne',
+ children: [],
+ textAutoResize: 'NONE',
+ strokes: [],
+ effects: [],
+ maxLines: 1,
+ getStyledTextSegments: () => [
+ {
+ ...createTextSegment('OneLine'),
+ characters: 'OneLine',
+ textStyleId: 'style1',
+ fills: [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }],
+ },
+ ],
+ textTruncation: 'DISABLED',
+ } as unknown as TextNode,
+ expected: `
+ OneLine
+`,
+ },
+ {
+ title: 'renders multi-line max line props',
+ node: {
+ type: 'TEXT',
+ name: 'MaxLineThree',
+ children: [],
+ textAutoResize: 'NONE',
+ strokes: [],
+ effects: [],
+ maxLines: 3,
+ getStyledTextSegments: () => [
+ {
+ ...createTextSegment('Three lines of text'),
+ characters: 'Three lines of text',
+ textStyleId: 'style1',
+ fills: [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }],
+ },
+ ],
+ textTruncation: 'DISABLED',
+ } as unknown as TextNode,
+ expected: `
+ Three lines of text
+`,
+ },
+ {
+ title: 'renders fixed size frame',
+ node: {
+ type: 'FRAME',
+ name: 'FixedFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 120,
+ height: 80,
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with padding',
+ node: {
+ type: 'FRAME',
+ name: 'PaddedFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ paddingTop: 8,
+ paddingRight: 16,
+ paddingBottom: 8,
+ paddingLeft: 16,
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders padding shorthand when all sides equal',
+ node: {
+ type: 'FRAME',
+ name: 'EqualPaddingFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 80,
+ height: 40,
+ paddingTop: 12,
+ paddingRight: 12,
+ paddingBottom: 12,
+ paddingLeft: 12,
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders padding with top equals bottom but left right different',
+ node: {
+ type: 'FRAME',
+ name: 'TopBottomEqualPaddingFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 60,
+ paddingTop: 10,
+ paddingRight: 20,
+ paddingBottom: 10,
+ paddingLeft: 30,
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders padding with left equals right but top bottom different',
+ node: {
+ type: 'FRAME',
+ name: 'LeftRightEqualPaddingFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 90,
+ height: 50,
+ paddingTop: 5,
+ paddingRight: 15,
+ paddingBottom: 25,
+ paddingLeft: 15,
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders padding with all sides different',
+ node: {
+ type: 'FRAME',
+ name: 'AllDifferentPaddingFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 110,
+ height: 70,
+ paddingTop: 1,
+ paddingRight: 2,
+ paddingBottom: 3,
+ paddingLeft: 4,
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with border radius',
+ node: {
+ type: 'FRAME',
+ name: 'RoundedFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 120,
+ height: 80,
+ cornerRadius: 8,
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders mixed border radius frame',
+ node: {
+ type: 'FRAME',
+ name: 'MixedRadius',
+ children: [],
+ topLeftRadius: 8,
+ topRightRadius: 4,
+ bottomRightRadius: 2,
+ bottomLeftRadius: 1,
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders ellipse with 50% border radius',
+ node: {
+ type: 'ELLIPSE',
+ name: 'Circle',
+ children: [],
+ arcData: {
+ innerRadius: 0,
+ },
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 100,
+ } as unknown as EllipseNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with stroke outline CENTER alignment',
+ node: {
+ type: 'FRAME',
+ name: 'StrokeCenterFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ strokes: [
+ {
+ type: 'SOLID',
+ color: { r: 0, g: 0, b: 1 },
+ opacity: 1,
+ visible: true,
+ },
+ ],
+ strokeWeight: 2,
+ strokeAlign: 'CENTER',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with stroke outline OUTSIDE alignment',
+ node: {
+ type: 'FRAME',
+ name: 'StrokeOutsideFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 120,
+ height: 60,
+ strokes: [
+ {
+ type: 'SOLID',
+ color: { r: 1, g: 0, b: 0 },
+ opacity: 1,
+ visible: true,
+ },
+ ],
+ strokeWeight: 3,
+ strokeAlign: 'OUTSIDE',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with stroke border INSIDE alignment',
+ node: {
+ type: 'FRAME',
+ name: 'StrokeInsideFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 80,
+ height: 40,
+ strokes: [
+ {
+ type: 'SOLID',
+ color: { r: 0, g: 1, b: 0 },
+ opacity: 1,
+ visible: true,
+ },
+ ],
+ strokeWeight: 1,
+ strokeAlign: 'INSIDE',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders line with stroke outline',
+ node: {
+ type: 'LINE',
+ name: 'StrokeLine',
+ strokes: [
+ {
+ type: 'SOLID',
+ color: { r: 1, g: 0, b: 0 },
+ opacity: 1,
+ visible: true,
+ },
+ ],
+ strokeWeight: 2,
+ strokeAlign: 'CENTER',
+ } as unknown as LineNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with dashed stroke',
+ node: {
+ type: 'FRAME',
+ name: 'DashedStrokeFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ strokes: [
+ {
+ type: 'SOLID',
+ color: { r: 0, g: 0, b: 1 },
+ opacity: 1,
+ visible: true,
+ },
+ ],
+ strokeWeight: 2,
+ strokeAlign: 'CENTER',
+ dashPattern: [5, 3],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with page parent and width 1920',
+ node: {
+ type: 'FRAME',
+ name: 'PageFrame',
+ children: [],
+ width: 1920,
+ height: 1080,
+ parent: {
+ type: 'PAGE',
+ name: 'Page 1',
+ },
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with section parent',
+ node: {
+ type: 'FRAME',
+ name: 'SectionFrame',
+ children: [],
+ width: 100,
+ height: 100,
+ parent: {
+ type: 'SECTION',
+ name: 'Section 1',
+ },
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with vertical center align child width shrinker',
+ node: {
+ type: 'FRAME',
+ name: 'Parent',
+ inferredAutoLayout: {
+ layoutMode: 'VERTICAL',
+ counterAxisAlignItems: 'CENTER',
+ },
+ children: [
+ {
+ type: 'FRAME',
+ name: 'Child',
+ children: [],
+ layoutSizingHorizontal: 'FILL',
+ layoutSizingVertical: 'FIXED',
+ height: 50,
+ parent: {
+ type: 'FRAME',
+ inferredAutoLayout: {
+ layoutMode: 'VERTICAL',
+ counterAxisAlignItems: 'CENTER',
+ },
+ },
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: `
+
+`,
+ },
+ {
+ title: 'renders frame with horizontal center align child height shrinker',
+ node: {
+ type: 'FRAME',
+ name: 'Parent',
+ inferredAutoLayout: {
+ layoutMode: 'HORIZONTAL',
+ counterAxisAlignItems: 'CENTER',
+ },
+ children: [
+ {
+ type: 'FRAME',
+ name: 'Child',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FILL',
+ width: 50,
+ parent: {
+ type: 'FRAME',
+ inferredAutoLayout: {
+ layoutMode: 'HORIZONTAL',
+ counterAxisAlignItems: 'CENTER',
+ },
+ },
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: `
+
+`,
+ },
+ {
+ title: 'renders frame with maxWidth child width shrinker',
+ node: {
+ type: 'FRAME',
+ name: 'Parent',
+ children: [
+ {
+ type: 'FRAME',
+ name: 'Child',
+ children: [],
+ layoutSizingHorizontal: 'FILL',
+ layoutSizingVertical: 'FIXED',
+ height: 50,
+ maxWidth: 200,
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: `
+
+`,
+ },
+ {
+ title: 'renders frame with maxHeight child height shrinker',
+ node: {
+ type: 'FRAME',
+ name: 'Parent',
+ children: [
+ {
+ type: 'FRAME',
+ name: 'Child',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FILL',
+ width: 50,
+ maxHeight: 200,
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: `
+
+`,
+ },
+ {
+ title: 'renders frame with individual stroke weights',
+ node: {
+ type: 'FRAME',
+ name: 'IndividualStrokeFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 120,
+ height: 80,
+ strokes: [
+ {
+ type: 'SOLID',
+ color: { r: 1, g: 0, b: 0 },
+ opacity: 1,
+ visible: true,
+ },
+ ],
+ strokeWeight: figma.mixed,
+ strokeTopWeight: 1,
+ strokeRightWeight: 2,
+ strokeBottomWeight: 3,
+ strokeLeftWeight: 4,
+ strokeAlign: 'INSIDE',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders group as Box with full size',
+ node: {
+ type: 'GROUP',
+ name: 'Group',
+ children: [],
+ } as unknown as GroupNode,
+ expected: ``,
+ },
+ {
+ title: 'renders boxSize when width equals height',
+ node: {
+ type: 'FRAME',
+ name: 'SquareFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 64,
+ height: 64,
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders min/max width/height with px',
+ node: {
+ type: 'FRAME',
+ name: 'MinMaxFrame',
+ children: [],
+ layoutSizingHorizontal: 'FILL',
+ layoutSizingVertical: 'FILL',
+ minWidth: 50,
+ minHeight: 20,
+ maxWidth: 200,
+ maxHeight: 120,
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders flex auto layout props',
+ node: {
+ type: 'FRAME',
+ name: 'FlexAutoLayout',
+ children: [],
+ inferredAutoLayout: {
+ layoutMode: 'HORIZONTAL',
+ itemSpacing: 10,
+ },
+ primaryAxisAlignItems: 'SPACE_BETWEEN',
+ counterAxisAlignItems: 'CENTER',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders grid auto layout props',
+ node: {
+ type: 'FRAME',
+ name: 'GridAutoLayout',
+ children: [],
+ inferredAutoLayout: {
+ layoutMode: 'GRID',
+ },
+ gridColumnCount: 3,
+ gridRowCount: 2,
+ gridColumnGap: 8,
+ gridRowGap: 12,
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders grid child positioning props when out of order',
+ node: {
+ type: 'FRAME',
+ name: 'GridWithChildren',
+ children: [
+ {
+ type: 'RECTANGLE',
+ name: 'ChildB',
+ visible: true,
+ gridColumnAnchorIndex: 1,
+ gridRowAnchorIndex: 0,
+ } as unknown as SceneNode,
+ {
+ type: 'RECTANGLE',
+ name: 'ChildA',
+ visible: true,
+ gridColumnAnchorIndex: 0,
+ gridRowAnchorIndex: 0,
+ } as unknown as SceneNode,
+ ],
+ inferredAutoLayout: {
+ layoutMode: 'GRID',
+ } as unknown as InferredAutoLayoutResult,
+ gridColumnCount: 2,
+ gridRowCount: 1,
+ gridColumnGap: 0,
+ gridRowGap: 0,
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 200,
+ height: 100,
+ } as unknown as FrameNode,
+ expected: `
+
+
+`,
+ },
+ {
+ title: 'renders vstack auto layout props',
+ node: {
+ type: 'FRAME',
+ name: 'VStackAutoLayout',
+ children: [
+ { visible: true } as unknown as SceneNode,
+ { visible: true } as unknown as SceneNode,
+ ],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 120,
+ height: 200,
+ inferredAutoLayout: {
+ layoutMode: 'VERTICAL',
+ itemSpacing: 16,
+ },
+ primaryAxisAlignItems: 'SPACE_BETWEEN',
+ counterAxisAlignItems: 'CENTER',
+ } as unknown as FrameNode,
+ expected: `
+
+
+`,
+ },
+ {
+ title: 'renders center auto layout props',
+ node: {
+ type: 'FRAME',
+ name: 'CenterAutoLayout',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 120,
+ height: 120,
+ inferredAutoLayout: {
+ layoutMode: 'HORIZONTAL',
+ itemSpacing: 0,
+ },
+ primaryAxisAlignItems: 'CENTER',
+ counterAxisAlignItems: 'CENTER',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders drop shadow effect props',
+ node: {
+ type: 'FRAME',
+ name: 'EffectFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 60,
+ effects: [
+ {
+ type: 'DROP_SHADOW',
+ offset: { x: 2, y: 4 },
+ radius: 6,
+ spread: 1,
+ color: { r: 1, g: 0, b: 0, a: 0.5 },
+ visible: true,
+ blendMode: 'NORMAL',
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders inner shadow effect props',
+ node: {
+ type: 'FRAME',
+ name: 'InnerShadowFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 90,
+ height: 70,
+ effects: [
+ {
+ type: 'INNER_SHADOW',
+ offset: { x: -2, y: 3 },
+ radius: 5,
+ spread: 0,
+ color: { r: 0, g: 1, b: 0, a: 1 },
+ visible: true,
+ blendMode: 'NORMAL',
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders layer blur effect props',
+ node: {
+ type: 'FRAME',
+ name: 'LayerBlurFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 120,
+ height: 80,
+ effects: [
+ {
+ type: 'LAYER_BLUR',
+ radius: 4,
+ visible: true,
+ blendMode: 'NORMAL',
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders background blur effect props',
+ node: {
+ type: 'FRAME',
+ name: 'BackdropBlurFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 110,
+ height: 90,
+ effects: [
+ {
+ type: 'BACKGROUND_BLUR',
+ radius: 8,
+ visible: true,
+ blendMode: 'NORMAL',
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders noise effect props',
+ node: {
+ type: 'FRAME',
+ name: 'NoiseEffectFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 140,
+ height: 70,
+ effects: [
+ {
+ type: 'NOISE',
+ radius: 0,
+ blendMode: 'NORMAL',
+ visible: true,
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders texture effect props',
+ node: {
+ type: 'FRAME',
+ name: 'TextureEffectFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 150,
+ height: 60,
+ effects: [
+ {
+ type: 'TEXTURE',
+ radius: 0,
+ blendMode: 'NORMAL',
+ visible: true,
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders glass effect props',
+ node: {
+ type: 'FRAME',
+ name: 'GlassEffectFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 160,
+ height: 80,
+ effects: [
+ {
+ type: 'GLASS',
+ radius: 12,
+ blendMode: 'NORMAL',
+ visible: true,
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders text node with content',
+ node: {
+ type: 'TEXT',
+ name: 'Text',
+ children: [],
+ textAutoResize: 'HEIGHT',
+ strokes: [],
+ effects: [],
+ getStyledTextSegments: () => [
+ {
+ ...createTextSegment('Hello'),
+ characters: 'Hello',
+ textStyleId: 'style1',
+ fills: [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }],
+ },
+ ],
+ textTruncation: 'DISABLED',
+ } as unknown as TextNode,
+ expected: `
+ Hello
+`,
+ },
+ {
+ title: 'renders another text node with content',
+ node: {
+ type: 'TEXT',
+ name: 'Text2',
+ children: [],
+ textAutoResize: 'HEIGHT',
+ strokes: [],
+ effects: [],
+ getStyledTextSegments: () => [
+ {
+ ...createTextSegment('World'),
+ characters: 'World',
+ textStyleId: 'style1',
+ fills: [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }],
+ },
+ ],
+ textTruncation: 'DISABLED',
+ } as unknown as TextNode,
+ expected: `
+ World
+`,
+ },
+ {
+ title: 'renders text node with ellipsis props',
+ node: {
+ type: 'TEXT',
+ name: 'EllipsisText',
+ children: [],
+ textAutoResize: 'HEIGHT',
+ strokes: [],
+ effects: [],
+ getStyledTextSegments: () => [
+ {
+ ...createTextSegment('Ellipsis'),
+ characters: 'Ellipsis',
+ textStyleId: 'style1',
+ fills: [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }],
+ },
+ ],
+ textTruncation: 'ENDING',
+ } as unknown as TextNode,
+ expected: `
+ Ellipsis
+`,
+ },
+ {
+ title: 'renders text node with single drop shadow',
+ node: {
+ type: 'TEXT',
+ name: 'TextWithShadow',
+ children: [],
+ textAutoResize: 'HEIGHT',
+ strokes: [],
+ effects: [
+ {
+ type: 'DROP_SHADOW',
+ offset: { x: 2, y: 4 },
+ radius: 6,
+ color: { r: 0, g: 0, b: 0, a: 0.5 },
+ visible: true,
+ blendMode: 'NORMAL',
+ },
+ ],
+ getStyledTextSegments: () => [
+ {
+ ...createTextSegment('Shadow'),
+ characters: 'Shadow',
+ textStyleId: 'style1',
+ fills: [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }],
+ },
+ ],
+ textTruncation: 'DISABLED',
+ } as unknown as TextNode,
+ expected: `
+ Shadow
+`,
+ },
+ {
+ title: 'renders text node with multiple drop shadows',
+ node: {
+ type: 'TEXT',
+ name: 'TextWithMultipleShadows',
+ children: [],
+ textAutoResize: 'HEIGHT',
+ strokes: [],
+ effects: [
+ {
+ type: 'DROP_SHADOW',
+ offset: { x: 1, y: 2 },
+ radius: 3,
+ color: { r: 1, g: 0, b: 0, a: 1 },
+ visible: true,
+ blendMode: 'NORMAL',
+ },
+ {
+ type: 'DROP_SHADOW',
+ offset: { x: 4, y: 5 },
+ radius: 6,
+ color: { r: 0, g: 1, b: 0, a: 0.8 },
+ visible: true,
+ blendMode: 'NORMAL',
+ },
+ ],
+ getStyledTextSegments: () => [
+ {
+ ...createTextSegment('MultiShadow'),
+ characters: 'MultiShadow',
+ textStyleId: 'style1',
+ fills: [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }],
+ },
+ ],
+ textTruncation: 'DISABLED',
+ } as unknown as TextNode,
+ expected: `
+ MultiShadow
+`,
+ },
+ {
+ title: 'renders text node with stroke',
+ node: {
+ type: 'TEXT',
+ name: 'TextWithStroke',
+ children: [],
+ textAutoResize: 'HEIGHT',
+ strokes: [
+ {
+ type: 'SOLID',
+ color: { r: 0, g: 0, b: 1 },
+ opacity: 1,
+ visible: true,
+ },
+ ],
+ strokeWeight: 2,
+ effects: [],
+ getStyledTextSegments: () => [
+ {
+ ...createTextSegment('Stroke'),
+ characters: 'Stroke',
+ textStyleId: 'style1',
+ fills: [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }],
+ },
+ ],
+ textTruncation: 'DISABLED',
+ } as unknown as TextNode,
+ expected: `
+ Stroke
+`,
+ },
+ {
+ title: 'renders text node with multiple segments',
+ node: {
+ type: 'TEXT',
+ name: 'MultiSegmentText',
+ children: [],
+ textAutoResize: 'HEIGHT',
+ strokes: [],
+ effects: [],
+ getStyledTextSegments: () => [
+ {
+ ...createTextSegment('Hello'),
+ characters: 'Hello',
+ textStyleId: 'style1',
+ fills: [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }],
+ },
+ {
+ ...createTextSegment('World'),
+ characters: 'World',
+ textStyleId: 'style2',
+ fontSize: 20,
+ fills: [{ type: 'SOLID', color: { r: 0, g: 0, b: 1 } }],
+ },
+ ],
+ textTruncation: 'DISABLED',
+ } as unknown as TextNode,
+ expected: `
+
+ Hello
+
+ World
+`,
+ },
+ {
+ title: 'renders text node with unordered list',
+ node: {
+ type: 'TEXT',
+ name: 'UnorderedListText',
+ children: [],
+ textAutoResize: 'HEIGHT',
+ strokes: [],
+ effects: [],
+ getStyledTextSegments: () => [
+ {
+ ...createTextSegment('Item 1'),
+ characters: 'Item 1',
+ textStyleId: 'style1',
+ fills: [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }],
+ listOptions: {
+ type: 'UNORDERED',
+ },
+ },
+ ],
+ textTruncation: 'DISABLED',
+ } as unknown as TextNode,
+ expected: `
+
+ Item 1
+
+`,
+ },
+ {
+ title: 'renders text node with ordered list',
+ node: {
+ type: 'TEXT',
+ name: 'OrderedListText',
+ children: [],
+ textAutoResize: 'HEIGHT',
+ strokes: [],
+ effects: [],
+ getStyledTextSegments: () => [
+ {
+ ...createTextSegment('Item 1'),
+ characters: 'Item 1',
+ textStyleId: 'style1',
+ fills: [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }],
+ listOptions: {
+ type: 'ORDERED',
+ },
+ },
+ ],
+ textTruncation: 'DISABLED',
+ } as unknown as TextNode,
+ expected: `
+
+ Item 1
+
+`,
+ },
+ {
+ title: 'renders text node with list and multiple lines',
+ node: {
+ type: 'TEXT',
+ name: 'MultiLineListText',
+ children: [],
+ textAutoResize: 'HEIGHT',
+ strokes: [],
+ effects: [],
+ getStyledTextSegments: () => [
+ {
+ ...createTextSegment('Item 1\nItem 2'),
+ characters: 'Item 1\nItem 2',
+ textStyleId: 'style1',
+ fills: [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }],
+ listOptions: {
+ type: 'UNORDERED',
+ },
+ },
+ ],
+ textTruncation: 'DISABLED',
+ } as unknown as TextNode,
+ expected: `
+
+ Item 1
+
+
+ Item 2
+
+`,
+ },
+ {
+ title: 'renders text node with multiple segments without props',
+ node: {
+ type: 'TEXT',
+ name: 'MultiSegmentNoPropsText',
+ children: [],
+ textAutoResize: 'HEIGHT',
+ strokes: [],
+ effects: [],
+ getStyledTextSegments: () => [
+ {
+ ...createTextSegment('Hello'),
+ characters: 'Hello',
+ textStyleId: 'style1',
+ fills: [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }],
+ },
+ {
+ ...createTextSegment('World'),
+ characters: 'World',
+ textStyleId: 'style1',
+ fills: [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }],
+ },
+ ],
+ textTruncation: 'DISABLED',
+ } as unknown as TextNode,
+ expected: `
+ Hello
+ World
+`,
+ },
+ {
+ title: 'renders frame with rotation transform',
+ node: {
+ type: 'FRAME',
+ name: 'RotatedFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ rotation: 45,
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with negative rotation transform',
+ node: {
+ type: 'FRAME',
+ name: 'NegativeRotatedFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 80,
+ height: 40,
+ rotation: -30,
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with decimal rotation transform',
+ node: {
+ type: 'FRAME',
+ name: 'DecimalRotatedFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 120,
+ height: 60,
+ rotation: 15.5,
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with opacity less than 1',
+ node: {
+ type: 'FRAME',
+ name: 'OpacityFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ opacity: 0.5,
+ blendMode: 'NORMAL',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with darken blend mode',
+ node: {
+ type: 'FRAME',
+ name: 'DarkenBlendFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ opacity: 1,
+ blendMode: 'DARKEN',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with multiply blend mode',
+ node: {
+ type: 'FRAME',
+ name: 'MultiplyBlendFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ opacity: 1,
+ blendMode: 'MULTIPLY',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with screen blend mode',
+ node: {
+ type: 'FRAME',
+ name: 'ScreenBlendFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ opacity: 1,
+ blendMode: 'SCREEN',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with overlay blend mode',
+ node: {
+ type: 'FRAME',
+ name: 'OverlayBlendFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ opacity: 1,
+ blendMode: 'OVERLAY',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with linear burn blend mode',
+ node: {
+ type: 'FRAME',
+ name: 'LinearBurnBlendFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ opacity: 1,
+ blendMode: 'LINEAR_BURN',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with color burn blend mode',
+ node: {
+ type: 'FRAME',
+ name: 'ColorBurnBlendFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ opacity: 1,
+ blendMode: 'COLOR_BURN',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with lighten blend mode',
+ node: {
+ type: 'FRAME',
+ name: 'LightenBlendFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ opacity: 1,
+ blendMode: 'LIGHTEN',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with linear dodge blend mode',
+ node: {
+ type: 'FRAME',
+ name: 'LinearDodgeBlendFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ opacity: 1,
+ blendMode: 'LINEAR_DODGE',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with color dodge blend mode',
+ node: {
+ type: 'FRAME',
+ name: 'ColorDodgeBlendFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ opacity: 1,
+ blendMode: 'COLOR_DODGE',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with soft light blend mode',
+ node: {
+ type: 'FRAME',
+ name: 'SoftLightBlendFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ opacity: 1,
+ blendMode: 'SOFT_LIGHT',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with hard light blend mode',
+ node: {
+ type: 'FRAME',
+ name: 'HardLightBlendFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ opacity: 1,
+ blendMode: 'HARD_LIGHT',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with difference blend mode',
+ node: {
+ type: 'FRAME',
+ name: 'DifferenceBlendFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ opacity: 1,
+ blendMode: 'DIFFERENCE',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with exclusion blend mode',
+ node: {
+ type: 'FRAME',
+ name: 'ExclusionBlendFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ opacity: 1,
+ blendMode: 'EXCLUSION',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with hue blend mode',
+ node: {
+ type: 'FRAME',
+ name: 'HueBlendFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ opacity: 1,
+ blendMode: 'HUE',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with saturation blend mode',
+ node: {
+ type: 'FRAME',
+ name: 'SaturationBlendFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ opacity: 1,
+ blendMode: 'SATURATION',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with color blend mode',
+ node: {
+ type: 'FRAME',
+ name: 'ColorBlendFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ opacity: 1,
+ blendMode: 'COLOR',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with luminosity blend mode',
+ node: {
+ type: 'FRAME',
+ name: 'LuminosityBlendFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ opacity: 1,
+ blendMode: 'LUMINOSITY',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with pass through blend mode',
+ node: {
+ type: 'FRAME',
+ name: 'PassThroughBlendFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ opacity: 1,
+ blendMode: 'PASS_THROUGH',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with opacity and blend mode',
+ node: {
+ type: 'FRAME',
+ name: 'OpacityBlendFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ opacity: 0.75,
+ blendMode: 'MULTIPLY',
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with solid fill and blend mode',
+ node: {
+ type: 'FRAME',
+ name: 'SolidBlendFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ fills: [
+ {
+ type: 'SOLID',
+ color: { r: 1, g: 0, b: 0 },
+ opacity: 1,
+ visible: true,
+ blendMode: 'MULTIPLY',
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders text node with gradient fill',
+ node: {
+ type: 'TEXT',
+ name: 'GradientText',
+ children: [],
+ textAutoResize: 'HEIGHT',
+ strokes: [],
+ effects: [],
+ width: 100,
+ height: 50,
+ fills: [
+ {
+ type: 'GRADIENT_LINEAR',
+ visible: true,
+ opacity: 1,
+ gradientStops: [
+ { position: 0, color: { r: 1, g: 0, b: 0, a: 1 } },
+ { position: 1, color: { r: 0, g: 0, b: 1, a: 1 } },
+ ],
+ gradientTransform: [
+ [1, 0, 0],
+ [0, 1, 0],
+ ],
+ },
+ ],
+ getStyledTextSegments: () => [
+ {
+ ...createTextSegment('Gradient'),
+ characters: 'Gradient',
+ textStyleId: 'style1',
+ fills: [
+ {
+ type: 'GRADIENT_LINEAR',
+ visible: true,
+ opacity: 1,
+ gradientStops: [
+ { position: 0, color: { r: 1, g: 0, b: 0, a: 1 } },
+ { position: 1, color: { r: 0, g: 0, b: 1, a: 1 } },
+ ],
+ gradientTransform: [
+ [1, 0, 0],
+ [0, 1, 0],
+ ],
+ },
+ ],
+ },
+ ],
+ textTruncation: 'DISABLED',
+ } as unknown as TextNode,
+ expected: `
+ Gradient
+`,
+ },
+ {
+ title: 'renders text node with HEIGHT auto resize and fixed sizing',
+ node: {
+ type: 'TEXT',
+ name: 'TextHeightFixed',
+ children: [],
+ textAutoResize: 'HEIGHT',
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ strokes: [],
+ effects: [],
+ getStyledTextSegments: () => [
+ {
+ ...createTextSegment('Height'),
+ characters: 'Height',
+ textStyleId: 'style1',
+ fills: [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }],
+ },
+ ],
+ textTruncation: 'DISABLED',
+ } as unknown as TextNode,
+ expected: `
+ Height
+`,
+ },
+ {
+ title: 'renders text node with NONE auto resize and fixed sizing',
+ node: {
+ type: 'TEXT',
+ name: 'TextNoneFixed',
+ children: [],
+ textAutoResize: 'NONE',
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ strokes: [],
+ effects: [],
+ getStyledTextSegments: () => [
+ {
+ ...createTextSegment('None'),
+ characters: 'None',
+ textStyleId: 'style1',
+ fills: [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }],
+ },
+ ],
+ textTruncation: 'DISABLED',
+ } as unknown as TextNode,
+ expected: `
+ None
+`,
+ },
+ {
+ title: 'renders text node with TRUNCATE auto resize and fixed sizing',
+ node: {
+ type: 'TEXT',
+ name: 'TextTruncateFixed',
+ children: [],
+ textAutoResize: 'TRUNCATE',
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ strokes: [],
+ effects: [],
+ getStyledTextSegments: () => [
+ {
+ ...createTextSegment('Truncate'),
+ characters: 'Truncate',
+ textStyleId: 'style1',
+ fills: [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }],
+ },
+ ],
+ textTruncation: 'DISABLED',
+ } as unknown as TextNode,
+ expected: `
+ Truncate
+`,
+ },
+ {
+ title: 'renders text node with textStyleId and typography',
+ node: {
+ type: 'TEXT',
+ name: 'TypographyText',
+ children: [],
+ textAutoResize: 'HEIGHT',
+ strokes: [],
+ effects: [],
+ getStyledTextSegments: () => [
+ {
+ ...createTextSegment('Heading'),
+ characters: 'Heading',
+ textStyleId: 'text-style-1',
+ fills: [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }],
+ },
+ ],
+ textTruncation: 'DISABLED',
+ } as unknown as TextNode,
+ expected: `
+ Heading
+`,
+ },
+ {
+ title: 'renders frame with linear gradient fill',
+ node: {
+ type: 'FRAME',
+ name: 'LinearGradientFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ fills: [
+ {
+ type: 'GRADIENT_LINEAR',
+ visible: true,
+ opacity: 1,
+ gradientStops: [
+ { position: 0, color: { r: 1, g: 0, b: 0, a: 1 } },
+ { position: 1, color: { r: 0, g: 0, b: 1, a: 1 } },
+ ],
+ gradientTransform: [
+ [1, 0, 0],
+ [0, 1, 0],
+ ],
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with radial gradient fill',
+ node: {
+ type: 'FRAME',
+ name: 'RadialGradientFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 100,
+ fills: [
+ {
+ type: 'GRADIENT_RADIAL',
+ visible: true,
+ opacity: 1,
+ gradientStops: [
+ { position: 0, color: { r: 1, g: 0, b: 0, a: 1 } },
+ { position: 1, color: { r: 0, g: 0, b: 1, a: 1 } },
+ ],
+ gradientTransform: [
+ [1, 0, 0],
+ [0, 1, 0],
+ ],
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with angular gradient fill',
+ node: {
+ type: 'FRAME',
+ name: 'AngularGradientFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 100,
+ fills: [
+ {
+ type: 'GRADIENT_ANGULAR',
+ visible: true,
+ opacity: 1,
+ gradientStops: [
+ { position: 0, color: { r: 1, g: 0, b: 0, a: 1 } },
+ { position: 0.5, color: { r: 0, g: 1, b: 0, a: 1 } },
+ { position: 1, color: { r: 0, g: 0, b: 1, a: 1 } },
+ ],
+ gradientTransform: [
+ [1, 0, 0],
+ [0, 1, 0],
+ ],
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with diamond gradient fill',
+ node: {
+ type: 'FRAME',
+ name: 'DiamondGradientFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 100,
+ fills: [
+ {
+ type: 'GRADIENT_DIAMOND',
+ visible: true,
+ opacity: 1,
+ gradientStops: [
+ { position: 0, color: { r: 1, g: 0, b: 0, a: 1 } },
+ { position: 1, color: { r: 0, g: 0, b: 1, a: 1 } },
+ ],
+ gradientTransform: [
+ [1, 0, 0],
+ [0, 1, 0],
+ ],
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with image fill TILE scaleMode',
+ node: {
+ type: 'FRAME',
+ name: 'TileImageFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ fills: [
+ {
+ type: 'IMAGE',
+ visible: true,
+ opacity: 1,
+ scaleMode: 'TILE',
+ imageHash: 'hash123',
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with pattern fill',
+ node: {
+ type: 'FRAME',
+ name: 'PatternFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ fills: [
+ {
+ type: 'PATTERN',
+ visible: true,
+ opacity: 1,
+ sourceNodeId: 'pattern-node-id',
+ spacing: { x: 0.1, y: 0.2 },
+ horizontalAlignment: 'CENTER',
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with pattern fill START alignment',
+ node: {
+ type: 'FRAME',
+ name: 'PatternStartFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ fills: [
+ {
+ type: 'PATTERN',
+ visible: true,
+ opacity: 1,
+ sourceNodeId: 'pattern-node-id',
+ spacing: { x: 0, y: 0 },
+ horizontalAlignment: 'START',
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with pattern fill vertical alignment',
+ node: {
+ type: 'FRAME',
+ name: 'PatternVerticalFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ fills: [
+ {
+ type: 'PATTERN',
+ visible: true,
+ opacity: 1,
+ sourceNodeId: 'pattern-node-id',
+ spacing: { x: 0.1, y: 0.2 },
+ horizontalAlignment: 'CENTER',
+ verticalAlignment: 'END',
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with multiple fills including non-last solid',
+ node: {
+ type: 'FRAME',
+ name: 'MultipleFillsFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ fills: [
+ {
+ type: 'SOLID',
+ visible: true,
+ color: { r: 0, g: 1, b: 0 },
+ opacity: 1,
+ },
+ {
+ type: 'SOLID',
+ visible: true,
+ color: { r: 1, g: 0, b: 0 },
+ opacity: 1,
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with invisible gradient fill',
+ node: {
+ type: 'FRAME',
+ name: 'InvisibleGradientFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ fills: [
+ {
+ type: 'GRADIENT_LINEAR',
+ visible: false,
+ opacity: 1,
+ gradientStops: [
+ { position: 0, color: { r: 1, g: 0, b: 0, a: 1 } },
+ { position: 1, color: { r: 0, g: 0, b: 1, a: 1 } },
+ ],
+ gradientTransform: [
+ [1, 0, 0],
+ [0, 1, 0],
+ ],
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with zero opacity gradient fill',
+ node: {
+ type: 'FRAME',
+ name: 'ZeroOpacityGradientFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ fills: [
+ {
+ type: 'GRADIENT_RADIAL',
+ visible: true,
+ opacity: 0,
+ gradientStops: [
+ { position: 0, color: { r: 1, g: 0, b: 0, a: 1 } },
+ { position: 1, color: { r: 0, g: 0, b: 1, a: 1 } },
+ ],
+ gradientTransform: [
+ [1, 0, 0],
+ [0, 1, 0],
+ ],
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ {
+ title: 'renders frame with solid fill opacity 0',
+ node: {
+ type: 'FRAME',
+ name: 'SolidOpacityZeroFrame',
+ children: [],
+ layoutSizingHorizontal: 'FIXED',
+ layoutSizingVertical: 'FIXED',
+ width: 100,
+ height: 50,
+ fills: [
+ {
+ type: 'SOLID',
+ visible: true,
+ color: { r: 1, g: 0, b: 0 },
+ opacity: 0,
+ },
+ ],
+ } as unknown as FrameNode,
+ expected: ``,
+ },
+ ])('$title', async ({ node, expected }) => {
+ addParent(node)
+ const codegen = new Codegen(node)
+ await codegen.run()
+ expect(codegen.getCode()).toBe(expected)
+ })
+
+ type ComponentTestCase = {
+ title: string
+ node: SceneNode
+ expected: string
+ expectedComponents: Array<[string, string]>
+ }
+
+ test.each([
+ {
+ title: 'renders component set with variants',
+ node: (() => {
+ const defaultVariant = {
+ type: 'COMPONENT',
+ name: 'Default',
+ children: [],
+ variantProperties: { state: 'default' },
+ reactions: [],
+ } as unknown as ComponentNode
+ const hoverVariant = {
+ type: 'COMPONENT',
+ name: 'Hover',
+ children: [],
+ variantProperties: { state: 'hover' },
+ reactions: [
+ {
+ trigger: { type: 'ON_HOVER' },
+ actions: [],
+ },
+ ],
+ } as unknown as ComponentNode
+ return {
+ type: 'COMPONENT_SET',
+ name: 'Button',
+ children: [defaultVariant, hoverVariant],
+ defaultVariant,
+ componentPropertyDefinitions: {
+ state: {
+ type: 'VARIANT',
+ variantOptions: ['default', 'hover'],
+ },
+ },
+ } as unknown as ComponentSetNode
+ })(),
+ expected: `
+
+
+`,
+ expectedComponents: [
+ [
+ 'Button',
+ `export interface ButtonProps {
+ state: default | hover
+}
+
+export function Button() {
+ return
+ }`,
+ ],
+ ],
+ },
+ {
+ title: 'renders component set with effect property',
+ node: (() => {
+ const defaultVariant = {
+ type: 'COMPONENT',
+ name: 'Default',
+ children: [],
+ variantProperties: { effect: 'default' },
+ reactions: [],
+ } as unknown as ComponentNode
+ const hoverVariant = {
+ type: 'COMPONENT',
+ name: 'Hover',
+ children: [],
+ variantProperties: { effect: 'hover' },
+ reactions: [],
+ } as unknown as ComponentNode
+ return {
+ type: 'COMPONENT_SET',
+ name: 'Button',
+ children: [defaultVariant, hoverVariant],
+ defaultVariant,
+ componentPropertyDefinitions: {
+ effect: {
+ type: 'VARIANT',
+ variantOptions: ['default', 'hover'],
+ },
+ },
+ } as unknown as ComponentSetNode
+ })(),
+ expected: `
+
+
+`,
+ expectedComponents: [
+ [
+ 'Button',
+ `export function Button() {
+ return
+ }`,
+ ],
+ ],
+ },
+ {
+ title: 'renders component set with transition',
+ node: (() => {
+ const defaultVariant = {
+ type: 'COMPONENT',
+ name: 'Default',
+ children: [],
+ variantProperties: {},
+ reactions: [
+ {
+ trigger: { type: 'ON_HOVER' },
+ actions: [
+ {
+ type: 'NODE',
+ transition: {
+ type: 'SMART_ANIMATE',
+ duration: 0.3,
+ easing: { type: 'EASE_IN_OUT' },
+ },
+ },
+ ],
+ },
+ ],
+ } as unknown as ComponentNode
+ const hoverVariant = {
+ type: 'COMPONENT',
+ name: 'Hover',
+ children: [],
+ variantProperties: {},
+ reactions: [
+ {
+ trigger: { type: 'ON_HOVER' },
+ actions: [
+ {
+ type: 'NODE',
+ transition: {
+ type: 'SMART_ANIMATE',
+ duration: 0.3,
+ easing: { type: 'EASE_IN_OUT' },
+ },
+ },
+ ],
+ },
+ ],
+ } as unknown as ComponentNode
+ return {
+ type: 'COMPONENT_SET',
+ name: 'Button',
+ children: [defaultVariant, hoverVariant],
+ defaultVariant,
+ componentPropertyDefinitions: {},
+ } as unknown as ComponentSetNode
+ })(),
+ expected: `
+
+
+`,
+ expectedComponents: [
+ [
+ 'Button',
+ `export function Button() {
+ return
+ }`,
+ ],
+ ],
+ },
+ {
+ title: 'renders component set with different props and transition',
+ node: (() => {
+ const defaultVariant = {
+ type: 'COMPONENT',
+ name: 'Default',
+ children: [],
+ variantProperties: {},
+ reactions: [
+ {
+ trigger: { type: 'ON_HOVER' },
+ actions: [
+ {
+ type: 'NODE',
+ transition: {
+ type: 'SMART_ANIMATE',
+ duration: 0.3,
+ easing: { type: 'EASE_IN_OUT' },
+ },
+ },
+ ],
+ },
+ ],
+ } as unknown as ComponentNode
+ const hoverVariant = {
+ type: 'COMPONENT',
+ name: 'Hover',
+ children: [],
+ variantProperties: {},
+ opacity: 0.8,
+ reactions: [
+ {
+ trigger: { type: 'ON_HOVER' },
+ actions: [
+ {
+ type: 'NODE',
+ transition: {
+ type: 'SMART_ANIMATE',
+ duration: 0.3,
+ easing: { type: 'EASE_IN_OUT' },
+ },
+ },
+ ],
+ },
+ ],
+ } as unknown as ComponentNode
+ return {
+ type: 'COMPONENT_SET',
+ name: 'Card',
+ children: [defaultVariant, hoverVariant],
+ defaultVariant,
+ componentPropertyDefinitions: {},
+ } as unknown as ComponentSetNode
+ })(),
+ expected: `
+
+
+`,
+ expectedComponents: [
+ [
+ 'Card',
+ `export function Card() {
+ return (
+
+ )
+ }`,
+ ],
+ ],
+ },
+ {
+ title: 'renders component with parent component set',
+ node: {
+ type: 'COMPONENT',
+ name: 'Hover',
+ parent: {
+ type: 'COMPONENT_SET',
+ name: 'Button',
+ children: [
+ {
+ type: 'COMPONENT',
+ name: 'Default',
+ children: [],
+ variantProperties: { state: 'default' },
+ reactions: [],
+ },
+ {
+ type: 'COMPONENT',
+ name: 'Hover',
+ children: [],
+ variantProperties: { state: 'hover' },
+ reactions: [
+ {
+ trigger: { type: 'ON_HOVER' },
+ actions: [],
+ },
+ ],
+ },
+ ],
+ defaultVariant: {
+ type: 'COMPONENT',
+ name: 'Default',
+ children: [],
+ variantProperties: { state: 'default' },
+ reactions: [],
+ },
+ componentPropertyDefinitions: {
+ state: {
+ type: 'VARIANT',
+ variantOptions: ['default', 'hover'],
+ },
+ },
+ },
+ children: [],
+ variantProperties: { state: 'hover' },
+ reactions: [
+ {
+ trigger: { type: 'ON_HOVER' },
+ actions: [],
+ },
+ ],
+ } as unknown as ComponentNode,
+ expected: `
+
+
+`,
+ expectedComponents: [],
+ },
+ {
+ title: 'renders component set with press trigger',
+ node: (() => {
+ const defaultVariant = {
+ type: 'COMPONENT',
+ name: 'Default',
+ children: [],
+ variantProperties: {},
+ reactions: [],
+ } as unknown as ComponentNode
+ const activeVariant = {
+ type: 'COMPONENT',
+ name: 'Active',
+ children: [],
+ variantProperties: {},
+ reactions: [
+ {
+ trigger: { type: 'ON_PRESS' },
+ actions: [],
+ },
+ ],
+ } as unknown as ComponentNode
+ return {
+ type: 'COMPONENT_SET',
+ name: 'Button',
+ children: [defaultVariant, activeVariant],
+ defaultVariant,
+ componentPropertyDefinitions: {},
+ } as unknown as ComponentSetNode
+ })(),
+ expected: `
+
+
+`,
+ expectedComponents: [
+ [
+ 'Button',
+ `export function Button() {
+ return
+ }`,
+ ],
+ ],
+ },
+ {
+ title: 'renders simple component without variants',
+ node: {
+ type: 'COMPONENT',
+ name: 'Icon',
+ children: [],
+ } as unknown as ComponentNode,
+ expected: ``,
+ expectedComponents: [
+ [
+ 'Icon',
+ `export function Icon() {
+ return
+ }`,
+ ],
+ ],
+ },
+ {
+ title: 'renders component with parent component set name',
+ node: {
+ type: 'COMPONENT',
+ name: 'Hover',
+ parent: {
+ type: 'COMPONENT_SET',
+ name: 'Button',
+ children: [],
+ },
+ children: [],
+ } as unknown as ComponentNode,
+ expected: ``,
+ expectedComponents: [],
+ },
+ ])('$title', async ({ node, expected, expectedComponents }) => {
+ addParent(node)
+ const codegen = new Codegen(node)
+ await codegen.run()
+ const componentsCodes = codegen.getComponentsCodes()
+ expect(codegen.getCode()).toBe(expected)
+ expect(componentsCodes).toEqual(expectedComponents)
+ })
+})
diff --git a/src/codegen/__tests__/render.test.ts b/src/codegen/__tests__/render.test.ts
new file mode 100644
index 0000000..a18450b
--- /dev/null
+++ b/src/codegen/__tests__/render.test.ts
@@ -0,0 +1,74 @@
+import { describe, expect, test } from 'bun:test'
+import { renderComponent, renderNode } from '../render'
+
+describe('renderNode', () => {
+ test.each([
+ {
+ title: 'removes component specific defaults for Flex',
+ component: 'Flex',
+ props: { display: 'flex', gap: '8px' },
+ deps: 0,
+ children: [] as string[],
+ expected: '',
+ },
+ {
+ title: 'drops default props and component filtered props',
+ component: 'Center',
+ props: {
+ alignItems: 'flex-start',
+ justifyContent: 'flex-start',
+ display: 'flex',
+ },
+ deps: 0,
+ children: [] as string[],
+ expected: '',
+ },
+ {
+ title: 'indents nested children',
+ component: 'Box',
+ props: { p: '8px' },
+ deps: 1,
+ children: [''] as string[],
+ expected: ' \n \n ',
+ },
+ ])('$title', ({ component, props, deps, children, expected }) => {
+ const result = renderNode(component, props, deps, children)
+ expect(result).toBe(expected)
+ })
+})
+
+describe('renderComponent', () => {
+ test.each([
+ {
+ title: 'renders simple component without variants',
+ component: 'Button',
+ code: '',
+ variants: {} as Record,
+ expected: `export function Button() {
+ return
+ }`,
+ },
+ {
+ title: 'renders component with variants and multiline code',
+ component: 'Banner',
+ code: `
+
+`,
+ variants: { size: '"sm" | "lg"' },
+ expected: `export interface BannerProps {
+ size: "sm" | "lg"
+}
+
+export function Banner() {
+ return (
+
+
+
+ )
+ }`,
+ },
+ ])('$title', ({ component, code, variants, expected }) => {
+ const result = renderComponent(component, code, variants)
+ expect(result).toBe(expected)
+ })
+})
diff --git a/src/codegen/props/auto-layout.ts b/src/codegen/props/auto-layout.ts
index a3d1e18..b41321d 100644
--- a/src/codegen/props/auto-layout.ts
+++ b/src/codegen/props/auto-layout.ts
@@ -33,17 +33,6 @@ export function getAutoLayoutProps(
function getJustifyContent(
node: SceneNode & BaseFrameMixin,
): string | undefined {
- // const layoutMode = node.inferredAutoLayout!.layoutMode
- // switch (layoutMode) {
- // case 'HORIZONTAL':
- // if (node.layoutSizingHorizontal === 'HUG') return undefined
- // break
- // case 'VERTICAL':
- // if (node.layoutSizingVertical === 'HUG') return undefined
- // break
- // default:
- // break
- // }
return {
MIN: 'flex-start',
MAX: 'flex-end',
@@ -53,71 +42,6 @@ function getJustifyContent(
}
function getAlignItems(node: SceneNode & BaseFrameMixin): string | undefined {
- // const layoutMode = node.inferredAutoLayout!.layoutMode
- // switch (layoutMode) {
- // case 'HORIZONTAL':
- // if (
- // node.children.length &&
- // node.children.every(
- // (child) =>
- // child.visible &&
- // 'layoutSizingVertical' in child &&
- // child.layoutSizingVertical === 'FILL' &&
- // child.maxHeight === null,
- // )
- // )
- // return
- // if (node.layoutSizingVertical === 'HUG') {
- // if (node.children.length > 1)
- // for (const child of node.children)
- // if (
- // child.visible &&
- // 'layoutSizingVertical' in child &&
- // child.layoutSizingVertical !== 'FILL'
- // )
- // return {
- // MIN: 'flex-start',
- // MAX: 'flex-end',
- // CENTER: 'center',
- // SPACE_BETWEEN: 'space-between',
- // BASELINE: 'baseline',
- // }[node.counterAxisAlignItems]
- // return
- // }
- // break
- // case 'VERTICAL':
- // if (
- // node.children.length &&
- // node.children.every(
- // (child) =>
- // child.visible &&
- // 'layoutSizingHorizontal' in child &&
- // child.layoutSizingHorizontal === 'FILL' &&
- // child.maxWidth === null,
- // )
- // )
- // return
- // if (node.layoutSizingHorizontal === 'HUG') {
- // if (node.children.length > 1)
- // for (const child of node.children)
- // if (
- // child.visible &&
- // 'layoutSizingHorizontal' in child &&
- // child.layoutSizingHorizontal !== 'FILL'
- // )
- // return {
- // MIN: 'flex-start',
- // MAX: 'flex-end',
- // CENTER: 'center',
- // SPACE_BETWEEN: 'space-between',
- // BASELINE: 'baseline',
- // }[node.counterAxisAlignItems]
- // return
- // }
- // break
- // default:
- // break
- // }
return {
MIN: 'flex-start',
MAX: 'flex-end',
diff --git a/src/codegen/props/effect.ts b/src/codegen/props/effect.ts
index 43de74a..e673069 100644
--- a/src/codegen/props/effect.ts
+++ b/src/codegen/props/effect.ts
@@ -6,69 +6,67 @@ export async function getEffectProps(
node: SceneNode,
): Promise | undefined> {
if ('effects' in node && node.effects.length > 0) {
- return node.effects.reduce((acc, effect) => {
- return {
- ...acc,
- ..._getEffectPropsFromEffect(effect),
- }
- }, {})
+ return node.effects.reduce(
+ (acc, effect) => {
+ const props = _getEffectPropsFromEffect(effect)
+ for (const [key, value] of Object.entries(props)) {
+ if (value) {
+ if (acc[key]) {
+ acc[key] = `${acc[key]}, ${value}`
+ } else {
+ acc[key] = value
+ }
+ }
+ }
+ return acc
+ },
+ {} as Record,
+ )
}
}
function _getEffectPropsFromEffect(effect: Effect): Record {
switch (effect.type) {
case 'DROP_SHADOW': {
- const shadow = effect as DropShadowEffect
- const offsetX = shadow.offset.x
- const offsetY = shadow.offset.y
- const blur = shadow.radius
- const spread = shadow.spread || 0
- const color = shadow.color
+ const { offset, radius, spread, color } = effect
+ const { x, y } = offset
return {
- boxShadow: `${addPx(offsetX, '0')} ${addPx(offsetY, '0')} ${addPx(blur, '0')} ${addPx(spread, '0')} ${optimizeHex(rgbaToHex(color))}`,
+ boxShadow: `${addPx(x, '0')} ${addPx(y, '0')} ${addPx(radius, '0')} ${addPx(spread, '0')} ${optimizeHex(rgbaToHex(color))}`,
}
}
case 'INNER_SHADOW': {
- const shadow = effect as InnerShadowEffect
- const offsetX = shadow.offset.x
- const offsetY = shadow.offset.y
- const blur = shadow.radius
- const spread = shadow.spread || 0
- const color = shadow.color
+ const { offset, radius, spread, color } = effect
+ const { x, y } = offset
return {
- boxShadow: `inset ${addPx(offsetX, '0')} ${addPx(offsetY, '0')} ${addPx(blur, '0')} ${addPx(spread, '0')} ${optimizeHex(rgbaToHex(color))}`,
+ boxShadow: `inset ${addPx(x, '0')} ${addPx(y, '0')} ${addPx(radius, '0')} ${addPx(spread, '0')} ${optimizeHex(rgbaToHex(color))}`,
}
}
- case 'LAYER_BLUR': {
+ case 'LAYER_BLUR':
return {
filter: `blur(${effect.radius}px)`,
}
- }
- case 'BACKGROUND_BLUR': {
+
+ case 'BACKGROUND_BLUR':
return {
backdropFilter: `blur(${effect.radius}px)`,
WebkitBackdropFilter: `blur(${effect.radius}px)`,
}
- }
- case 'NOISE': {
+
+ case 'NOISE':
return {
filter: `contrast(100%) brightness(100%)`,
}
- }
- case 'TEXTURE': {
+
+ case 'TEXTURE':
return {
filter: `contrast(100%) brightness(100%)`,
}
- }
- case 'GLASS': {
+ case 'GLASS':
return {
backdropFilter: `blur(${effect.radius}px)`,
WebkitBackdropFilter: `blur(${effect.radius}px)`,
}
- }
- default:
- return {}
}
}
diff --git a/src/codegen/props/grid-child.ts b/src/codegen/props/grid-child.ts
index 424d770..31d823f 100644
--- a/src/codegen/props/grid-child.ts
+++ b/src/codegen/props/grid-child.ts
@@ -13,11 +13,10 @@ export function getGridChildProps(
const columnCount = parent.gridColumnCount
const currentIdx =
node.gridColumnAnchorIndex + node.gridRowAnchorIndex * columnCount
- if (parent.children[currentIdx] !== node) {
+ if (parent.children[currentIdx] !== node)
return {
gridColumn: `${node.gridColumnAnchorIndex + 1} / span 1`,
gridRow: `${node.gridRowAnchorIndex + 1} / span 1`,
}
- }
}
}
diff --git a/src/codegen/props/layout.ts b/src/codegen/props/layout.ts
index b57b973..e77d150 100644
--- a/src/codegen/props/layout.ts
+++ b/src/codegen/props/layout.ts
@@ -91,9 +91,8 @@ function _getLayoutProps(
node.maxWidth !== null)
? '100%'
: undefined,
- h: aspectRatio
- ? null
- : hType === 'FIXED'
+ h:
+ hType === 'FIXED'
? addPx(node.height)
: hType === 'FILL' &&
((node.parent && isChildWidthShrinker(node.parent, 'height')) ||
diff --git a/src/codegen/props/position.ts b/src/codegen/props/position.ts
index 359ba66..6dd0363 100644
--- a/src/codegen/props/position.ts
+++ b/src/codegen/props/position.ts
@@ -1,14 +1,23 @@
import { addPx } from '../utils/add-px'
+function isFreelayout(node: BaseNode & ChildrenMixin) {
+ return (
+ (!('inferredAutoLayout' in node) || !node.inferredAutoLayout) &&
+ 'layoutPositioning' in node &&
+ node.layoutPositioning === 'AUTO'
+ )
+}
+
export function getPositionProps(
node: SceneNode,
): Record | undefined {
if (
'parent' in node &&
node.parent &&
+ (('layoutPositioning' in node && node.layoutPositioning === 'ABSOLUTE') ||
+ isFreelayout(node.parent)) &&
'width' in node.parent &&
- 'layoutPositioning' in node &&
- node.layoutPositioning === 'ABSOLUTE'
+ 'height' in node.parent
) {
const constraints =
'constraints' in node
@@ -20,30 +29,38 @@ export function getPositionProps(
: undefined
if (!constraints) return
const { horizontal, vertical } = constraints
- let left, right, top, bottom: string | undefined
- switch (horizontal) {
- case 'MIN':
- left = addPx(node.x) ?? '0px'
- break
- case 'MAX':
- right = addPx(node.parent?.width - node.x - node.width) ?? '0px'
- break
- default:
- left = '0px'
- right = '0px'
- break
- }
- switch (vertical) {
- case 'MIN':
- top = addPx(node.y) ?? '0px'
- break
- case 'MAX':
- bottom = addPx(node.parent.height - node.y - node.height) ?? '0px'
- break
- default:
- top = '0px'
- bottom = '0px'
- break
+ let left: string | undefined
+ let right: string | undefined
+ let top: string | undefined
+ let bottom: string | undefined
+ if (isFreelayout(node.parent)) {
+ left = addPx(node.x) ?? '0px'
+ top = addPx(node.y) ?? '0px'
+ } else {
+ switch (horizontal) {
+ case 'MIN':
+ left = addPx(node.x) ?? '0px'
+ break
+ case 'MAX':
+ right = addPx(node.parent.width - node.x - node.width) ?? '0px'
+ break
+ default:
+ left = '0px'
+ right = '0px'
+ break
+ }
+ switch (vertical) {
+ case 'MIN':
+ top = addPx(node.y) ?? '0px'
+ break
+ case 'MAX':
+ bottom = addPx(node.parent.height - node.y - node.height) ?? '0px'
+ break
+ default:
+ top = '0px'
+ bottom = '0px'
+ break
+ }
}
return {
pos: 'absolute',
@@ -55,10 +72,15 @@ export function getPositionProps(
}
if (
'children' in node &&
- node.children.some(
+ (node.children.some(
(child) =>
'layoutPositioning' in child && child.layoutPositioning === 'ABSOLUTE',
- )
+ ) ||
+ (isFreelayout(node) &&
+ node.children.some(
+ (child) =>
+ 'layoutPositioning' in child && child.layoutPositioning === 'AUTO',
+ )))
) {
return {
pos: 'relative',
diff --git a/src/codegen/props/selector.ts b/src/codegen/props/selector.ts
index abd7f4c..becc1e3 100644
--- a/src/codegen/props/selector.ts
+++ b/src/codegen/props/selector.ts
@@ -24,7 +24,7 @@ export async function getSelectorProps(
async (component) =>
[
hasEffect
- ? component.variantProperties!.effect
+ ? component.variantProperties?.effect
: triggerTypeToEffect(component.reactions[0]?.trigger?.type),
await getProps(component),
] as const,
@@ -60,7 +60,9 @@ export async function getSelectorProps(
const def = difference(props, defaultProps)
if (Object.keys(def).length === 0) continue
result.props[`_${effect}`] = def
- Object.keys(def).forEach((key) => diffKeys.add(key))
+ for (const key of Object.keys(def)) {
+ diffKeys.add(key)
+ }
}
if (transition?.type === 'SMART_ANIMATE' && diffKeys.size > 0) {
const keys = Array.from(diffKeys)
diff --git a/src/codegen/render/index.ts b/src/codegen/render/index.ts
index 4e8f9f2..f6d56cd 100644
--- a/src/codegen/render/index.ts
+++ b/src/codegen/render/index.ts
@@ -16,18 +16,18 @@ export function renderNode(
filterPropsWithComponent(component, filteredProps),
)
const hasChildren = childrenCodes.length > 0
- const tail = hasChildren ? space(deps) + `${component}>` : ''
+ const tail = hasChildren ? `${space(deps)}${component}>` : ''
const multiProps = propsString.includes('\n')
return [
`${space(deps)}<${component}${propsString ? (multiProps ? `\n${paddingLeftMultiline(propsString, deps + 1)}` : ` ${propsString}`) : ''}${
- (multiProps ? '\n' + space(deps) : !hasChildren ? ' ' : '') +
+ (multiProps ? `\n${space(deps)}` : !hasChildren ? ' ' : '') +
(hasChildren ? '>' : '/>')
}`,
hasChildren
? childrenCodes
.map(
(child) =>
- space(deps + 1) + child.split('\n').join('\n' + space(deps + 1)),
+ space(deps + 1) + child.split('\n').join(`\n${space(deps + 1)}`),
)
.join('\n')
: '',
@@ -57,7 +57,7 @@ export function renderComponent(
.split('\n')
.map((line) => line)
.join('\n')}\n${space(1)})`
- : code
+ : code.trim().replace(/\s+/g, ' ')
}
}`
}
diff --git a/src/codegen/render/text.ts b/src/codegen/render/text.ts
index 8450129..6b903df 100644
--- a/src/codegen/render/text.ts
+++ b/src/codegen/render/text.ts
@@ -32,7 +32,7 @@ export async function renderText(node: TextNode): Promise<{
Object.entries(
await propsToPropsWithTypography(
{
- ...((await textSegmentToTypography(seg)) as any),
+ ...(await textSegmentToTypography(seg)),
color: (
await Promise.all(
seg.fills.map(
diff --git a/src/codegen/utils/__tests__/paint-to-css.test.ts b/src/codegen/utils/__tests__/paint-to-css.test.ts
new file mode 100644
index 0000000..27d0b84
--- /dev/null
+++ b/src/codegen/utils/__tests__/paint-to-css.test.ts
@@ -0,0 +1,63 @@
+import { describe, expect, mock, test } from 'bun:test'
+import { paintToCSS } from '../paint-to-css'
+
+// mock asset checker to avoid real node handling
+mock.module('../check-asset-node', () => ({
+ checkAssetNode: () => 'png',
+}))
+
+describe('paintToCSS', () => {
+ test('converts image paint with TILE scaleMode to repeat url', async () => {
+ ;(globalThis as { figma?: unknown }).figma = {
+ util: { rgba: (v: unknown) => v },
+ } as unknown as typeof figma
+
+ const res = await paintToCSS(
+ {
+ type: 'IMAGE',
+ visible: true,
+ opacity: 1,
+ scaleMode: 'TILE',
+ } as unknown as ImagePaint,
+ { width: 100, height: 100 } as unknown as SceneNode,
+ false,
+ )
+
+ expect(res).toBe('url(/icons/image.png) repeat')
+ })
+
+ test('converts pattern paint with alignments and spacing', async () => {
+ ;(globalThis as { figma?: unknown }).figma = {
+ getNodeByIdAsync: mock(() =>
+ Promise.resolve({ name: 'patternNode' } as unknown as SceneNode),
+ ),
+ } as unknown as typeof figma
+
+ const res = await paintToCSS(
+ {
+ type: 'PATTERN',
+ visible: true,
+ opacity: 1,
+ sourceNodeId: '1',
+ spacing: { x: 1, y: 2 },
+ horizontalAlignment: 'CENTER',
+ verticalAlignment: 'END',
+ } as unknown as PatternPaint,
+ { width: 100, height: 100 } as unknown as SceneNode,
+ false,
+ )
+
+ expect(res).toBe(
+ 'url(/icons/patternNode.png) center 100% bottom 200% repeat',
+ )
+ })
+
+ test('returns null for unsupported paint type', async () => {
+ const res = await paintToCSS(
+ { type: 'VIDEO' } as unknown as Paint,
+ { width: 10, height: 10 } as unknown as SceneNode,
+ false,
+ )
+ expect(res).toBeNull()
+ })
+})
diff --git a/src/codegen/utils/__tests__/props-to-str.test.ts b/src/codegen/utils/__tests__/props-to-str.test.ts
new file mode 100644
index 0000000..ad95557
--- /dev/null
+++ b/src/codegen/utils/__tests__/props-to-str.test.ts
@@ -0,0 +1,37 @@
+import { describe, expect, test } from 'bun:test'
+import { propsToString } from '../props-to-str'
+
+describe('propsToString', () => {
+ test('sorts uppercase keys first and formats booleans/strings', () => {
+ const res = propsToString({ Z: '1', a: '2', b: false })
+ expect(res.split(' ')).toContain('Z="1"')
+ expect(res.split(' ')).toContain('b={false}')
+ })
+
+ test('omits assignment for boolean true', () => {
+ const res = propsToString({ Active: true, label: 'ok' })
+ expect(res).toContain('Active')
+ expect(res).not.toContain('Active={true}')
+ })
+
+ test('sort comparator handles lower then upper keys', () => {
+ const res = propsToString({ b: '1', A: '2' })
+ expect(res.startsWith('A="2"')).toBe(true)
+ })
+
+ test('formats objects with newlines when many props', () => {
+ const res = propsToString({
+ a: '1',
+ b: { x: 1 },
+ c: '3',
+ d: '4',
+ e: '5',
+ })
+ expect(res).toContain('\n')
+ expect(res).toContain(`b={${JSON.stringify({ x: 1 }, null, 2)}}`)
+ })
+
+ test('handles empty props', () => {
+ expect(propsToString({})).toBe('')
+ })
+})
diff --git a/src/codegen/utils/add-px.ts b/src/codegen/utils/add-px.ts
index 8d007d4..890ad68 100644
--- a/src/codegen/utils/add-px.ts
+++ b/src/codegen/utils/add-px.ts
@@ -1,8 +1,10 @@
+export function addPx(value: unknown, fallback: string): string
+export function addPx(value: unknown, fallback?: string): string | undefined
export function addPx(
value: unknown,
fallback: string | undefined = undefined,
) {
- if (typeof value !== 'number') return
+ if (typeof value !== 'number') return fallback
const fixed = value.toFixed(3)
const str = fixed.endsWith('.000')
? String(Math.round(value))
diff --git a/src/codegen/utils/four-value-shortcut.ts b/src/codegen/utils/four-value-shortcut.ts
index 80899bb..c562e6a 100644
--- a/src/codegen/utils/four-value-shortcut.ts
+++ b/src/codegen/utils/four-value-shortcut.ts
@@ -7,7 +7,7 @@ export function fourValueShortcut(
fourth: number,
): string {
if (first === second && second === third && third === fourth)
- return addPx(first, '0')!
+ return addPx(first, '0')
if (first === third && second === fourth)
return `${addPx(first, '0')} ${addPx(second, '0')}`
if (first === third && second === fourth)
diff --git a/src/codegen/utils/get-devup-component.ts b/src/codegen/utils/get-devup-component.ts
index ea32664..c242213 100644
--- a/src/codegen/utils/get-devup-component.ts
+++ b/src/codegen/utils/get-devup-component.ts
@@ -11,15 +11,12 @@ export function getDevupComponentByNode(
}
export function getDevupComponentByProps(props: Record) {
- switch (props['display']) {
+ switch (props.display) {
case 'flex':
- if (
- props['alignItems'] === 'center' &&
- props['justifyContent'] === 'center'
- ) {
+ if (props.alignItems === 'center' && props.justifyContent === 'center') {
return 'Center'
}
- return props['flexDir'] === 'column' ? 'VStack' : 'Flex'
+ return props.flexDir === 'column' ? 'VStack' : 'Flex'
case 'grid':
return 'Grid'
default:
diff --git a/src/codegen/utils/node-to-json.ts b/src/codegen/utils/node-to-json.ts
index 435a0f3..7fe5af6 100644
--- a/src/codegen/utils/node-to-json.ts
+++ b/src/codegen/utils/node-to-json.ts
@@ -1,7 +1,6 @@
export async function nodeToJson(node: SceneNode): Promise {
- return (
- (await node.exportAsync({
- format: 'JSON_REST_V1',
- })) as any
- ).document
+ const result = await node.exportAsync({
+ format: 'JSON_REST_V1',
+ })
+ return (result as { document: SceneNode }).document
}
diff --git a/src/codegen/utils/optimize-space.ts b/src/codegen/utils/optimize-space.ts
index cde1746..70c8b45 100644
--- a/src/codegen/utils/optimize-space.ts
+++ b/src/codegen/utils/optimize-space.ts
@@ -6,31 +6,31 @@ export function optimizeSpace(
r: number,
b: number,
l: number,
-): Record {
+): Record {
if (t === r && r === b && b === l) {
- return { [type]: addPx(t)! }
+ return { [type]: addPx(t) }
}
if (t === b && r === l) {
- return { [type + 'y']: addPx(t)!, [type + 'x']: addPx(l)! }
+ return { [`${type}y`]: addPx(t), [`${type}x`]: addPx(l) }
}
if (t === b) {
return {
- [type + 'y']: addPx(t)!,
- [type + 'r']: addPx(r)!,
- [type + 'l']: addPx(l)!,
+ [`${type}y`]: addPx(t),
+ [`${type}r`]: addPx(r),
+ [`${type}l`]: addPx(l),
}
}
if (l === r) {
return {
- [type + 'x']: addPx(l)!,
- [type + 't']: addPx(t)!,
- [type + 'b']: addPx(b)!,
+ [`${type}x`]: addPx(l),
+ [`${type}t`]: addPx(t),
+ [`${type}b`]: addPx(b),
}
}
return {
- [type + 't']: addPx(t)!,
- [type + 'r']: addPx(r)!,
- [type + 'b']: addPx(b)!,
- [type + 'l']: addPx(l)!,
+ [`${type}t`]: addPx(t),
+ [`${type}r`]: addPx(r),
+ [`${type}b`]: addPx(b),
+ [`${type}l`]: addPx(l),
}
}
diff --git a/src/codegen/utils/paint-to-css.ts b/src/codegen/utils/paint-to-css.ts
index 121933c..8160280 100644
--- a/src/codegen/utils/paint-to-css.ts
+++ b/src/codegen/utils/paint-to-css.ts
@@ -3,6 +3,7 @@ import { rgbaToHex } from '../../utils/rgba-to-hex'
import { checkAssetNode } from './check-asset-node'
import { fmtPct } from './fmtPct'
import { solidToString } from './solid-to-string'
+
interface Point {
x: number
y: number
@@ -177,7 +178,13 @@ async function convertPattern(fill: PatternPaint): Promise {
},
)
const verticalPosition = convertPosition(
- (fill as any).verticalAlignment,
+ 'verticalAlignment' in fill
+ ? (
+ fill as PatternPaint & {
+ verticalAlignment: 'START' | 'CENTER' | 'END'
+ }
+ ).verticalAlignment
+ : 'START',
fill.spacing.y,
{
START: 'top',
@@ -188,7 +195,7 @@ async function convertPattern(fill: PatternPaint): Promise {
const position = [horizontalPosition, verticalPosition]
.filter(Boolean)
.join(' ')
- return `url(/icons/${imageName}.${imageExtension}) ${position} repeat`
+ return `url(/icons/${imageName}.${imageExtension})${position ? ` ${position}` : ''} repeat`
}
function convertPosition(
@@ -425,8 +432,7 @@ function _calculateRadialPositions(
// Calculate radius as distance from center to the radius point (for backward compatibility)
const radius = Math.sqrt(
- Math.pow(radiusPoint.x - center.x, 2) +
- Math.pow(radiusPoint.y - center.y, 2),
+ (radiusPoint.x - center.x) ** 2 + (radiusPoint.y - center.y) ** 2,
)
// Calculate separate radius for width and height in normalized space
diff --git a/src/codegen/utils/props-to-str.ts b/src/codegen/utils/props-to-str.ts
index 512635f..f3a043e 100644
--- a/src/codegen/utils/props-to-str.ts
+++ b/src/codegen/utils/props-to-str.ts
@@ -1,20 +1,23 @@
export function propsToString(props: Record) {
- return Object.entries(props)
- .sort((a, b) => {
- const isAUpper = /^[A-Z]/.test(a[0])
- const isBUpper = /^[A-Z]/.test(b[0])
- if (isAUpper && !isBUpper) return -1
- if (!isAUpper && isBUpper) return 1
- return a[0].localeCompare(b[0])
- })
- .map(
- ([key, value]) =>
- `${key}${typeof value === 'boolean' ? (value ? '' : `={${value}}`) : typeof value === 'object' ? `={${JSON.stringify(value, null, 2)}}` : `="${value}"`}`,
- )
- .join(
- Object.keys(props).length >= 5 ||
- Object.values(props).some((value) => typeof value === 'object')
- ? '\n'
- : ' ',
- )
+ const sorted = Object.entries(props).sort((a, b) => {
+ const isAUpper = /^[A-Z]/.test(a[0])
+ const isBUpper = /^[A-Z]/.test(b[0])
+ if (isAUpper && !isBUpper) return -1
+ if (!isAUpper && isBUpper) return 1
+ return a[0].localeCompare(b[0])
+ })
+
+ const parts = sorted.map(([key, value]) => {
+ if (typeof value === 'boolean') return `${key}${value ? '' : `={${value}}`}`
+ if (typeof value === 'object')
+ return `${key}={${JSON.stringify(value, null, 2)}}`
+ return `${key}="${value}"`
+ })
+
+ const separator =
+ Object.keys(props).length >= 5 ||
+ Object.values(props).some((value) => typeof value === 'object')
+ ? '\n'
+ : ' '
+ return parts.join(separator)
}
diff --git a/src/commands/__tests__/exportAssets.test.ts b/src/commands/__tests__/exportAssets.test.ts
index cd7ff94..6ceee01 100644
--- a/src/commands/__tests__/exportAssets.test.ts
+++ b/src/commands/__tests__/exportAssets.test.ts
@@ -1,175 +1,59 @@
-import { downloadFile } from '../../utils/download-file'
+import { afterAll, beforeEach, describe, expect, mock, test } from 'bun:test'
+import { Codegen } from '../../codegen/Codegen'
import { exportAssets } from '../exportAssets'
-vi.mock('download-file', () => ({
- downloadFile: vi.fn().mockResolvedValue(undefined),
-}))
-vi.mock('jszip', () => ({
- default: class JSZipMock {
- files: Record = {}
- file(name: string, data: unknown) {
- this.files[name] = data
- }
- async generateAsync() {
- return new Uint8Array([1, 2, 3])
- }
- },
-}))
-
-vi.mock('../../utils/download-file', () => ({
- downloadFile: vi.fn().mockResolvedValue(undefined),
-}))
-
-function createNode(
- type: SceneNode['type'],
- {
- characters,
- children,
- textStyleId,
- name,
- fills,
- parent,
- layoutPositioning = 'AUTO',
- layoutSizingHorizontal,
- styledTextSegments = [],
- variantProperties,
- visible = true,
- ...props
- }: {
- [_: string]: any
- characters?: string
- name?: string
- textStyleId?: string
- children?: SceneNode[]
- layoutPositioning?: string
- styledTextSegments?: any[]
- variantProperties?: Record
- } = {},
-): SceneNode {
- const ret = {
- type,
- getCSSAsync: async () => props,
- exportAsync: async () => '',
- getStyledTextSegments: () => styledTextSegments,
- layoutSizingHorizontal,
- textStyleId,
- parent,
- characters,
- visible,
- layoutPositioning,
- width: props.width ? parseInt(props.width) : undefined,
- height: props.height ? parseInt(props.height) : undefined,
- name,
- fills,
- variantProperties,
- children: children ?? [],
- } as unknown as SceneNode
- ;(ret as any).children.forEach((child: any) => {
- ;(child as any).parent = ret
- })
- return ret
-}
-
-const notifyMock = vi.fn()
-const showUIMock = vi.fn()
-const postMessageMock = vi.fn()
+const runMock = mock(() => Promise.resolve())
+let constructedNodes: SceneNode[] = []
+
+const originalCodegen = Codegen
+
+const notifyMock = mock(() => {})
+beforeEach(() => {
+ mock.module('../../codegen/Codegen', () => ({
+ Codegen: class {
+ constructor(node: SceneNode) {
+ constructedNodes.push(node)
+ }
+ run = runMock
+ },
+ }))
+ runMock.mockClear()
+ constructedNodes = []
+ ;(globalThis as { figma?: unknown }).figma = {
+ currentPage: { selection: [] },
+ notify: notifyMock,
+ } as unknown as typeof figma
+})
+afterAll(() => {
+ ;(globalThis as { figma?: unknown }).figma = undefined
+ notifyMock.mockClear()
+ mock.module('../../codegen/Codegen', () => ({
+ Codegen: originalCodegen,
+ }))
+})
describe('exportAssets', () => {
- beforeEach(() => {
- ;(globalThis as any).figma = {
- currentPage: {
- selection: [],
- name: 'TestPage',
- },
- notify: notifyMock,
- showUI: showUIMock,
- ui: { postMessage: postMessageMock, onmessage: null },
- }
- notifyMock.mockClear()
- })
+ test('runs Codegen for each selected node', async () => {
+ const selection = [
+ { id: '1' } as unknown as SceneNode,
+ { id: '2' } as unknown as SceneNode,
+ ]
+ ;(
+ (globalThis as { figma?: { currentPage?: { selection?: SceneNode[] } } })
+ .figma?.currentPage as { selection: SceneNode[] }
+ ).selection = selection
- afterEach(() => {
- vi.resetAllMocks()
- })
-
- it('should notify and return if no assets found', async () => {
- const node = createNode('RECTANGLE', {
- fills: [],
- })
- ;(globalThis as any).figma.currentPage.selection = [node]
await exportAssets()
- expect(notifyMock).toHaveBeenCalledWith('No assets found')
- })
- it('should not export assets if all children are invisible', async () => {
- const node = createNode('GROUP', {
- fills: [],
- children: [
- createNode('VECTOR', {
- fills: [],
- visible: false,
- }),
- ],
- })
- ;(globalThis as any).figma.currentPage.selection = [node]
- await exportAssets()
- expect(downloadFile).not.toHaveBeenCalled()
+ expect(notifyMock).toHaveBeenCalledWith('Exporting assets...')
+ expect(runMock).toHaveBeenCalledTimes(2)
+ expect(constructedNodes).toEqual(selection)
})
- it('should export assets and call downloadFile', async () => {
- const node = createNode('RECTANGLE', {
- fills: [],
- children: [
- createNode('VECTOR', {
- fills: [],
- width: '100px',
- height: '100px',
- }),
- createNode('RECTANGLE', {
- fills: [
- {
- type: 'IMAGE',
- imageRef: {
- id: '123',
- },
- scaleMode: 'FILL',
- },
- ],
- background: 'red',
- width: '100px',
- height: '100px',
- name: 'image.png',
- }),
- ],
- })
- ;(globalThis as any).figma.currentPage.selection = [node]
+ test('handles empty selection', async () => {
await exportAssets()
- expect(downloadFile).toHaveBeenCalledWith(
- 'TestPage.zip',
- expect.any(Uint8Array),
- )
- expect(notifyMock).toHaveBeenCalledWith(
- 'Assets exported',
- expect.any(Object),
- )
- })
- it('should raise error if exportAsync fails', async () => {
- const node = createNode('RECTANGLE', {
- fills: [
- {
- type: 'IMAGE',
- },
- ],
- })
- ;(globalThis as any).figma.currentPage.selection = [node]
- vi.spyOn(console, 'error').mockImplementation(() => {})
- ;(node.exportAsync as any) = vi.fn().mockRejectedValue('test')
- await exportAssets()
- expect(notifyMock).toHaveBeenCalledWith('Error exporting assets', {
- timeout: 3000,
- error: true,
- })
- vi.spyOn(console, 'error').mockRestore()
+ expect(notifyMock).toHaveBeenCalledWith('Exporting assets...')
+ expect(runMock).not.toHaveBeenCalled()
})
})
diff --git a/src/commands/__tests__/exportComponents.test.ts b/src/commands/__tests__/exportComponents.test.ts
index 3e88afa..5f955c3 100644
--- a/src/commands/__tests__/exportComponents.test.ts
+++ b/src/commands/__tests__/exportComponents.test.ts
@@ -1,10 +1,18 @@
-import { downloadFile } from '../../utils/download-file'
+import {
+ afterAll,
+ afterEach,
+ beforeEach,
+ describe,
+ expect,
+ mock,
+ spyOn,
+ test,
+} from 'bun:test'
+import * as downloadFileModule from '../../utils/download-file'
import { exportComponents } from '../exportComponents'
-vi.mock('download-file', () => ({
- downloadFile: vi.fn().mockResolvedValue(undefined),
-}))
-vi.mock('jszip', () => ({
+// mock jszip
+mock.module('jszip', () => ({
default: class JSZipMock {
files: Record = {}
file(name: string, data: unknown) {
@@ -16,10 +24,30 @@ vi.mock('jszip', () => ({
},
}))
-vi.mock('../../utils/download-file', () => ({
- downloadFile: vi.fn().mockResolvedValue(undefined),
+const runMock = mock(() => Promise.resolve())
+const getComponentsCodesMock = mock(() => ({}))
+
+mock.module('../codegen/Codegen', () => ({
+ Codegen: class {
+ node: SceneNode
+ constructor(node: SceneNode) {
+ this.node = node
+ }
+ run = runMock
+ getComponentsCodes = getComponentsCodesMock
+ },
}))
+const downloadFileMock = mock(() => Promise.resolve(undefined))
+
+beforeEach(() => {
+ spyOn(downloadFileModule, 'downloadFile').mockImplementation(downloadFileMock)
+})
+
+afterAll(() => {
+ spyOn(downloadFileModule, 'downloadFile').mockRestore()
+})
+
function createNode(
type: SceneNode['type'],
{
@@ -36,13 +64,13 @@ function createNode(
visible = true,
...props
}: {
- [_: string]: any
+ [_: string]: unknown
characters?: string
name?: string
textStyleId?: string
children?: SceneNode[]
layoutPositioning?: string
- styledTextSegments?: any[]
+ styledTextSegments?: unknown[]
variantProperties?: Record
} = {},
): SceneNode {
@@ -57,26 +85,30 @@ function createNode(
characters,
visible,
layoutPositioning,
- width: props.width ? parseInt(props.width) : undefined,
- height: props.height ? parseInt(props.height) : undefined,
+ width: props.width ? parseInt(props.width as string, 10) : undefined,
+ height: props.height ? parseInt(props.height as string, 10) : undefined,
name,
fills,
variantProperties,
children: children ?? [],
} as unknown as SceneNode
- ;(ret as any).children.forEach((child: any) => {
- ;(child as any).parent = ret
- })
+ const retWithChildren = ret as SceneNode & { children: SceneNode[] }
+ for (const child of retWithChildren.children) {
+ const childWithParent = child as Omit & {
+ parent?: SceneNode
+ }
+ childWithParent.parent = ret as SceneNode & { parent?: SceneNode }
+ }
return ret
}
-const notifyMock = vi.fn()
-const showUIMock = vi.fn()
-const postMessageMock = vi.fn()
+const notifyMock = mock(() => {})
+const showUIMock = mock(() => {})
+const postMessageMock = mock(() => {})
describe('exportComponents', () => {
beforeEach(() => {
- ;(globalThis as any).figma = {
+ ;(globalThis as { figma?: unknown }).figma = {
currentPage: {
selection: [],
name: 'TestPage',
@@ -84,24 +116,33 @@ describe('exportComponents', () => {
notify: notifyMock,
showUI: showUIMock,
ui: { postMessage: postMessageMock, onmessage: null },
- }
+ } as unknown as typeof figma
notifyMock.mockClear()
})
afterEach(() => {
- vi.resetAllMocks()
+ notifyMock.mockClear()
+ showUIMock.mockClear()
+ postMessageMock.mockClear()
+ downloadFileMock.mockClear()
+ runMock.mockClear()
+ getComponentsCodesMock.mockClear()
})
- it('should notify and return if no components found', async () => {
+ test('should notify and return if no components found', async () => {
const node = createNode('RECTANGLE', {
fills: [],
})
- ;(globalThis as any).figma.currentPage.selection = [node]
+ ;(
+ (globalThis as { figma?: { currentPage?: { selection?: SceneNode[] } } })
+ .figma?.currentPage as { selection: SceneNode[] }
+ ).selection = [node]
+ getComponentsCodesMock.mockReturnValueOnce({})
await exportComponents()
expect(notifyMock).toHaveBeenCalledWith('No components found')
})
- it('should not export components if all children are invisible', async () => {
+ test('should not export components if all children are invisible', async () => {
const node = createNode('GROUP', {
fills: [],
children: [
@@ -111,12 +152,16 @@ describe('exportComponents', () => {
}),
],
})
- ;(globalThis as any).figma.currentPage.selection = [node]
+ ;(
+ (globalThis as { figma?: { currentPage?: { selection?: SceneNode[] } } })
+ .figma?.currentPage as { selection: SceneNode[] }
+ ).selection = [node]
+ getComponentsCodesMock.mockReturnValueOnce({})
await exportComponents()
- expect(downloadFile).not.toHaveBeenCalled()
+ expect(downloadFileMock).not.toHaveBeenCalled()
})
- it('should export components and call downloadFile', async () => {
+ test('should export components and call downloadFile', async () => {
const node = createNode('COMPONENT', {
fills: [],
name: 'Component',
@@ -143,9 +188,15 @@ describe('exportComponents', () => {
}),
],
})
- ;(globalThis as any).figma.currentPage.selection = [node]
+ ;(
+ (globalThis as { figma?: { currentPage?: { selection?: SceneNode[] } } })
+ .figma?.currentPage as { selection: SceneNode[] }
+ ).selection = [node]
+ getComponentsCodesMock.mockReturnValueOnce({
+ Component: [['Component.tsx', '']],
+ })
await exportComponents()
- expect(downloadFile).toHaveBeenCalledWith(
+ expect(downloadFileMock).toHaveBeenCalledWith(
'TestPage.zip',
expect.any(Uint8Array),
)
@@ -155,7 +206,7 @@ describe('exportComponents', () => {
)
})
- it('should raise error', async () => {
+ test('should raise error', async () => {
const node = createNode('COMPONENT', {
children: [
createNode('RECTANGLE', {
@@ -163,13 +214,19 @@ describe('exportComponents', () => {
}),
],
})
- ;(globalThis as any).figma.currentPage.selection = [node]
- vi.spyOn(console, 'error').mockImplementation(() => {})
+ ;(
+ (globalThis as { figma?: { currentPage?: { selection?: SceneNode[] } } })
+ .figma?.currentPage as { selection: SceneNode[] }
+ ).selection = [node]
+ getComponentsCodesMock.mockImplementation(() => {
+ throw new Error('boom')
+ })
+ const consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {})
await exportComponents()
expect(notifyMock).toHaveBeenCalledWith('Error exporting components', {
timeout: 3000,
error: true,
})
- vi.spyOn(console, 'error').mockRestore()
+ consoleErrorSpy.mockRestore()
})
})
diff --git a/src/commands/devup/__tests__/apply-typography.test.ts b/src/commands/devup/__tests__/apply-typography.test.ts
new file mode 100644
index 0000000..f9a7fa1
--- /dev/null
+++ b/src/commands/devup/__tests__/apply-typography.test.ts
@@ -0,0 +1,167 @@
+import {
+ afterAll,
+ afterEach,
+ describe,
+ expect,
+ mock,
+ spyOn,
+ test,
+} from 'bun:test'
+import { applyTypography } from '../apply-typography'
+
+describe('applyTypography', () => {
+ afterEach(() => {
+ ;(globalThis as { figma?: unknown }).figma = undefined
+ })
+
+ afterAll(() => {
+ mock.restore()
+ })
+
+ test('applies typography to a newly created style', async () => {
+ const styleObj = { name: '' } as unknown as TextStyle
+ const createTextStyle = mock(() => styleObj)
+ const loadFontAsync = mock(() => Promise.resolve())
+ const notify = mock(() => {})
+
+ ;(globalThis as { figma?: unknown }).figma = {
+ createTextStyle,
+ loadFontAsync,
+ notify,
+ } as unknown as typeof figma
+
+ await applyTypography(
+ 'mobile/title',
+ {
+ fontFamily: 'Inter',
+ fontStyle: 'italic',
+ fontSize: '14',
+ letterSpacing: '0.1em',
+ lineHeight: 'normal',
+ textTransform: 'uppercase',
+ textDecoration: 'underline',
+ },
+ [],
+ )
+
+ expect(createTextStyle).toHaveBeenCalled()
+ expect(loadFontAsync).toHaveBeenCalled()
+ expect((styleObj as unknown as { fontName: FontName }).fontName).toEqual({
+ family: 'Inter',
+ style: 'Italic',
+ })
+ expect((styleObj as unknown as { fontSize: number }).fontSize).toBe(14)
+ expect(
+ (styleObj as unknown as { letterSpacing: LetterSpacing }).letterSpacing,
+ ).toEqual({ unit: 'PERCENT', value: 0.1 })
+ expect(
+ (styleObj as unknown as { lineHeight: LineHeight }).lineHeight,
+ ).toEqual({ unit: 'AUTO' })
+ expect((styleObj as unknown as { textCase: string }).textCase).toBe(
+ 'UPPERCASE',
+ )
+ expect(
+ (styleObj as unknown as { textDecoration: TextDecoration })
+ .textDecoration,
+ ).toBe('UNDERLINE')
+ expect(notify).not.toHaveBeenCalled()
+ })
+
+ test('notifies on font load failure and leaves style untouched', async () => {
+ const styleObj = { name: '' } as unknown as TextStyle
+ const createTextStyle = mock(() => styleObj)
+ const loadFontAsync = mock(() => Promise.reject('font'))
+ const notify = mock(() => {})
+
+ ;(globalThis as { figma?: unknown }).figma = {
+ createTextStyle,
+ loadFontAsync,
+ notify,
+ } as unknown as typeof figma
+ spyOn(console, 'error').mockImplementation(() => {})
+
+ await applyTypography(
+ 'mobile/title',
+ {
+ fontFamily: 'Inter',
+ fontSize: '12',
+ letterSpacing: '2', // triggers PIXELS branch if it were to run
+ lineHeight: 120,
+ },
+ [],
+ )
+
+ expect(loadFontAsync).toHaveBeenCalled()
+ expect(notify).toHaveBeenCalledWith(
+ expect.stringContaining('Failed to create text style'),
+ { error: true },
+ )
+ expect(console.error).toHaveBeenCalledWith(
+ 'Failed to create text style',
+ 'font',
+ )
+ })
+
+ test('applies px letterSpacing and percent lineHeight on existing style', async () => {
+ const styleObj = {
+ name: 'mobile/body',
+ } as unknown as TextStyle
+ const loadFontAsync = mock(() => Promise.resolve())
+ const notify = mock(() => {})
+
+ ;(globalThis as { figma?: unknown }).figma = {
+ loadFontAsync,
+ notify,
+ createTextStyle: mock(() => styleObj),
+ } as unknown as typeof figma
+
+ await applyTypography(
+ 'mobile/body',
+ {
+ fontFamily: 'Inter',
+ fontSize: '16',
+ letterSpacing: '2',
+ lineHeight: 120,
+ },
+ [styleObj],
+ )
+
+ expect(
+ (styleObj as unknown as { letterSpacing: LetterSpacing }).letterSpacing,
+ ).toEqual({ unit: 'PIXELS', value: 200 })
+ expect(
+ (styleObj as unknown as { lineHeight: LineHeight }).lineHeight,
+ ).toEqual({
+ unit: 'PERCENT',
+ value: 1.2,
+ })
+ expect(loadFontAsync).toHaveBeenCalled()
+ expect(notify).not.toHaveBeenCalled()
+ })
+
+ test('applies string lineHeight as pixels', async () => {
+ const styleObj = { name: '' } as unknown as TextStyle
+ const createTextStyle = mock(() => styleObj)
+ const loadFontAsync = mock(() => Promise.resolve())
+
+ ;(globalThis as { figma?: unknown }).figma = {
+ createTextStyle,
+ loadFontAsync,
+ notify: mock(() => {}),
+ } as unknown as typeof figma
+
+ await applyTypography(
+ 'mobile/caption',
+ {
+ fontFamily: 'Inter',
+ fontSize: '11',
+ lineHeight: '24',
+ },
+ [],
+ )
+
+ expect(
+ (styleObj as unknown as { lineHeight: LineHeight }).lineHeight,
+ ).toEqual({ unit: 'PIXELS', value: 24 })
+ })
+})
diff --git a/src/commands/devup/__tests__/import-devup.test.ts b/src/commands/devup/__tests__/import-devup.test.ts
new file mode 100644
index 0000000..e9877c8
--- /dev/null
+++ b/src/commands/devup/__tests__/import-devup.test.ts
@@ -0,0 +1,138 @@
+import { describe, expect, mock, spyOn, test } from 'bun:test'
+import * as uploadFileModule from '../../../utils/upload-file'
+import { importDevup } from '../import-devup'
+import * as uploadXlsxModule from '../utils/upload-devup-xlsx'
+
+describe('import-devup (standalone file)', () => {
+ test('returns early when theme is missing', async () => {
+ const uploadFile = mock(() => Promise.resolve('{}'))
+ spyOn(uploadFileModule, 'uploadFile').mockImplementation(uploadFile)
+ await importDevup('json')
+ expect(uploadFile).toHaveBeenCalledWith('.json')
+ })
+
+ test('imports colors and typography from excel payload', async () => {
+ const uploadXlsx = mock(() =>
+ Promise.resolve({
+ theme: {
+ colors: { Light: { primary: '#111111' } },
+ typography: {
+ heading: {
+ fontFamily: 'Inter',
+ fontStyle: 'italic',
+ fontSize: '12',
+ letterSpacing: '0.1em',
+ lineHeight: 'normal',
+ textTransform: 'uppercase',
+ textDecoration: 'underline',
+ },
+ },
+ },
+ }),
+ )
+ spyOn(uploadXlsxModule, 'uploadDevupXlsx').mockImplementation(uploadXlsx)
+
+ const setValueForMode = mock(() => {})
+ const createVariable = mock(
+ () =>
+ ({
+ name: 'primary',
+ setValueForMode,
+ remove: mock(() => {}),
+ }) as unknown as Variable,
+ )
+ const addMode = mock((name: string) => `${name}-id`)
+ const collection = {
+ modes: [] as { modeId: string; name: string }[],
+ addMode,
+ removeMode: mock(() => {}),
+ } as unknown as VariableCollection
+
+ const createTextStyle = mock(
+ () =>
+ ({
+ name: '',
+ }) as unknown as TextStyle,
+ )
+ const loadFontAsync = mock(() => Promise.resolve())
+
+ ;(globalThis as { figma?: unknown }).figma = {
+ util: { rgba: (v: unknown) => v },
+ variables: {
+ getLocalVariableCollectionsAsync: async () => [],
+ getLocalVariablesAsync: async () => [],
+ createVariableCollection: () => collection,
+ createVariable,
+ },
+ getLocalTextStylesAsync: async () => [],
+ createTextStyle,
+ loadFontAsync,
+ } as unknown as typeof figma
+
+ await importDevup('excel')
+
+ expect(addMode).toHaveBeenCalledWith('Light')
+ expect(setValueForMode).toHaveBeenCalledWith('Light-id', '#111111')
+ expect(createTextStyle).toHaveBeenCalled()
+ expect(loadFontAsync).toHaveBeenCalled()
+ })
+
+ test('imports array typography and skips nulls', async () => {
+ const uploadXlsx = mock(() =>
+ Promise.resolve({
+ theme: {
+ typography: {
+ title: [
+ null,
+ {
+ fontFamily: 'Inter',
+ fontSize: '14',
+ letterSpacing: '1',
+ lineHeight: 120,
+ },
+ ],
+ },
+ },
+ }),
+ )
+ spyOn(uploadXlsxModule, 'uploadDevupXlsx').mockImplementation(uploadXlsx)
+
+ const createTextStyle = mock(
+ () =>
+ ({
+ name: '',
+ }) as unknown as TextStyle,
+ )
+ const loadFontAsync = mock(() => Promise.resolve())
+
+ ;(globalThis as { figma?: unknown }).figma = {
+ util: { rgba: (v: unknown) => v },
+ variables: {
+ getLocalVariableCollectionsAsync: async () => [],
+ getLocalVariablesAsync: async () => [],
+ createVariableCollection: () =>
+ ({
+ modes: [],
+ addMode: mock(() => 'm1'),
+ removeMode: mock(() => {}),
+ }) as unknown as VariableCollection,
+ createVariable: mock(
+ () =>
+ ({
+ name: 'primary',
+ setValueForMode: mock(() => {}),
+ remove: mock(() => {}),
+ }) as unknown as Variable,
+ ),
+ },
+ getLocalTextStylesAsync: async () => [],
+ createTextStyle,
+ loadFontAsync,
+ } as unknown as typeof figma
+
+ await importDevup('excel')
+
+ expect(createTextStyle).toHaveBeenCalled()
+ expect(loadFontAsync).toHaveBeenCalled()
+ })
+})
diff --git a/src/commands/devup/__tests__/index.test.ts b/src/commands/devup/__tests__/index.test.ts
index 231acd9..6e53430 100644
--- a/src/commands/devup/__tests__/index.test.ts
+++ b/src/commands/devup/__tests__/index.test.ts
@@ -1,380 +1,604 @@
-import { beforeEach, describe, expect, it, vi } from 'vitest'
-
+import {
+ afterAll,
+ afterEach,
+ beforeEach,
+ describe,
+ expect,
+ mock,
+ spyOn,
+ test,
+} from 'bun:test'
import * as downloadFileModule from '../../../utils/download-file'
+import * as isVariableAliasModule from '../../../utils/is-variable-alias'
+import * as optimizeHexModule from '../../../utils/optimize-hex'
+import * as rgbaToHexModule from '../../../utils/rgba-to-hex'
+import * as styleNameToTypographyModule from '../../../utils/style-name-to-typography'
+import * as textSegmentToTypographyModule from '../../../utils/text-segment-to-typography'
+import * as textStyleToTypographyModule from '../../../utils/text-style-to-typography'
import * as uploadFileModule from '../../../utils/upload-file'
+import * as variableAliasToValueModule from '../../../utils/variable-alias-to-value'
import { exportDevup, importDevup } from '../index'
-import type { Devup } from '../types'
-import * as colorUtils from '../utils/get-devup-color-collection'
+import type { DevupTypography } from '../types'
+import * as downloadXlsxModule from '../utils/download-devup-xlsx'
+import * as getColorCollectionModule from '../utils/get-devup-color-collection'
+import * as uploadXlsxModule from '../utils/upload-devup-xlsx'
+
+afterAll(() => {
+ mock.restore()
+})
+
+describe('devup commands', () => {
+ const downloadFileMock = mock(() => Promise.resolve(undefined))
+ const downloadXlsxMock = mock(() => Promise.resolve(undefined))
+ const uploadFileMock = mock(() => Promise.resolve(''))
+ const uploadXlsxMock = mock(() =>
+ Promise.resolve({
+ theme: { colors: {}, typography: {} },
+ }),
+ )
+ let downloadFileSpy: ReturnType | null = null
+ let downloadXlsxSpy: ReturnType | null = null
+ let uploadFileSpy: ReturnType | null = null
+ let uploadXlsxSpy: ReturnType | null = null
+ let getColorCollectionSpy: ReturnType | null = null
+ let styleNameToTypographySpy: ReturnType | null = null
+ let textStyleToTypographySpy: ReturnType | null = null
+ let textSegmentToTypographySpy: ReturnType | null = null
+ let isVariableAliasSpy: ReturnType | null = null
+ let variableAliasToValueSpy: ReturnType | null = null
-describe('devup/index', () => {
beforeEach(() => {
- vi.resetAllMocks()
- ;(globalThis as any).figma = {
- variables: {
- getLocalVariableCollectionsAsync: vi.fn(),
- getVariableByIdAsync: vi.fn(),
- getLocalVariablesAsync: vi.fn(),
- createVariableCollection: vi.fn(),
- createVariable: vi.fn(),
- },
- util: {
- rgba: vi.fn((color: any) => color),
- },
- root: {
- children: [],
- },
- loadAllPagesAsync: vi.fn(),
- getLocalTextStylesAsync: vi.fn().mockResolvedValue([]),
- getStyleByIdAsync: vi.fn(),
- createTextStyle: vi.fn(),
- showUI: vi.fn(),
- ui: {
- onmessage: vi.fn(),
- postMessage: vi.fn(),
- close: vi.fn(),
- },
- base64Decode: vi.fn(),
- loadFontAsync: vi.fn().mockResolvedValue(undefined),
- } as any
+ downloadFileSpy = spyOn(
+ downloadFileModule,
+ 'downloadFile',
+ ).mockImplementation(downloadFileMock)
+ downloadXlsxSpy = spyOn(
+ downloadXlsxModule,
+ 'downloadDevupXlsx',
+ ).mockImplementation(downloadXlsxMock)
+ uploadFileSpy = spyOn(uploadFileModule, 'uploadFile').mockImplementation(
+ uploadFileMock,
+ )
+ uploadXlsxSpy = spyOn(
+ uploadXlsxModule,
+ 'uploadDevupXlsx',
+ ).mockImplementation(uploadXlsxMock)
})
- it('should export devup', async () => {
- vi.spyOn(colorUtils, 'getDevupColorCollection').mockResolvedValue({
- name: 'Devup Colors',
- modes: [
- { name: 'Light', modeId: 'light' },
- { name: 'Dark', modeId: 'dark' },
- ],
- variableIds: ['var1'],
- } as any)
- ;(figma.variables.getVariableByIdAsync as any).mockImplementation(
- async (id: string) => {
- if (id === 'var1') {
- return {
- name: 'Primary',
- valuesByMode: {
- light: { r: 1, g: 0, b: 0, a: 1 },
- dark: { r: 0, g: 0, b: 0, a: 1 },
- },
- }
- }
- return null
- },
+ afterEach(() => {
+ ;(globalThis as { figma?: unknown }).figma = undefined
+ downloadFileMock.mockClear()
+ downloadXlsxMock.mockClear()
+ uploadFileMock.mockClear()
+ uploadXlsxMock.mockClear()
+ downloadFileSpy?.mockRestore()
+ downloadXlsxSpy?.mockRestore()
+ uploadFileSpy?.mockRestore()
+ uploadXlsxSpy?.mockRestore()
+ getColorCollectionSpy?.mockRestore()
+ styleNameToTypographySpy?.mockRestore()
+ textStyleToTypographySpy?.mockRestore()
+ textSegmentToTypographySpy?.mockRestore()
+ isVariableAliasSpy?.mockRestore()
+ variableAliasToValueSpy?.mockRestore()
+ getColorCollectionSpy = null
+ styleNameToTypographySpy = null
+ textStyleToTypographySpy = null
+ textSegmentToTypographySpy = null
+ isVariableAliasSpy = null
+ variableAliasToValueSpy = null
+ })
+
+ test('exportDevup exports colors and downloads json', async () => {
+ getColorCollectionSpy = spyOn(
+ getColorCollectionModule,
+ 'getDevupColorCollection',
)
- // downloadFile mock
- const downloadFileMock = vi
- .spyOn(downloadFileModule, 'downloadFile')
- .mockResolvedValue(undefined)
- ;(figma.getLocalTextStylesAsync as any).mockResolvedValue([
- { id: 'style1', name: 'mobile/Title' },
- ])
- ;(figma.root.children as any) = [
- {
- findAll: () => [
- {
- type: 'TEXT',
- textStyleId: 'style1',
- getStyledTextSegments: () => [
- {
- fontName: { family: 'Pretendard', style: 'Regular' },
- fontWeight: 700,
- fontSize: 20,
- textDecoration: 'NONE',
- textCase: 'ORIGINAL',
- lineHeight: { unit: 'PIXELS', value: 24 },
- letterSpacing: { unit: 'PIXELS', value: 0 },
- textStyleId: 'style1',
- },
- ],
- },
- ],
+ getColorCollectionSpy.mockResolvedValue({
+ modes: [{ modeId: 'm1', name: 'Light' }],
+ variableIds: ['v1'],
+ } as unknown as VariableCollection)
+
+ spyOn(rgbaToHexModule, 'rgbaToHex').mockReturnValue('#ff0000')
+ spyOn(optimizeHexModule, 'optimizeHex').mockImplementation((v) => v)
+
+ ;(globalThis as { figma?: unknown }).figma = {
+ util: { rgba: (v: unknown) => v },
+ loadAllPagesAsync: async () => {},
+ getLocalTextStylesAsync: async () => [],
+ root: { findAllWithCriteria: () => [] },
+ variables: {
+ getVariableByIdAsync: async () =>
+ ({
+ name: 'Primary',
+ valuesByMode: { m1: { r: 1, g: 0, b: 0, a: 1 } },
+ }) as unknown as Variable,
},
- ]
- ;(figma.getStyleByIdAsync as any).mockResolvedValue({
- id: 'style1',
- name: 'mobile/Title',
- })
+ } as unknown as typeof figma
await exportDevup('json')
- expect(downloadFileMock).toHaveBeenCalled()
- const [fileName, data] = downloadFileMock.mock.calls[0]
- expect(fileName).toBe('devup.json')
- const parsed = JSON.parse(
- typeof data === 'string' ? data : Buffer.from(data).toString('utf-8'),
- ) as Devup
- expect(parsed.theme?.colors?.light?.primary).toBe('#F00')
- expect(parsed.theme?.colors?.dark?.primary).toBe('#000')
- expect(parsed.theme?.typography?.title).toBeTruthy()
- })
- it('should import devup', async () => {
- // uploadFile mock
- const devupData: Devup = {
- theme: {
- colors: {
- Light: { primary: '#FF0000' },
- },
- typography: {
- title: {
- fontFamily: 'Pretendard',
- fontStyle: 'normal',
- fontSize: '20px',
- fontWeight: 700,
- lineHeight: '24px',
- letterSpacing: '0px',
- },
- },
- },
- }
- vi.spyOn(uploadFileModule, 'uploadFile').mockResolvedValue(
- JSON.stringify(devupData),
- )
- vi.spyOn(colorUtils, 'getDevupColorCollection').mockResolvedValue({
- name: 'Devup Colors',
- modes: [{ name: 'Light', modeId: 'light' }],
- addMode: vi.fn((name: string) => name + '_id'),
- variableIds: [],
- removeMode: vi.fn(),
- } as any)
- ;(figma.variables.getLocalVariablesAsync as any).mockResolvedValue([])
- ;(figma.variables.createVariable as any).mockImplementation(
- (name: string) =>
- ({
- name,
- setValueForMode: vi.fn(),
- }) as any,
+ expect(getColorCollectionSpy).toHaveBeenCalled()
+ expect(downloadFileMock).toHaveBeenCalledWith(
+ 'devup.json',
+ expect.stringContaining('"primary":"#ff0000"'),
)
- ;(figma.getLocalTextStylesAsync as any).mockResolvedValue([])
- ;(figma.createTextStyle as any).mockReturnValue({ name: '' })
- await importDevup('json')
- expect(figma.variables.createVariable).toHaveBeenCalledWith(
- 'primary',
- expect.anything(),
- 'COLOR',
- )
- expect(figma.createTextStyle).toHaveBeenCalled()
})
- it('should not create colors when color collection is not found', async () => {
- vi.spyOn(colorUtils, 'getDevupColorCollection').mockResolvedValue(null)
- const downloadFileMock = vi
- .spyOn(downloadFileModule, 'downloadFile')
- .mockResolvedValue(undefined)
- await exportDevup('json')
- expect(downloadFileMock).toHaveBeenCalled()
- const [_, data] = downloadFileMock.mock.calls[0]
- const parsed = JSON.parse(
- typeof data === 'string' ? data : Buffer.from(data).toString('utf-8'),
+ test('exportDevup treeshake false exports typography and downloads excel', async () => {
+ getColorCollectionSpy = spyOn(
+ getColorCollectionModule,
+ 'getDevupColorCollection',
+ ).mockResolvedValue(null)
+ styleNameToTypographySpy = spyOn(
+ styleNameToTypographyModule,
+ 'styleNameToTypography',
+ ).mockReturnValue({
+ level: 0,
+ name: 'heading',
+ })
+ textStyleToTypographySpy = spyOn(
+ textStyleToTypographyModule,
+ 'textStyleToTypography',
+ ).mockReturnValue({
+ fontFamily: 'Inter',
+ } as unknown as DevupTypography)
+
+ ;(globalThis as { figma?: unknown }).figma = {
+ util: { rgba: (v: unknown) => v },
+ loadAllPagesAsync: async () => {},
+ getLocalTextStylesAsync: async () =>
+ [
+ { id: '1', name: 'heading/1' },
+ { id: '2', name: 'heading/2' },
+ ] as unknown as TextStyle[],
+ root: { findAllWithCriteria: () => [] },
+ variables: {
+ getVariableByIdAsync: async () => null,
+ },
+ } as unknown as typeof figma
+
+ await exportDevup('excel', false)
+
+ expect(downloadXlsxMock).toHaveBeenCalledWith(
+ 'devup.xlsx',
+ expect.stringContaining('"typography"'),
)
- expect(parsed.theme?.colors).toBeUndefined()
})
- it('should not add colors when variable is null', async () => {
- vi.spyOn(colorUtils, 'getDevupColorCollection').mockResolvedValue({
- name: 'Devup Colors',
- modes: [{ name: 'Light', modeId: 'light' }],
+ test('exportDevup treeshake true handles variable aliases and segments', async () => {
+ getColorCollectionSpy = spyOn(
+ getColorCollectionModule,
+ 'getDevupColorCollection',
+ ).mockResolvedValue({
+ modes: [{ modeId: 'm1', name: 'Light' }],
variableIds: ['var1'],
- } as any)
- ;(figma.variables.getVariableByIdAsync as any).mockResolvedValue(null)
- const downloadFileMock = vi
- .spyOn(downloadFileModule, 'downloadFile')
- .mockResolvedValue(undefined)
- await exportDevup('json')
- const [_, data] = downloadFileMock.mock.calls[0]
- const parsed = JSON.parse(
- typeof data === 'string' ? data : Buffer.from(data).toString('utf-8'),
- )
- expect(parsed.theme?.colors?.light).toEqual({})
- })
+ } as unknown as VariableCollection)
+ isVariableAliasSpy = spyOn(
+ isVariableAliasModule,
+ 'isVariableAlias',
+ ).mockReturnValue(true)
+ variableAliasToValueSpy = spyOn(
+ variableAliasToValueModule,
+ 'variableAliasToValue',
+ ).mockResolvedValue({ r: 1, g: 0, b: 0, a: 1 })
+ spyOn(rgbaToHexModule, 'rgbaToHex').mockReturnValue('#ff0000')
+ spyOn(optimizeHexModule, 'optimizeHex').mockImplementation((v) => v)
+ styleNameToTypographySpy = spyOn(
+ styleNameToTypographyModule,
+ 'styleNameToTypography',
+ ).mockReturnValue({ level: 0, name: 'heading' })
+ const typoSeg = { fontFamily: 'Inter', fontSize: 12 }
+ textStyleToTypographySpy = spyOn(
+ textStyleToTypographyModule,
+ 'textStyleToTypography',
+ ).mockReturnValue(typoSeg as unknown as DevupTypography)
+ textSegmentToTypographySpy = spyOn(
+ textSegmentToTypographyModule,
+ 'textSegmentToTypography',
+ ).mockReturnValue(typoSeg as unknown as DevupTypography)
+
+ const textNode = {
+ type: 'TEXT',
+ textStyleId: 'style1',
+ getStyledTextSegments: () => [{ textStyleId: 'style1' }],
+ } as unknown as TextNode
- it('should not add colors when value is boolean/number', async () => {
- vi.spyOn(colorUtils, 'getDevupColorCollection').mockResolvedValue({
- name: 'Devup Colors',
- modes: [{ name: 'Light', modeId: 'light' }],
- variableIds: ['var1', 'var2'],
- } as any)
- ;(figma.variables.getVariableByIdAsync as any).mockImplementation(
- async (id: string) => {
- if (id === 'var1') return { name: 'A', valuesByMode: { light: true } }
- if (id === 'var2') return { name: 'B', valuesByMode: { light: 123 } }
- return null
+ ;(globalThis as { figma?: unknown }).figma = {
+ util: { rgba: (v: unknown) => v },
+ loadAllPagesAsync: async () => {},
+ getLocalTextStylesAsync: async () => [
+ { id: 'style1', name: 'heading/1' } as unknown as TextStyle,
+ ],
+ root: {
+ findAllWithCriteria: () => [textNode],
+ children: [],
+ findAll: () => [],
+ },
+ getStyleByIdAsync: async () =>
+ ({ id: 'style1', name: 'heading/1' }) as unknown as TextStyle,
+ mixed: Symbol('mixed'),
+ variables: {
+ getVariableByIdAsync: async () =>
+ ({
+ name: 'Primary',
+ valuesByMode: { m1: { type: 'VARIABLE_ALIAS', id: 'var1' } },
+ }) as unknown as Variable,
},
+ } as unknown as typeof figma
+
+ await exportDevup('json', true)
+
+ expect(downloadFileMock).toHaveBeenCalledWith(
+ 'devup.json',
+ expect.stringContaining('"typography"'),
)
- const downloadFileMock = vi
- .spyOn(downloadFileModule, 'downloadFile')
- .mockResolvedValue(undefined)
- await exportDevup('json')
- const [_, data] = downloadFileMock.mock.calls[0]
- const parsed = JSON.parse(
- typeof data === 'string' ? data : Buffer.from(data).toString('utf-8'),
+ })
+
+ test('exportDevup fills missing typography levels from styles map', async () => {
+ getColorCollectionSpy = spyOn(
+ getColorCollectionModule,
+ 'getDevupColorCollection',
+ ).mockResolvedValue(null)
+ styleNameToTypographySpy = spyOn(
+ styleNameToTypographyModule,
+ 'styleNameToTypography',
+ ).mockImplementation((name: string) =>
+ name.includes('2')
+ ? ({ level: 1, name: 'heading' } as const)
+ : ({ level: 0, name: 'heading' } as const),
)
- expect(parsed.theme?.colors?.light).toEqual({})
+ const typoSeg = { fontFamily: 'Inter', fontSize: 12 }
+ textSegmentToTypographySpy = spyOn(
+ textSegmentToTypographyModule,
+ 'textSegmentToTypography',
+ ).mockReturnValue(typoSeg as unknown as DevupTypography)
+ textStyleToTypographySpy = spyOn(
+ textStyleToTypographyModule,
+ 'textStyleToTypography',
+ ).mockReturnValue(typoSeg as unknown as DevupTypography)
+
+ const textNode = {
+ type: 'TEXT',
+ textStyleId: 'style1',
+ getStyledTextSegments: () => [{ textStyleId: 'style1' }],
+ } as unknown as TextNode
+
+ ;(globalThis as { figma?: unknown }).figma = {
+ util: { rgba: (v: unknown) => v },
+ loadAllPagesAsync: async () => {},
+ getLocalTextStylesAsync: async () =>
+ [
+ { id: 'style1', name: 'heading/1' },
+ { id: 'style2', name: 'heading/2' },
+ ] as unknown as TextStyle[],
+ root: { findAllWithCriteria: () => [textNode], children: [] },
+ getStyleByIdAsync: async (id: string) =>
+ id === 'style1'
+ ? ({ id: 'style1', name: 'heading/1' } as unknown as TextStyle)
+ : ({ id: 'style2', name: 'heading/2' } as unknown as TextStyle),
+ mixed: Symbol('mixed'),
+ variables: { getVariableByIdAsync: async () => null },
+ } as unknown as typeof figma
+
+ await exportDevup('json', true)
+
+ const firstCall = downloadFileMock.mock.calls[0] as unknown[] | undefined
+ const data = (firstCall?.[1] as string) ?? '{}'
+ const parsed = JSON.parse(data) as {
+ theme?: { typography?: Record }
+ }
+ expect(parsed.theme?.typography?.heading).toBeDefined()
})
- it('should not add colors when isVariableAlias is true and nextValue is null/boolean/number', async () => {
- vi.spyOn(colorUtils, 'getDevupColorCollection').mockResolvedValue({
- name: 'Devup Colors',
- modes: [{ name: 'Light', modeId: 'light' }],
- variableIds: ['var1', 'var2', 'var3'],
- } as any)
- // isVariableAlias mock
- vi.mock('../../utils/is-variable-alias', () => ({
- isVariableAlias: () => true,
- }))
- // variableAliasToValue mock
- const variableAliasToValue =
- await import('../../../utils/variable-alias-to-value')
- vi.spyOn(
- variableAliasToValue,
- 'variableAliasToValue',
- ).mockResolvedValueOnce(null)
- vi.spyOn(
- variableAliasToValue,
- 'variableAliasToValue',
- ).mockResolvedValueOnce(true)
- vi.spyOn(
- variableAliasToValue,
- 'variableAliasToValue',
- ).mockResolvedValueOnce(123)
- ;(figma.variables.getVariableByIdAsync as any).mockImplementation(
- async (id: string) => {
- return {
- name: id,
- valuesByMode: { light: { type: 'VARIABLE_ALIAS', id: 'alias' } },
- }
- },
+ test('exportDevup builds typography array when first level missing', async () => {
+ getColorCollectionSpy = spyOn(
+ getColorCollectionModule,
+ 'getDevupColorCollection',
+ ).mockResolvedValue(null)
+ styleNameToTypographySpy = spyOn(
+ styleNameToTypographyModule,
+ 'styleNameToTypography',
+ ).mockImplementation((name: string) =>
+ name.includes('3')
+ ? ({ level: 3, name: 'heading' } as const)
+ : ({ level: 1, name: 'heading' } as const),
)
- const downloadFileMock = vi
- .spyOn(downloadFileModule, 'downloadFile')
- .mockResolvedValue(undefined)
- await exportDevup('json')
- const [_, data] = downloadFileMock.mock.calls[0]
- const parsed = JSON.parse(
- typeof data === 'string' ? data : Buffer.from(data).toString('utf-8'),
+ textStyleToTypographySpy = spyOn(
+ textStyleToTypographyModule,
+ 'textStyleToTypography',
+ ).mockImplementation(
+ (style: TextStyle) => ({ id: style.id }) as unknown as DevupTypography,
)
- expect(parsed.theme?.colors?.light).toEqual({})
+
+ ;(globalThis as { figma?: unknown }).figma = {
+ util: { rgba: (v: unknown) => v },
+ loadAllPagesAsync: async () => {},
+ getLocalTextStylesAsync: async () =>
+ [
+ { id: 'style1', name: 'heading/1' },
+ { id: 'style3', name: 'heading/3' },
+ ] as unknown as TextStyle[],
+ root: { findAllWithCriteria: () => [], children: [] },
+ variables: { getVariableByIdAsync: async () => null },
+ } as unknown as typeof figma
+
+ await exportDevup('json', false)
+
+ const firstCall = downloadFileMock.mock.calls[0] as unknown[] | undefined
+ const data = (firstCall?.[1] as string) ?? '{}'
+ const parsed = JSON.parse(data) as {
+ theme?: { typography?: Record }
+ }
+ expect(parsed.theme?.typography?.heading).toBeDefined()
})
- it('should not add typography when text.textStyleId is not string', async () => {
- vi.spyOn(colorUtils, 'getDevupColorCollection').mockResolvedValue(null)
- ;(figma.root.children as any) = [
- { findAll: () => [{ type: 'TEXT', textStyleId: 123 }] },
- ]
- ;(figma.getLocalTextStylesAsync as any).mockResolvedValue([])
- const downloadFileMock = vi
- .spyOn(downloadFileModule, 'downloadFile')
- .mockResolvedValue(undefined)
- await exportDevup('json')
- const [_, data] = downloadFileMock.mock.calls[0]
- const parsed = JSON.parse(
- typeof data === 'string' ? data : Buffer.from(data).toString('utf-8'),
+ test('exportDevup keeps full array when first level exists', async () => {
+ getColorCollectionSpy = spyOn(
+ getColorCollectionModule,
+ 'getDevupColorCollection',
+ ).mockResolvedValue(null)
+ styleNameToTypographySpy = spyOn(
+ styleNameToTypographyModule,
+ 'styleNameToTypography',
+ ).mockImplementation((name: string) =>
+ name.includes('2')
+ ? ({ level: 1, name: 'heading' } as const)
+ : ({ level: 0, name: 'heading' } as const),
+ )
+ textStyleToTypographySpy = spyOn(
+ textStyleToTypographyModule,
+ 'textStyleToTypography',
+ ).mockImplementation(
+ (style: TextStyle) => ({ id: style.id }) as unknown as DevupTypography,
)
- expect(parsed.theme?.typography).toBeUndefined()
+
+ ;(globalThis as { figma?: unknown }).figma = {
+ util: { rgba: (v: unknown) => v },
+ loadAllPagesAsync: async () => {},
+ getLocalTextStylesAsync: async () =>
+ [
+ { id: 'style0', name: 'heading/0' },
+ { id: 'style1', name: 'heading/2' },
+ ] as unknown as TextStyle[],
+ root: { findAllWithCriteria: () => [], children: [] },
+ variables: { getVariableByIdAsync: async () => null },
+ } as unknown as typeof figma
+
+ await exportDevup('json', false)
+
+ const firstCall = downloadFileMock.mock.calls[0] as unknown[] | undefined
+ const data = (firstCall?.[1] as string) ?? '{}'
+ const parsed = JSON.parse(data) as {
+ theme?: { typography?: Record }
+ }
+ expect(Array.isArray(parsed.theme?.typography?.heading)).toBe(true)
})
- it('should not add typography when style is not found or ids is not found', async () => {
- vi.spyOn(colorUtils, 'getDevupColorCollection').mockResolvedValue(null)
- ;(figma.root.children as any) = [
- {
- findAll: () => [
+ test('exportDevup filters out empty typography entries', async () => {
+ getColorCollectionSpy = spyOn(
+ getColorCollectionModule,
+ 'getDevupColorCollection',
+ ).mockResolvedValue(null)
+ styleNameToTypographySpy = spyOn(
+ styleNameToTypographyModule,
+ 'styleNameToTypography',
+ ).mockReturnValue({ level: 0, name: 'heading' })
+ textSegmentToTypographySpy = spyOn(
+ textSegmentToTypographyModule,
+ 'textSegmentToTypography',
+ ).mockReturnValue(null as unknown as DevupTypography)
+
+ ;(globalThis as { figma?: unknown }).figma = {
+ util: { rgba: (v: unknown) => v },
+ variables: {},
+ loadAllPagesAsync: async () => {},
+ getLocalTextStylesAsync: async () => [
+ {
+ id: 'id',
+ name: 'heading/1',
+ fontName: { family: 'Inter', style: 'Regular' },
+ } as unknown as TextStyle,
+ ],
+ root: {
+ findAllWithCriteria: () => [
{
- type: 'TEXT',
- textStyleId: 'style1',
- getStyledTextSegments: () => [{ textStyleId: 'style1' }],
- },
+ textStyleId: 'id',
+ getStyledTextSegments: () => [{ textStyleId: 'id' }],
+ } as unknown as TextNode,
],
},
- ]
- ;(figma.getLocalTextStylesAsync as any).mockResolvedValue([
- { id: 'style2', name: 'mobile/Title' },
- ])
- ;(figma.getStyleByIdAsync as any).mockResolvedValue(null)
- const downloadFileMock = vi
- .spyOn(downloadFileModule, 'downloadFile')
- .mockResolvedValue(undefined)
+ getStyleByIdAsync: async () =>
+ ({
+ id: 'id',
+ name: 'heading/1',
+ fontName: { family: 'Inter', style: 'Regular' },
+ }) as unknown as TextStyle,
+ } as unknown as typeof figma
+
await exportDevup('json')
- const [_, data] = downloadFileMock.mock.calls[0]
- const parsed = JSON.parse(
- typeof data === 'string' ? data : Buffer.from(data).toString('utf-8'),
+
+ const payload = JSON.parse(
+ (downloadFileMock.mock.calls[0] as unknown as { args: [string, string] })
+ ?.args?.[1] ?? '{}',
)
- expect(parsed.theme?.typography).toBeUndefined()
+ expect(payload.theme?.typography).toBeUndefined()
})
- it('should not add typography when typography[name] is already exists and value is already exists', async () => {
- vi.spyOn(colorUtils, 'getDevupColorCollection').mockResolvedValue(null)
- ;(figma.getLocalTextStylesAsync as any).mockResolvedValue([
- { id: 'style1', name: 'mobile/Title' },
- ])
- ;(figma.root.children as any) = [
- {
- findAll: () => [
- {
- type: 'TEXT',
- textStyleId: 'style1',
- getStyledTextSegments: () => [
- {
- fontName: { family: 'Pretendard', style: 'Regular' },
- fontWeight: 700,
- fontSize: 20,
- textDecoration: 'NONE',
- textCase: 'ORIGINAL',
- lineHeight: { unit: 'PIXELS', value: 24 },
- letterSpacing: { unit: 'PIXELS', value: 0 },
- },
- ],
+ test('importDevup creates colors and typography from json', async () => {
+ getColorCollectionSpy = spyOn(
+ getColorCollectionModule,
+ 'getDevupColorCollection',
+ ).mockResolvedValue(null)
+
+ uploadFileMock.mockResolvedValue(
+ JSON.stringify({
+ theme: {
+ colors: { Light: { primary: '#112233' } },
+ typography: {
+ heading: {
+ fontFamily: 'Inter',
+ fontSize: '16',
+ letterSpacing: '0.1em',
+ lineHeight: 'normal',
+ textTransform: 'uppercase',
+ textDecoration: 'underline',
+ },
},
- ],
+ },
+ }),
+ )
+
+ const setValueForMode = mock(() => {})
+ const removeMode = mock(() => {})
+ const removeVariable = mock(() => {})
+ const createVariable = mock(
+ () =>
+ ({
+ name: 'primary',
+ setValueForMode,
+ remove: removeVariable,
+ }) as unknown as Variable,
+ )
+
+ const createdCollection = {
+ modes: [] as { modeId: string; name: string }[],
+ addMode: (name: string) => {
+ const id = `${name}-id`
+ createdCollection.modes.push({ modeId: id, name })
+ return id
},
- ]
- ;(figma.getStyleByIdAsync as any).mockResolvedValue({
- id: 'style1',
- name: 'mobile/Title',
- })
- const downloadFileMock = vi
- .spyOn(downloadFileModule, 'downloadFile')
- .mockResolvedValue(undefined)
- await exportDevup('json')
- expect(downloadFileMock).toHaveBeenCalled()
- })
+ removeMode,
+ } as unknown as VariableCollection
- it('should not do anything when devup.theme is not found', async () => {
- vi.spyOn(uploadFileModule, 'uploadFile').mockResolvedValue(
- JSON.stringify({}),
+ const createTextStyleMock = mock(
+ () =>
+ ({
+ name: '',
+ }) as unknown as TextStyle,
)
+
+ const loadFontAsync = mock(() => Promise.resolve())
+ ;(globalThis as { figma?: unknown }).figma = {
+ util: { rgba: (v: unknown) => v },
+ variables: {
+ createVariableCollection: () => createdCollection,
+ getLocalVariablesAsync: async () => [],
+ createVariable,
+ },
+ getLocalTextStylesAsync: async () => [],
+ createTextStyle: createTextStyleMock,
+ loadFontAsync,
+ } as unknown as typeof figma
+
await importDevup('json')
- expect(figma.variables.createVariable).not.toHaveBeenCalled()
- expect(figma.createTextStyle).not.toHaveBeenCalled()
- })
- it('should not do anything when colors is not found', async () => {
- vi.spyOn(uploadFileModule, 'uploadFile').mockResolvedValue(
- JSON.stringify({ theme: {} }),
+ expect(getColorCollectionSpy).toHaveBeenCalled()
+ expect(createdCollection.modes[0]?.name).toBe('Light')
+ expect(createVariable).toHaveBeenCalledWith(
+ 'primary',
+ createdCollection,
+ 'COLOR',
)
- await importDevup('json')
- expect(figma.variables.createVariable).not.toHaveBeenCalled()
+ expect(setValueForMode).toHaveBeenCalledWith('Light-id', '#112233')
+ expect(createTextStyleMock).toHaveBeenCalled()
+ expect(loadFontAsync).toHaveBeenCalled()
})
- it('should not add typography when typography is array and v is falsy', async () => {
- vi.spyOn(uploadFileModule, 'uploadFile').mockResolvedValue(
- JSON.stringify({ theme: { typography: { title: [null, null, null] } } }),
+ test('importDevup handles excel input with modes and removal', async () => {
+ const addModeMock = mock((name: string) => `${name}-id`)
+ getColorCollectionSpy = spyOn(
+ getColorCollectionModule,
+ 'getDevupColorCollection',
+ ).mockResolvedValue({
+ modes: [{ modeId: 'existing', name: 'Old' }],
+ removeMode: mock(() => {}),
+ addMode: addModeMock,
+ } as unknown as VariableCollection)
+
+ uploadXlsxMock.mockResolvedValue({
+ theme: {
+ colors: { Light: { primary: '#111111' } },
+ typography: {
+ title: [
+ {
+ fontFamily: 'Inter',
+ fontStyle: 'italic',
+ fontSize: '18',
+ letterSpacing: '0.2em',
+ lineHeight: 120,
+ textTransform: 'lowercase',
+ textDecoration: 'line-through',
+ },
+ null,
+ ],
+ },
+ },
+ })
+
+ const setValueForMode = mock(() => {})
+ const remove = mock(() => {})
+ const variable = {
+ name: 'unused',
+ setValueForMode,
+ remove,
+ } as unknown as Variable
+
+ const collection = {
+ modes: [{ modeId: 'existing', name: 'Old' }],
+ addMode: mock(() => 'Light-id'),
+ removeMode: mock(() => {}),
+ } as unknown as VariableCollection
+
+ const createVariable = mock(
+ () =>
+ ({
+ name: 'primary',
+ setValueForMode,
+ remove,
+ }) as unknown as Variable,
)
- ;(figma.getLocalTextStylesAsync as any).mockResolvedValue([])
- await importDevup('json')
- expect(figma.createTextStyle).not.toHaveBeenCalled()
+
+ const createTextStyleMock = mock(
+ () =>
+ ({
+ name: '',
+ }) as unknown as TextStyle,
+ )
+
+ const loadFontAsync = mock(() => Promise.resolve())
+
+ ;(globalThis as { figma?: unknown }).figma = {
+ util: { rgba: (v: unknown) => v },
+ variables: {
+ createVariableCollection: () => collection,
+ getLocalVariablesAsync: async () => [variable],
+ createVariable,
+ },
+ getLocalTextStylesAsync: async () => [],
+ createTextStyle: createTextStyleMock,
+ loadFontAsync,
+ } as unknown as typeof figma
+
+ await importDevup('excel')
+
+ expect(addModeMock).toHaveBeenCalledWith('Light')
+ expect(setValueForMode).toHaveBeenCalledWith('Light-id', '#111111')
+ expect(remove).toHaveBeenCalled()
+ expect(createTextStyleMock).toHaveBeenCalled()
})
- it('should cover letterSpacing, lineHeight, textTransform, textDecoration branches', async () => {
- vi.spyOn(uploadFileModule, 'uploadFile').mockResolvedValue(
+ test('importDevup notifies when font load fails and covers letterSpacing/lineHeight', async () => {
+ uploadFileMock.mockResolvedValue(
JSON.stringify({
theme: {
typography: {
title: {
- fontFamily: 'Pretendard',
+ fontFamily: 'Inter',
fontStyle: 'italic',
- fontSize: '20px',
- fontWeight: 700,
- lineHeight: 'normal',
- letterSpacing: '1em',
+ fontSize: '18',
+ letterSpacing: '2px',
+ lineHeight: '20',
textTransform: 'uppercase',
textDecoration: 'underline',
},
@@ -382,9 +606,110 @@ describe('devup/index', () => {
},
}),
)
- ;(figma.getLocalTextStylesAsync as any).mockResolvedValue([])
- ;(figma.createTextStyle as any).mockReturnValue({ name: '' })
+
+ const notifyMock = mock(() => {})
+ const createTextStyleMock = mock(
+ () =>
+ ({
+ name: '',
+ }) as unknown as TextStyle,
+ )
+ const loadFontAsync = mock(() => Promise.reject('font'))
+ spyOn(console, 'error').mockImplementation(() => {})
+
+ ;(globalThis as { figma?: unknown }).figma = {
+ util: { rgba: (v: unknown) => v },
+ variables: {
+ createVariableCollection: () =>
+ ({
+ modes: [],
+ addMode: () => 'm1',
+ removeMode: () => {},
+ }) as unknown as VariableCollection,
+ getLocalVariablesAsync: async () => [],
+ createVariable: () =>
+ ({
+ setValueForMode: () => {},
+ remove: () => {},
+ }) as unknown as Variable,
+ },
+ getLocalTextStylesAsync: async () => [],
+ createTextStyle: createTextStyleMock,
+ loadFontAsync,
+ notify: notifyMock,
+ } as unknown as typeof figma
+
await importDevup('json')
- expect(figma.createTextStyle).toHaveBeenCalled()
+
+ expect(notifyMock).toHaveBeenCalledWith(
+ expect.stringContaining('Failed to create text style'),
+ expect.any(Object),
+ )
+ expect(console.error).toHaveBeenCalledWith(
+ 'Failed to create text style',
+ 'font',
+ )
+ })
+
+ test('importDevup sets typography spacing values', async () => {
+ uploadFileMock.mockResolvedValue(
+ JSON.stringify({
+ theme: {
+ typography: {
+ body: {
+ fontFamily: 'Inter',
+ fontStyle: 'normal',
+ fontSize: '14',
+ letterSpacing: '1px',
+ lineHeight: '18',
+ },
+ },
+ },
+ }),
+ )
+
+ const createTextStyleMock = mock(
+ () =>
+ ({
+ name: '',
+ }) as unknown as TextStyle,
+ )
+ const loadFontAsync = mock(() => Promise.resolve())
+
+ const styleObj = createTextStyleMock() as TextStyle & {
+ letterSpacing?: LetterSpacing
+ lineHeight?: LineHeight
+ textCase?: TextCase
+ textDecoration?: TextDecoration
+ fontSize?: number
+ fontName?: FontName
+ }
+
+ ;(globalThis as { figma?: unknown }).figma = {
+ util: { rgba: (v: unknown) => v },
+ variables: {
+ createVariableCollection: () =>
+ ({
+ modes: [],
+ addMode: () => 'm1',
+ removeMode: () => {},
+ }) as unknown as VariableCollection,
+ getLocalVariablesAsync: async () => [],
+ createVariable: () =>
+ ({
+ setValueForMode: () => {},
+ remove: () => {},
+ }) as unknown as Variable,
+ },
+ getLocalTextStylesAsync: async () => [],
+ createTextStyle: () => styleObj,
+ loadFontAsync,
+ notify: mock(() => {}),
+ } as unknown as typeof figma
+
+ await importDevup('json')
+
+ expect(styleObj.letterSpacing).toMatchObject({ unit: 'PIXELS', value: 100 })
+ expect(styleObj.lineHeight).toMatchObject({ unit: 'PIXELS', value: 18 })
})
})
diff --git a/src/commands/devup/apply-typography.ts b/src/commands/devup/apply-typography.ts
new file mode 100644
index 0000000..6d4c899
--- /dev/null
+++ b/src/commands/devup/apply-typography.ts
@@ -0,0 +1,58 @@
+import type { DevupTypography } from './types'
+
+export async function applyTypography(
+ target: string,
+ typography: DevupTypography,
+ styles: TextStyle[],
+) {
+ const st = styles.find((s) => s.name === target) ?? figma.createTextStyle()
+ st.name = target
+ const fontFamily = {
+ family: typography.fontFamily ?? 'Inter',
+ style: typography.fontStyle === 'italic' ? 'Italic' : 'Regular',
+ }
+
+ try {
+ await figma.loadFontAsync(fontFamily)
+ st.fontName = fontFamily
+ if (typography.fontSize) st.fontSize = parseInt(typography.fontSize, 10)
+ if (typography.letterSpacing) {
+ st.letterSpacing = typography.letterSpacing.endsWith('em')
+ ? {
+ unit: 'PERCENT',
+ value: parseFloat(typography.letterSpacing),
+ }
+ : {
+ unit: 'PIXELS',
+ value: parseFloat(typography.letterSpacing) * 100,
+ }
+ }
+ if (typography.lineHeight) {
+ st.lineHeight =
+ typography.lineHeight === 'normal'
+ ? { unit: 'AUTO' }
+ : typeof typography.lineHeight === 'string'
+ ? {
+ unit: 'PIXELS',
+ value: parseInt(typography.lineHeight, 10),
+ }
+ : {
+ unit: 'PERCENT',
+ value: Math.round(typography.lineHeight / 10) / 10,
+ }
+ }
+ if (typography.textTransform) {
+ st.textCase = typography.textTransform.toUpperCase() as TextCase
+ }
+ if (typography.textDecoration) {
+ st.textDecoration =
+ typography.textDecoration.toUpperCase() as TextDecoration
+ }
+ } catch (error) {
+ console.error('Failed to create text style', error)
+ figma.notify(
+ `Failed to create text style (${target}, ${fontFamily.family} - ${fontFamily.style})`,
+ { error: true },
+ )
+ }
+}
diff --git a/src/commands/devup/build-target-style-names.ts b/src/commands/devup/build-target-style-names.ts
new file mode 100644
index 0000000..f3cda3c
--- /dev/null
+++ b/src/commands/devup/build-target-style-names.ts
@@ -0,0 +1,21 @@
+import type { DevupTypography } from './types'
+
+type TargetTypography = [target: string, typography: DevupTypography]
+const TYPO_PREFIX = ['mobile', '1', 'tablet', '3', 'desktop', '5'] as const
+
+export function buildTargetStyleNames(
+ style: string,
+ value: DevupTypography | (DevupTypography | null)[],
+): TargetTypography[] {
+ const targets: TargetTypography[] = []
+ if (Array.isArray(value)) {
+ value.forEach((typo, idx) => {
+ if (!typo) return
+ const prefix = TYPO_PREFIX[idx] ?? `${idx}`
+ targets.push([`${prefix}/${style}`, typo])
+ })
+ return targets
+ }
+ targets.push([`mobile/${style}`, value])
+ return targets
+}
diff --git a/src/commands/devup/export-devup.ts b/src/commands/devup/export-devup.ts
new file mode 100644
index 0000000..62c6004
--- /dev/null
+++ b/src/commands/devup/export-devup.ts
@@ -0,0 +1,152 @@
+import { downloadFile } from '../../utils/download-file'
+import { isVariableAlias } from '../../utils/is-variable-alias'
+import { optimizeHex } from '../../utils/optimize-hex'
+import { rgbaToHex } from '../../utils/rgba-to-hex'
+import { styleNameToTypography } from '../../utils/style-name-to-typography'
+import { textSegmentToTypography } from '../../utils/text-segment-to-typography'
+import { textStyleToTypography } from '../../utils/text-style-to-typography'
+import { toCamel } from '../../utils/to-camel'
+import { variableAliasToValue } from '../../utils/variable-alias-to-value'
+import type { Devup, DevupTypography } from './types'
+import { downloadDevupXlsx } from './utils/download-devup-xlsx'
+import { getDevupColorCollection } from './utils/get-devup-color-collection'
+
+export async function exportDevup(
+ output: 'json' | 'excel',
+ treeshaking: boolean = true,
+) {
+ const devup: Devup = {}
+
+ const collection = await getDevupColorCollection()
+ if (collection) {
+ for (const mode of collection.modes) {
+ devup.theme ??= {}
+ devup.theme.colors ??= {}
+ const colors: Record = {}
+ devup.theme.colors[mode.name.toLowerCase()] = colors
+ await Promise.all(
+ collection.variableIds.map(async (varId) => {
+ const variable = await figma.variables.getVariableByIdAsync(varId)
+ if (variable === null) return
+ const value = variable.valuesByMode[mode.modeId]
+ if (typeof value === 'boolean' || typeof value === 'number') return
+ if (isVariableAlias(value)) {
+ const nextValue = await variableAliasToValue(value, mode.modeId)
+ if (nextValue === null) return
+ if (typeof nextValue === 'boolean' || typeof nextValue === 'number')
+ return
+ colors[toCamel(variable.name)] = optimizeHex(
+ rgbaToHex(figma.util.rgba(nextValue)),
+ )
+ } else {
+ colors[toCamel(variable.name)] = optimizeHex(
+ rgbaToHex(figma.util.rgba(value)),
+ )
+ }
+ }),
+ )
+ }
+ }
+
+ await figma.loadAllPagesAsync()
+
+ const textStyles = await figma.getLocalTextStylesAsync()
+ const ids = new Set()
+ const styles: Record = {}
+ for (const style of textStyles) {
+ ids.add(style.id)
+ styles[style.name] = style
+ }
+
+ const typography: Record = {}
+ if (treeshaking) {
+ const texts = figma.root.findAllWithCriteria({ types: ['TEXT'] })
+ await Promise.all(
+ texts
+ .filter(
+ (text) =>
+ (typeof text.textStyleId === 'string' && text.textStyleId) ||
+ text.textStyleId === figma.mixed,
+ )
+ .map(async (text) => {
+ for (const seg of text.getStyledTextSegments([
+ 'fontName',
+ 'fontWeight',
+ 'fontSize',
+ 'textDecoration',
+ 'textCase',
+ 'lineHeight',
+ 'letterSpacing',
+ 'fills',
+ 'textStyleId',
+ 'fillStyleId',
+ 'listOptions',
+ 'indentation',
+ 'hyperlink',
+ ])) {
+ if (seg?.textStyleId) {
+ const style = await figma.getStyleByIdAsync(seg.textStyleId)
+
+ if (!(style && ids.has(style.id))) continue
+ const { level, name } = styleNameToTypography(style.name)
+ const typo = textSegmentToTypography(seg)
+ if (typography[name]?.[level]) continue
+ typography[name] ??= [null, null, null, null, null, null]
+ typography[name][level] = typo
+ }
+ }
+ }),
+ )
+ } else {
+ for (const [styleName, style] of Object.entries(styles)) {
+ const { level, name } = styleNameToTypography(styleName)
+ const typo = textStyleToTypography(style)
+ if (typography[name]?.[level]) continue
+ typography[name] ??= [null, null, null, null, null, null]
+ typography[name][level] = typo
+ }
+ }
+
+ for (const [name, style] of Object.entries(styles)) {
+ const { level, name: styleName } = styleNameToTypography(name)
+ if (typography[styleName] && !typography[styleName][level]) {
+ typography[styleName][level] = textStyleToTypography(style)
+ }
+ }
+
+ if (Object.keys(typography).length > 0) {
+ devup.theme ??= {}
+ devup.theme.typography = Object.entries(typography).reduce(
+ (acc, [key, value]) => {
+ const filtered = value.filter((v) => v !== null)
+ if (filtered.length === 0) {
+ return acc
+ }
+ if (filtered.length === 1) {
+ acc[key] = filtered[0]
+ return acc
+ }
+ if (value[0] === null) {
+ acc[key] = [filtered[0]]
+ for (let i = 1; i < value.length; i += 1) {
+ acc[key].push(value[i])
+ }
+ while (acc[key][acc[key].length - 1] === null) {
+ acc[key].pop()
+ }
+ return acc
+ }
+ acc[key] = value
+ return acc
+ },
+ {} as Record,
+ )
+ }
+
+ switch (output) {
+ case 'json':
+ return downloadFile('devup.json', JSON.stringify(devup))
+ case 'excel':
+ return downloadDevupXlsx('devup.xlsx', JSON.stringify(devup))
+ }
+}
diff --git a/src/commands/devup/import-devup.ts b/src/commands/devup/import-devup.ts
new file mode 100644
index 0000000..b715ea0
--- /dev/null
+++ b/src/commands/devup/import-devup.ts
@@ -0,0 +1,72 @@
+import { uploadFile } from '../../utils/upload-file'
+import { applyTypography } from './apply-typography'
+import { buildTargetStyleNames } from './build-target-style-names'
+import type { Devup } from './types'
+import { getDevupColorCollection } from './utils/get-devup-color-collection'
+import { uploadDevupXlsx } from './utils/upload-devup-xlsx'
+
+export async function importDevup(input: 'json' | 'excel') {
+ const devup = await loadDevup(input)
+ await importColors(devup)
+ await importTypography(devup)
+}
+
+async function loadDevup(input: 'json' | 'excel'): Promise {
+ return input === 'json'
+ ? JSON.parse(await uploadFile('.json'))
+ : await uploadDevupXlsx()
+}
+
+async function importColors(devup: Devup) {
+ const colors = devup.theme?.colors
+ if (!colors) return
+
+ const collection =
+ (await getDevupColorCollection()) ??
+ (await figma.variables.createVariableCollection('Devup Colors'))
+
+ const themes = new Set()
+ const colorNames = new Set()
+
+ for (const [theme, value] of Object.entries(colors)) {
+ const modeId =
+ collection.modes.find((mode) => mode.name === theme)?.modeId ??
+ collection.addMode(theme)
+
+ const variables = await figma.variables.getLocalVariablesAsync()
+ for (const [colorKey, colorValue] of Object.entries(value)) {
+ const variable =
+ variables.find((variable) => variable.name === colorKey) ??
+ figma.variables.createVariable(colorKey, collection, 'COLOR')
+
+ variable.setValueForMode(modeId, figma.util.rgba(colorValue))
+ colorNames.add(colorKey)
+ }
+ themes.add(theme)
+ }
+
+ for (const theme of collection.modes.filter(
+ (mode) => !themes.has(mode.name),
+ )) {
+ collection.removeMode(theme.modeId)
+ }
+
+ const variables = await figma.variables.getLocalVariablesAsync()
+ for (const variable of variables.filter((v) => !colorNames.has(v.name))) {
+ variable.remove()
+ }
+}
+
+async function importTypography(devup: Devup) {
+ const typography = devup.theme?.typography
+ if (!typography) return
+
+ const styles = await figma.getLocalTextStylesAsync()
+
+ for (const [style, value] of Object.entries(typography)) {
+ const targetStyleNames = buildTargetStyleNames(style, value)
+ for (const [target, typo] of targetStyleNames) {
+ await applyTypography(target, typo, styles)
+ }
+ }
+}
diff --git a/src/commands/devup/index.ts b/src/commands/devup/index.ts
index 46c43b9..d9e2f07 100644
--- a/src/commands/devup/index.ts
+++ b/src/commands/devup/index.ts
@@ -1,293 +1,2 @@
-import { downloadFile } from '../../utils/download-file'
-import { isVariableAlias } from '../../utils/is-variable-alias'
-import { optimizeHex } from '../../utils/optimize-hex'
-import { rgbaToHex } from '../../utils/rgba-to-hex'
-import { styleNameToTypography } from '../../utils/style-name-to-typography'
-import { textSegmentToTypography } from '../../utils/text-segment-to-typography'
-import { textStyleToTypography } from '../../utils/text-style-to-typography'
-import { toCamel } from '../../utils/to-camel'
-import { uploadFile } from '../../utils/upload-file'
-import { variableAliasToValue } from '../../utils/variable-alias-to-value'
-import { type Devup, DevupTypography } from './types'
-import { downloadDevupXlsx } from './utils/download-devup-xlsx'
-import { getDevupColorCollection } from './utils/get-devup-color-collection'
-import { uploadDevupXlsx } from './utils/upload-devup-xlsx'
-export async function exportDevup(
- output: 'json' | 'excel',
- treeshaking: boolean = true,
-) {
- const devup: Devup = {}
-
- const collection = await getDevupColorCollection()
- if (collection) {
- for (const mode of collection.modes) {
- devup['theme'] ??= {}
- devup['theme']['colors'] ??= {}
- const colors: Record = {}
- devup['theme']['colors'][mode.name.toLowerCase()] = colors
- await Promise.all(
- collection.variableIds.map(async (varId) => {
- const variable = await figma.variables.getVariableByIdAsync(varId)
- if (variable === null) return
- const value = variable.valuesByMode[mode.modeId]
- if (typeof value === 'boolean' || typeof value === 'number') return
- if (isVariableAlias(value)) {
- const nextValue = await variableAliasToValue(value, mode.modeId)
- if (nextValue === null) return
- if (typeof nextValue === 'boolean' || typeof nextValue === 'number')
- return
- colors[toCamel(variable.name)] = optimizeHex(
- rgbaToHex(figma.util.rgba(nextValue)),
- )
- } else {
- colors[toCamel(variable.name)] = optimizeHex(
- rgbaToHex(figma.util.rgba(value)),
- )
- }
- }),
- )
- }
- }
-
- await figma.loadAllPagesAsync()
-
- const textStyles = await figma.getLocalTextStylesAsync()
- const ids = new Set()
- const styles: Record = {}
- for (const style of textStyles) {
- ids.add(style.id)
- styles[style.name] = style
- }
-
- const typography: Record = {}
- if (treeshaking) {
- const texts = figma.root.findAllWithCriteria({ types: ['TEXT'] })
- await Promise.all(
- texts
- .filter(
- (text) =>
- (typeof text.textStyleId === 'string' && text.textStyleId) ||
- text.textStyleId === figma.mixed,
- )
- .map(async (text) => {
- for (const seg of text.getStyledTextSegments([
- 'fontName',
- 'fontWeight',
- 'fontSize',
- 'textDecoration',
- 'textCase',
- 'lineHeight',
- 'letterSpacing',
- 'fills',
- 'textStyleId',
- 'fillStyleId',
- 'listOptions',
- 'indentation',
- 'hyperlink',
- ])) {
- if (seg && seg.textStyleId) {
- const style = await figma.getStyleByIdAsync(seg.textStyleId)
-
- if (!(style && ids.has(style.id))) continue
- const { level, name } = styleNameToTypography(style.name)
- const typo = textSegmentToTypography(seg)
- if (typography[name] && typography[name][level]) continue
- typography[name] ??= [null, null, null, null, null, null]
- typography[name][level] = typo
- }
- }
- }),
- )
- } else {
- for (const [styleName, style] of Object.entries(styles)) {
- const { level, name } = styleNameToTypography(styleName)
- const typo = textStyleToTypography(style)
- if (typography[name] && typography[name][level]) continue
- typography[name] ??= [null, null, null, null, null, null]
- typography[name][level] = typo
- }
- }
-
- for (const [name, style] of Object.entries(styles)) {
- const { level, name: styleName } = styleNameToTypography(name)
- if (typography[styleName] && !typography[styleName][level]) {
- typography[styleName][level] = textStyleToTypography(style)
- }
- }
-
- if (Object.keys(typography).length > 0) {
- devup['theme'] ??= {}
- devup['theme']['typography'] = Object.entries(typography).reduce(
- (acc, [key, value]) => {
- const filtered = value.filter((v) => v !== null)
- if (filtered.length === 0) return acc
- if (filtered.length === 1) {
- acc[key] = filtered[0]
- return acc
- }
- if (value[0] === null) {
- acc[key] = [filtered[0]]
- let init = false
- for (let i = 0; i < value.length; i += 1) {
- if (value[i] === null) {
- if (init) {
- acc[key].push(null)
- } else {
- if (!init) {
- acc[key].push(null)
- init = true
- } else {
- acc[key].push(value[i])
- }
- }
- }
- }
- return acc
- }
- acc[key] = value
- return acc
- },
- {} as Record,
- )
- }
-
- switch (output) {
- case 'json':
- return downloadFile('devup.json', JSON.stringify(devup))
- case 'excel':
- return downloadDevupXlsx('devup.xlsx', JSON.stringify(devup))
- }
-}
-
-export async function importDevup(input: 'json' | 'excel') {
- const devup: Devup =
- input === 'json'
- ? JSON.parse(await uploadFile('.json'))
- : await uploadDevupXlsx()
- if (devup.theme?.colors) {
- const collection =
- (await getDevupColorCollection()) ??
- (await figma.variables.createVariableCollection('Devup Colors'))
- const themes = new Set()
- const colors = new Set()
- for (const [theme, value] of Object.entries(devup.theme.colors)) {
- const modeId =
- collection.modes.find((mode) => mode.name === theme)?.modeId ??
- collection.addMode(theme)
-
- const variables = await figma.variables.getLocalVariablesAsync()
- for (const [colorKey, colorValue] of Object.entries(value)) {
- const variable =
- variables.find((variable) => variable.name === colorKey) ??
- figma.variables.createVariable(colorKey, collection, 'COLOR')
-
- variable.setValueForMode(modeId, figma.util.rgba(colorValue))
- colors.add(colorKey)
- }
- themes.add(theme)
- }
- for (const theme of collection.modes.filter(
- (mode) => !themes.has(mode.name),
- ))
- collection.removeMode(theme.modeId)
-
- const variables = await figma.variables.getLocalVariablesAsync()
- for (const variable of variables.filter(
- (variable) => !colors.has(variable.name),
- ))
- variable.remove()
- }
- if (devup.theme?.typography) {
- const styles = await figma.getLocalTextStylesAsync()
- for (const [style, value] of Object.entries(devup.theme.typography)) {
- const targetStyleNames: [target: string, typography: DevupTypography][] =
- []
- if (Array.isArray(value)) {
- for (const v in value) {
- if (v && value[v]) {
- targetStyleNames.push([
- `${
- {
- 0: 'mobile',
- 1: '1',
- 2: 'tablet',
- 3: '3',
- 4: 'desktop',
- 5: '5',
- }[v]
- }/${style}`,
- value[v],
- ])
- }
- }
- } else {
- targetStyleNames.push([`mobile/${style}`, value])
- }
-
- for (const [target, typography] of targetStyleNames) {
- const st =
- styles.find((s) => s.name === target) ?? figma.createTextStyle()
-
- st.name = target
- const fontFamily = {
- family: typography.fontFamily ?? 'Inter',
- style: typography.fontStyle == 'italic' ? 'Italic' : 'Regular',
- }
- try {
- await figma.loadFontAsync(fontFamily)
- st.fontName = fontFamily
- if (typography.fontSize) {
- st.fontSize = parseInt(typography.fontSize)
- }
-
- if (typography.letterSpacing) {
- if (typography.letterSpacing.endsWith('em')) {
- st.letterSpacing = {
- unit: 'PERCENT',
- value: parseFloat(typography.letterSpacing),
- }
- } else {
- st.letterSpacing = {
- unit: 'PIXELS',
- value: parseFloat(typography.letterSpacing) * 100,
- }
- }
- }
-
- if (typography.lineHeight) {
- if (typography.lineHeight === 'normal') {
- st.lineHeight = {
- unit: 'AUTO',
- }
- } else {
- if (typeof typography.lineHeight === 'string') {
- st.lineHeight = {
- unit: 'PIXELS',
- value: parseInt(typography.lineHeight),
- }
- } else {
- st.lineHeight = {
- unit: 'PERCENT',
- value: Math.round(typography.lineHeight / 10) / 10,
- }
- }
- }
- }
- if (typography.textTransform) {
- st.textCase = typography.textTransform.toUpperCase() as TextCase
- }
- if (typography.textDecoration) {
- st.textDecoration =
- typography.textDecoration.toUpperCase() as TextDecoration
- }
- } catch (error) {
- console.error('Failed to create text style', error)
- figma.notify(
- `Failed to create text style (${target}, ${fontFamily.family} - ${fontFamily.style})`,
- { error: true },
- )
- }
- }
- }
- }
-}
+export { exportDevup } from './export-devup'
+export { importDevup } from './import-devup'
diff --git a/src/commands/devup/utils/__tests__/download-devup-xlsx.test.ts b/src/commands/devup/utils/__tests__/download-devup-xlsx.test.ts
new file mode 100644
index 0000000..74ba2ad
--- /dev/null
+++ b/src/commands/devup/utils/__tests__/download-devup-xlsx.test.ts
@@ -0,0 +1,91 @@
+import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'
+import { downloadDevupXlsx } from '../download-devup-xlsx'
+
+describe('downloadDevupXlsx', () => {
+ let showUIMock: ReturnType
+ let postMessageMock: ReturnType
+ let onmessageHandler: ((message: unknown) => void) | null = null
+
+ beforeEach(() => {
+ showUIMock = mock(() => {})
+ postMessageMock = mock(() => {})
+ onmessageHandler = null
+
+ const uiObj: {
+ onmessage?: (message: unknown) => void
+ postMessage?: (message: unknown) => void
+ } = {}
+
+ Object.defineProperty(uiObj, 'onmessage', {
+ set: (fn: (message: unknown) => void) => {
+ onmessageHandler = fn
+ },
+ get: () => onmessageHandler,
+ configurable: true,
+ })
+
+ uiObj.postMessage = postMessageMock
+
+ ;(globalThis as { figma?: unknown }).figma = {
+ showUI: showUIMock,
+ ui: uiObj,
+ } as unknown as typeof figma
+ })
+
+ afterEach(() => {
+ ;(globalThis as { figma?: unknown }).figma = undefined
+ })
+
+ test('should call showUI with correct HTML string and visible false', () => {
+ downloadDevupXlsx('test.xlsx', '{"theme":{"colors":{},"typography":{}}}')
+ expect(showUIMock).toHaveBeenCalledWith(
+ expect.stringContaining('xlsx-0.20.3'),
+ { visible: false },
+ )
+ expect(showUIMock).toHaveBeenCalledWith(
+ expect.stringContaining('onmessage'),
+ { visible: false },
+ )
+ })
+
+ test('should set onmessage handler and post message', () => {
+ downloadDevupXlsx('test.xlsx', '{"theme":{"colors":{},"typography":{}}}')
+ expect(onmessageHandler).not.toBeNull()
+ expect(postMessageMock).toHaveBeenCalledWith({
+ type: 'download',
+ fileName: 'test.xlsx',
+ data: '{"theme":{"colors":{},"typography":{}}}',
+ })
+ })
+
+ test('should return a promise that resolves when onmessage is called', async () => {
+ const promise = downloadDevupXlsx(
+ 'test.xlsx',
+ '{"theme":{"colors":{},"typography":{}}}',
+ )
+
+ // Simulate message from UI
+ if (onmessageHandler) {
+ onmessageHandler(undefined)
+ }
+
+ await promise
+ expect(postMessageMock).toHaveBeenCalled()
+ })
+
+ test('should handle different file names and data', () => {
+ downloadDevupXlsx(
+ 'devup.xlsx',
+ JSON.stringify({
+ theme: { colors: { light: { primary: '#000' } }, typography: {} },
+ }),
+ )
+ expect(postMessageMock).toHaveBeenCalledWith({
+ type: 'download',
+ fileName: 'devup.xlsx',
+ data: JSON.stringify({
+ theme: { colors: { light: { primary: '#000' } }, typography: {} },
+ }),
+ })
+ })
+})
diff --git a/src/commands/devup/utils/__tests__/get-devup-color-collection.test.ts b/src/commands/devup/utils/__tests__/get-devup-color-collection.test.ts
index cdad41f..ca9b88d 100644
--- a/src/commands/devup/utils/__tests__/get-devup-color-collection.test.ts
+++ b/src/commands/devup/utils/__tests__/get-devup-color-collection.test.ts
@@ -1,13 +1,20 @@
+import { describe, expect, it, mock, vi } from 'bun:test'
import { getDevupColorCollection } from '../get-devup-color-collection'
describe('getDevupColorCollection', () => {
it('should get Devup Color Collection', async () => {
- const getLocalVariableCollectionsAsync = vi.fn()
- ;(globalThis as any).figma = {
+ const getLocalVariableCollectionsAsync = mock(() =>
+ Promise.resolve([
+ {
+ name: 'Devup Colors',
+ },
+ ]),
+ )
+ ;(globalThis as { figma?: unknown }).figma = {
variables: {
getLocalVariableCollectionsAsync,
},
- } as any
+ } as unknown as typeof figma
getLocalVariableCollectionsAsync.mockResolvedValue([
{
name: 'Devup Colors',
@@ -15,16 +22,16 @@ describe('getDevupColorCollection', () => {
])
expect(await getDevupColorCollection()).toEqual({
name: 'Devup Colors',
- })
+ } as unknown as VariableCollection)
})
it('should return null if Devup Color Collection not found', async () => {
const getLocalVariableCollectionsAsync = vi.fn()
- ;(globalThis as any).figma = {
+ ;(globalThis as { figma?: unknown }).figma = {
variables: {
getLocalVariableCollectionsAsync,
},
- } as any
+ } as unknown as typeof figma
getLocalVariableCollectionsAsync.mockResolvedValue([])
expect(await getDevupColorCollection()).toBeNull()
})
diff --git a/src/commands/devup/utils/__tests__/upload-devup-xlsx.test.ts b/src/commands/devup/utils/__tests__/upload-devup-xlsx.test.ts
new file mode 100644
index 0000000..b0bc4f9
--- /dev/null
+++ b/src/commands/devup/utils/__tests__/upload-devup-xlsx.test.ts
@@ -0,0 +1,88 @@
+import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'
+import { uploadDevupXlsx } from '../upload-devup-xlsx'
+
+describe('uploadDevupXlsx', () => {
+ let showUIMock: ReturnType
+ let closeMock: ReturnType
+ let onmessageHandler: ((message: string) => void) | null = null
+
+ beforeEach(() => {
+ showUIMock = mock(() => {})
+ closeMock = mock(() => {})
+ onmessageHandler = null
+
+ const uiObj: {
+ onmessage?: (message: string) => void
+ close?: () => void
+ } = {}
+
+ Object.defineProperty(uiObj, 'onmessage', {
+ set: (fn: (message: string) => void) => {
+ onmessageHandler = fn
+ },
+ get: () => onmessageHandler,
+ configurable: true,
+ })
+
+ uiObj.close = closeMock
+
+ ;(globalThis as { figma?: unknown }).figma = {
+ showUI: showUIMock,
+ ui: uiObj,
+ } as unknown as typeof figma
+ })
+
+ afterEach(() => {
+ ;(globalThis as { figma?: unknown }).figma = undefined
+ })
+
+ test('should call showUI with correct HTML string', () => {
+ uploadDevupXlsx()
+ expect(showUIMock).toHaveBeenCalledWith(
+ expect.stringContaining('accept=".xlsx"'),
+ )
+ expect(showUIMock).toHaveBeenCalledWith(
+ expect.stringContaining('xlsx-0.20.3'),
+ )
+ })
+
+ test('should resolve with parsed JSON when message is received', async () => {
+ const testData = { theme: { colors: {}, typography: {} } }
+ const promise = uploadDevupXlsx()
+
+ // Simulate message from UI
+ if (onmessageHandler) {
+ onmessageHandler(JSON.stringify(testData))
+ }
+
+ const result = await promise
+ expect(closeMock).toHaveBeenCalled()
+ expect(result).toEqual(testData)
+ })
+
+ test('should handle message with colors and typography', async () => {
+ const testData = {
+ theme: {
+ colors: {
+ light: {
+ primary: '#000000',
+ },
+ },
+ typography: {
+ heading: {
+ fontFamily: 'Arial',
+ fontSize: 24,
+ },
+ },
+ },
+ }
+ const promise = uploadDevupXlsx()
+
+ if (onmessageHandler) {
+ onmessageHandler(JSON.stringify(testData))
+ }
+
+ const result = await promise
+ expect(result).toEqual(testData)
+ })
+})
diff --git a/src/commands/devup/utils/download-devup-xlsx.ts b/src/commands/devup/utils/download-devup-xlsx.ts
index 67f3aa7..8e7848f 100644
--- a/src/commands/devup/utils/download-devup-xlsx.ts
+++ b/src/commands/devup/utils/download-devup-xlsx.ts
@@ -39,17 +39,14 @@ function downloadFileUi() {
}
workbook.Sheets['Colors'] = XLSX.utils.aoa_to_sheet(colors)
- console.log("devup.theme.typography", JSON.stringify(devup.theme.typography))
const typographyKeys = Object.keys(devup.theme.typography);
const typography = [['Name', 'Level', 'fontFamily', 'fontStyle', 'fontWeight', 'fontSize', 'lineHeight', 'letterSpacing']]
if (typographyKeys.length > 0) {
for (const typographyKey of typographyKeys) {
const typographyValue = Array.isArray(devup.theme.typography[typographyKey]) ? devup.theme.typography[typographyKey] : [devup.theme.typography[typographyKey]];
- console.log("typographyValue", typographyValue)
for (let i = 0; i < typographyValue.length; i++) {
const value = typographyValue[i];
if (value) {
- console.log("value", value)
typography.push([typographyKey, i, value.fontFamily, value.fontStyle, value.fontWeight, value.fontSize, value.lineHeight, value.letterSpacing])
}
}
diff --git a/src/utils.ts b/src/utils.ts
index 43d1ec6..fa4a640 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,26 +1,25 @@
-import { rgbaToHex } from './utils/rgba-to-hex'
import { toCamel } from './utils/to-camel'
import { toPascal } from './utils/to-pascal'
export async function propsToPropsWithTypography(
- props: Record,
+ props: Record,
textStyleId: string,
) {
- const ret: Record = { ...props }
- delete ret['w']
- delete ret['h']
+ const ret: Record = { ...props }
+ delete ret.w
+ delete ret.h
const styles = await figma.getLocalTextStylesAsync()
if (textStyleId && styles.find((style) => style.id === textStyleId)) {
const style = await figma.getStyleByIdAsync(textStyleId)
if (style) {
const split = style.name.split('/')
- ret['typography'] = toCamel(split[split.length - 1])
- delete ret['fontFamily']
- delete ret['fontSize']
- delete ret['fontWeight']
- delete ret['fontStyle']
- delete ret['letterSpacing']
- delete ret['lineHeight']
+ ret.typography = toCamel(split[split.length - 1])
+ delete ret.fontFamily
+ delete ret.fontSize
+ delete ret.fontWeight
+ delete ret.fontStyle
+ delete ret.letterSpacing
+ delete ret.lineHeight
}
}
return ret
@@ -38,33 +37,3 @@ export function getComponentName(node: SceneNode) {
)
return toPascal(node.name)
}
-
-export const colorFromFills = async (
- fills:
- | ReadonlyArray<
- Paint & {
- boundVariables?: { color: VariableAlias }
- color?: RGB
- }
- >
- | undefined,
-): Promise => {
- const fill = fills?.find((fill) => fill.visible)
- if (fill && fill.color) {
- if (fill.boundVariables?.color?.id) {
- const variable = await figma.variables.getVariableByIdAsync(
- fill.boundVariables.color.id as string,
- )
- if (variable?.name) return `$${variable.name}`
- }
- if (fill.opacity === 0) return 'transparent'
-
- return rgbaToHex(
- figma.util.rgba({
- ...fill.color,
- a: fill.opacity,
- }),
- )
- }
- return ''
-}
diff --git a/src/utils/__tests__/download-file.test.ts b/src/utils/__tests__/download-file.test.ts
index afbdc30..fef0fc1 100644
--- a/src/utils/__tests__/download-file.test.ts
+++ b/src/utils/__tests__/download-file.test.ts
@@ -1,18 +1,19 @@
+import { describe, expect, mock, test } from 'bun:test'
import { downloadFile } from '../download-file'
describe('downloadFile', () => {
- it('should download file', () => {
- const showUI = vi.fn()
+ test('should download file', () => {
+ const showUI = mock(() => {})
- const postMessage = vi.fn()
+ const postMessage = mock(() => {})
const obj = {
postMessage,
}
- ;(globalThis as any).figma = {
+ ;(globalThis as { figma?: unknown }).figma = {
showUI,
ui: obj,
- } as any
+ } as unknown as typeof figma
downloadFile('filename.txt', 'text')
expect(showUI).toHaveBeenCalledWith(expect.any(String), {
visible: false,
diff --git a/src/utils/__tests__/extract-key-value-from-css-var.test.ts b/src/utils/__tests__/extract-key-value-from-css-var.test.ts
index e550b1b..431dc1d 100644
--- a/src/utils/__tests__/extract-key-value-from-css-var.test.ts
+++ b/src/utils/__tests__/extract-key-value-from-css-var.test.ts
@@ -1,7 +1,8 @@
+import { describe, expect, test } from 'bun:test'
import { extractKeyValueFromCssVar } from '../extract-key-value-from-css-var'
describe('extractKeyValueFromCssVar', () => {
- it('should extract value from css var', () => {
+ test('should extract value from css var', () => {
expect(extractKeyValueFromCssVar('var(--primary)')).toBeUndefined()
expect(extractKeyValueFromCssVar('var(--primary, red)')).toEqual([
'$primary',
diff --git a/src/utils/__tests__/is-variable-alias.test.ts b/src/utils/__tests__/is-variable-alias.test.ts
index 96e1ab7..0d76020 100644
--- a/src/utils/__tests__/is-variable-alias.test.ts
+++ b/src/utils/__tests__/is-variable-alias.test.ts
@@ -1,7 +1,8 @@
+import { describe, expect, test } from 'bun:test'
import { isVariableAlias } from '../is-variable-alias'
describe('isVariableAlias', () => {
- it('should check variableAlias', () => {
+ test('should check variableAlias', () => {
expect(
isVariableAlias({
r: 0,
diff --git a/src/utils/__tests__/optimize-rgba-func.test.ts b/src/utils/__tests__/optimize-rgba-func.test.ts
index cdeec31..077f84f 100644
--- a/src/utils/__tests__/optimize-rgba-func.test.ts
+++ b/src/utils/__tests__/optimize-rgba-func.test.ts
@@ -1,7 +1,8 @@
+import { describe, expect, test } from 'bun:test'
import { optimizeRgbaFunc } from '../optimize-rgba-func'
describe('optimizeRgbaFunc', () => {
- it('rgba()λ₯Ό 16μ§μλ‘ λ³ννλ€', () => {
+ test('converts rgba() to hex', () => {
expect(optimizeRgbaFunc('rgba(255, 0, 0, 1)')).toBe('#F00')
expect(optimizeRgbaFunc('rgba(0, 255, 0, 0.5)')).toBe('#00FF0080')
expect(optimizeRgbaFunc('rgba(0, 0, 255, 0.25)')).toBe('#0000FF40')
@@ -9,14 +10,14 @@ describe('optimizeRgbaFunc', () => {
expect(optimizeRgbaFunc('rgba(12, 34, 56, 0.8)')).toBe('#0C2238CC')
})
- it('rgb to hex', () => {
+ test('rgb to hex', () => {
expect(optimizeRgbaFunc('rgb(255, 0, 0)')).toBe('#F00')
expect(optimizeRgbaFunc('rgb(0, 255, 0)')).toBe('#0F0')
expect(optimizeRgbaFunc('rgb(0, 0, 255)')).toBe('#00F')
expect(optimizeRgbaFunc('rgb(12, 34, 56)')).toBe('#0C2238')
})
- it('λ¬Έμμ΄ λ΄ μ¬λ¬ rgba/rgbλ₯Ό λͺ¨λ λ³ννλ€', () => {
+ test('converts multiple rgba/rgb in string', () => {
expect(
optimizeRgbaFunc(
'background: linear-gradient(rgba(255,0,0,1), rgb(0,255,0));',
@@ -24,7 +25,7 @@ describe('optimizeRgbaFunc', () => {
).toBe('background: linear-gradient(#F00, #0F0);')
})
- it('rgba/rgbκ° μμΌλ©΄ μλ³Έμ λ°ννλ€', () => {
+ test('returns original when rgba/rgb not present', () => {
expect(optimizeRgbaFunc('none')).toBe('none')
expect(optimizeRgbaFunc('background: #fff;')).toBe('background: #fff;')
})
diff --git a/src/utils/__tests__/rgba-to-hex.test.ts b/src/utils/__tests__/rgba-to-hex.test.ts
index 0355603..b0f63da 100644
--- a/src/utils/__tests__/rgba-to-hex.test.ts
+++ b/src/utils/__tests__/rgba-to-hex.test.ts
@@ -1,7 +1,8 @@
+import { describe, expect, test } from 'bun:test'
import { rgbaToHex } from '../rgba-to-hex'
describe('rgbaToHex', () => {
- it('should convert rgba to hex', () => {
+ test('should convert rgba to hex', () => {
expect(
rgbaToHex({
r: 0,
diff --git a/src/utils/__tests__/style-name-to-typography.test.ts b/src/utils/__tests__/style-name-to-typography.test.ts
index 89c89e4..340b5ca 100644
--- a/src/utils/__tests__/style-name-to-typography.test.ts
+++ b/src/utils/__tests__/style-name-to-typography.test.ts
@@ -1,7 +1,8 @@
+import { describe, expect, test } from 'bun:test'
import { styleNameToTypography } from '../style-name-to-typography'
describe('styleNameToTypography', () => {
- it('should convert styleName to Typography', () => {
+ test('should convert styleName to Typography', () => {
expect(styleNameToTypography('mobile/font')).toEqual({
level: 0,
name: 'font',
diff --git a/src/utils/__tests__/text-segment-to-typography.test.ts b/src/utils/__tests__/text-segment-to-typography.test.ts
index fbb131b..2247cd4 100644
--- a/src/utils/__tests__/text-segment-to-typography.test.ts
+++ b/src/utils/__tests__/text-segment-to-typography.test.ts
@@ -1,7 +1,8 @@
+import { describe, expect, test } from 'bun:test'
import { textSegmentToTypography } from '../text-segment-to-typography'
describe('textSegmentToTypography', () => {
- it('should convert variableAlias to value', async () => {
+ test('should convert variableAlias to value', async () => {
expect(
textSegmentToTypography({
fontName: {
diff --git a/src/utils/__tests__/text-style-to-typography.test.ts b/src/utils/__tests__/text-style-to-typography.test.ts
new file mode 100644
index 0000000..a608d3e
--- /dev/null
+++ b/src/utils/__tests__/text-style-to-typography.test.ts
@@ -0,0 +1,44 @@
+import { describe, expect, test } from 'bun:test'
+import { textStyleToTypography } from '../text-style-to-typography'
+
+function makeStyle(styleName: string): TextStyle {
+ return {
+ id: 'style',
+ name: 'style',
+ description: '',
+ remote: false,
+ documentationLinks: [],
+ key: '',
+ fontName: { family: 'Pretendard', style: styleName },
+ fontSize: 16,
+ textDecoration: 'NONE',
+ textCase: 'ORIGINAL',
+ paragraphIndent: 0,
+ paragraphSpacing: 0,
+ textAlignHorizontal: 'LEFT',
+ textAlignVertical: 'TOP',
+ lineHeight: { unit: 'AUTO' },
+ letterSpacing: { unit: 'PIXELS', value: 0 },
+ } as unknown as TextStyle
+}
+
+describe('textStyleToTypography', () => {
+ test.each([
+ ['Thin', 100],
+ ['Extra Light', 200],
+ ['Light', 300],
+ ['Regular', 400],
+ ['normal', 400],
+ ['Medium', 500],
+ ['Semibold', 600],
+ ['Bold', 700],
+ ['Extra Bold', 800],
+ ['Black', 900],
+ ['Heavy', 900],
+ ['750', 750],
+ ['UnknownWeight', 400],
+ ])('maps %s to fontWeight %d', (styleName, expected) => {
+ const result = textStyleToTypography(makeStyle(styleName))
+ expect(result.fontWeight).toBe(expected)
+ })
+})
diff --git a/src/utils/__tests__/to-camel.test.ts b/src/utils/__tests__/to-camel.test.ts
index 5329336..295f89c 100644
--- a/src/utils/__tests__/to-camel.test.ts
+++ b/src/utils/__tests__/to-camel.test.ts
@@ -1,7 +1,8 @@
+import { describe, expect, test } from 'bun:test'
import { toCamel } from '../to-camel'
describe('toCamel', () => {
- it('should convert camel', () => {
+ test('should convert camel', () => {
expect(toCamel('to-camel')).toBe('toCamel')
expect(toCamel('to/camel')).toBe('toCamel')
expect(toCamel('toCamel')).toBe('toCamel')
diff --git a/src/utils/__tests__/to-pascal.test.ts b/src/utils/__tests__/to-pascal.test.ts
index 4e42d2e..7880ce6 100644
--- a/src/utils/__tests__/to-pascal.test.ts
+++ b/src/utils/__tests__/to-pascal.test.ts
@@ -1,7 +1,8 @@
+import { describe, expect, test } from 'bun:test'
import { toPascal } from '../to-pascal'
describe('toPascal', () => {
- it('should convert to pascal case', () => {
+ test('should convert to pascal case', () => {
expect(toPascal('to-camel')).toBe('ToCamel')
expect(toPascal('to/camel')).toBe('ToCamel')
expect(toPascal('toCamel')).toBe('ToCamel')
diff --git a/src/utils/__tests__/upload-file.test.ts b/src/utils/__tests__/upload-file.test.ts
index 957a9ea..be2ab27 100644
--- a/src/utils/__tests__/upload-file.test.ts
+++ b/src/utils/__tests__/upload-file.test.ts
@@ -1,42 +1,44 @@
+import { describe, expect, mock, test } from 'bun:test'
import { uploadFile } from '../upload-file'
describe('uploadFile', () => {
- it('should upload file', () => {
- const showUI = vi.fn()
+ test('should upload file', () => {
+ const showUI = mock(() => {})
const obj = {}
- ;(globalThis as any).figma = {
+ ;(globalThis as { figma?: unknown }).figma = {
showUI,
ui: obj,
- } as any
+ } as unknown as typeof figma
uploadFile('.txt')
expect(showUI).toHaveBeenCalledWith(expect.stringContaining('.txt'))
})
- it('should resolve with decoded value and close UI on message', async () => {
- const showUI = vi.fn()
- const close = vi.fn()
- const onmessageSetter = vi.fn()
- let onmessageHandler: any = null
- const obj: any = {}
+ test('should resolve with decoded value and close UI on message', async () => {
+ const showUI = mock(() => {})
+ const close = mock(() => {})
+ const onmessageSetter = mock(() => {})
+ let onmessageHandler: ((data: string) => void) | null = null
+ const obj: { onmessage?: (data: string) => void; close?: () => void } = {}
Object.defineProperty(obj, 'onmessage', {
set: (fn) => {
onmessageHandler = fn
- onmessageSetter(fn)
+ onmessageSetter()
},
get: () => onmessageHandler,
configurable: true,
})
- const base64Decode = vi.fn(() => [65, 66, 67]) // 'ABC'
- ;(globalThis as any).figma = {
+ const base64Decode = mock(() => [65, 66, 67]) // 'ABC'
+ ;(globalThis as { figma?: unknown }).figma = {
showUI,
ui: obj,
base64Decode,
- }
+ } as unknown as typeof figma
obj.close = close
const promise = uploadFile('.txt')
- // onmessageμ΄ νΈμΆλμμ λ
- onmessageHandler('dummy')
+ // invoke onmessage
+ // biome-ignore lint/style/noNonNullAssertion: onmessageHandler is set
+ onmessageHandler!('dummy')
const result = await promise
expect(close).toHaveBeenCalled()
expect(base64Decode).toHaveBeenCalledWith('dummy')
diff --git a/src/utils/__tests__/variable-alias-to-value.test.ts b/src/utils/__tests__/variable-alias-to-value.test.ts
index c0c6d10..389918a 100644
--- a/src/utils/__tests__/variable-alias-to-value.test.ts
+++ b/src/utils/__tests__/variable-alias-to-value.test.ts
@@ -1,17 +1,20 @@
+import { beforeEach, describe, expect, mock, test } from 'bun:test'
import { variableAliasToValue } from '../variable-alias-to-value'
beforeEach(() => {
- vi.clearAllMocks()
+ // Clear mocks if needed
})
describe('variableAliasToValue', () => {
- const getVariableByIdAsync = vi.fn()
- ;(globalThis as any).figma = {
+ const getVariableByIdAsync = mock(
+ (): Promise => Promise.resolve(null),
+ )
+ ;(globalThis as { figma?: unknown }).figma = {
variables: {
getVariableByIdAsync,
},
- } as any
- it('should convert variableAlias to value', async () => {
+ } as unknown as typeof figma
+ test('should convert variableAlias to value', async () => {
getVariableByIdAsync.mockResolvedValue(null)
expect(
await variableAliasToValue(
@@ -30,12 +33,12 @@ describe('variableAliasToValue', () => {
type: 'VARIABLE_ALIAS',
},
},
- })
+ } as unknown as Variable)
getVariableByIdAsync.mockResolvedValueOnce({
valuesByMode: {
modeId: 'value',
},
- })
+ } as unknown as Variable)
expect(
await variableAliasToValue(
{
diff --git a/src/utils/extract-key-value-from-css-var.ts b/src/utils/extract-key-value-from-css-var.ts
index d757bce..44668dc 100644
--- a/src/utils/extract-key-value-from-css-var.ts
+++ b/src/utils/extract-key-value-from-css-var.ts
@@ -4,5 +4,5 @@ export function extractKeyValueFromCssVar(
const [key, value] = cssVar.split(',')
const resValue = value?.split(')')[0].trim()
const resKey = key?.split('(--')[1].trim()
- if (resKey && resValue) return ['$' + resKey, resValue]
+ if (resKey && resValue) return [`$${resKey}`, resValue]
}
diff --git a/src/utils/optimize-rgba-func.ts b/src/utils/optimize-rgba-func.ts
index 1c751f1..d28d42c 100644
--- a/src/utils/optimize-rgba-func.ts
+++ b/src/utils/optimize-rgba-func.ts
@@ -5,7 +5,7 @@ const rgbaRegex =
export function optimizeRgbaFunc(value: string) {
const match = value.replace(rgbaRegex, (_, r, g, b, __, a = 1) => {
return optimizeHex(
- `#${parseInt(r).toString(16).padStart(2, '0')}${parseInt(g).toString(16).padStart(2, '0')}${parseInt(b).toString(16).padStart(2, '0')}${Math.round(
+ `#${parseInt(r, 10).toString(16).padStart(2, '0')}${parseInt(g, 10).toString(16).padStart(2, '0')}${parseInt(b, 10).toString(16).padStart(2, '0')}${Math.round(
a * 255,
)
.toString(16)
diff --git a/src/utils/style-name-to-typography.ts b/src/utils/style-name-to-typography.ts
index e1ff48b..ed8e274 100644
--- a/src/utils/style-name-to-typography.ts
+++ b/src/utils/style-name-to-typography.ts
@@ -13,8 +13,9 @@ export function styleNameToTypography(name: string): {
return { level: 0, name: toCamel(name.slice(7)) }
if (lower.includes('/')) {
const [type, _name] = name.split('/')
- const typeNumber = parseInt(type)
- if (!isNaN(typeNumber)) return { level: typeNumber, name: toCamel(_name) }
+ const typeNumber = parseInt(type, 10)
+ if (!Number.isNaN(typeNumber))
+ return { level: typeNumber, name: toCamel(_name) }
}
return { level: 0, name: toCamel(name) }
diff --git a/src/utils/text-segment-to-typography.ts b/src/utils/text-segment-to-typography.ts
index 5f0e75e..a420fa8 100644
--- a/src/utils/text-segment-to-typography.ts
+++ b/src/utils/text-segment-to-typography.ts
@@ -1,4 +1,4 @@
-import { DevupTypography } from '../commands/devup/types'
+import type { DevupTypography } from '../commands/devup/types'
export function textSegmentToTypography(
segment: Pick<
@@ -16,7 +16,7 @@ export function textSegmentToTypography(
fontFamily: segment.fontName.family,
fontStyle: segment.fontName.style.includes('Italic') ? 'italic' : undefined,
fontWeight: segment.fontWeight,
- fontSize: segment.fontSize + 'px',
+ fontSize: `${segment.fontSize}px`,
textDecoration: {
NONE: undefined,
UNDERLINE: 'underline',
@@ -31,10 +31,10 @@ export function textSegmentToTypography(
? 'normal'
: segment.lineHeight.unit === 'PERCENT'
? Math.round(segment.lineHeight.value / 10) / 10
- : segment.lineHeight.value + 'px',
+ : `${segment.lineHeight.value}px`,
letterSpacing:
segment.letterSpacing.unit === 'PERCENT'
? `${Math.round(segment.letterSpacing.value) / 100}em`
- : segment.letterSpacing.value + 'px',
+ : `${segment.letterSpacing.value}px`,
}
}
diff --git a/src/utils/text-style-to-typography.ts b/src/utils/text-style-to-typography.ts
index eaa0b7b..929d15f 100644
--- a/src/utils/text-style-to-typography.ts
+++ b/src/utils/text-style-to-typography.ts
@@ -1,4 +1,4 @@
-import { DevupTypography } from '../commands/devup/types'
+import type { DevupTypography } from '../commands/devup/types'
import { textSegmentToTypography } from './text-segment-to-typography'
import { toCamel } from './to-camel'
@@ -36,10 +36,8 @@ function getFontWeight(weight: string): number {
case 'black':
case 'heavy':
return 900
- default: {
- const weightNumber = parseInt(weight)
- if (!isNaN(weightNumber)) return weightNumber
- return 400
- }
}
+
+ const weightNumber = Number.parseInt(weight, 10)
+ return Number.isNaN(weightNumber) ? 400 : weightNumber
}
diff --git a/tsconfig.json b/tsconfig.json
index 64a3d1c..76793cf 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,23 +1,15 @@
{
"compilerOptions": {
- "target": "ESNext",
- "types": [
- "vitest/importMeta",
- "vitest/globals",
- "@figma/plugin-typings"
- ],
- "lib": [
- "ESNext"
- ],
+ "target": "es2015",
+ "types": ["@figma/plugin-typings", "bun"],
+ "lib": ["ESNext"],
"strict": true,
"module": "ESNext",
"outDir": "./dist",
"isolatedModules": true,
"moduleResolution": "node",
- "esModuleInterop": true
+ "esModuleInterop": true,
+ "skipLibCheck": true
},
- "include": [
- "./src",
- "./rspack.config.js"
- ]
-}
\ No newline at end of file
+ "include": ["./src"]
+}
diff --git a/ui.html b/ui.html
index 0f30092..e7a4caf 100644
--- a/ui.html
+++ b/ui.html
@@ -19,10 +19,10 @@ Devup Design System Creator
parent.postMessage({ pluginMessage: { type: 'close' } }, '*')
}
-onmessage = (event) => {
+window.onmessage = (event) => {
const message = event.data.pluginMessage;
switch(message.type) {
- case 'download':
+ case 'download': {
const a = document.createElement('a');
const { data, name } = message;
a.href = URL.createObjectURL(new File([data], name, { type: 'application/json' }));
@@ -30,6 +30,7 @@ Devup Design System Creator
a.click();
URL.revokeObjectURL(a.href);
break;
+ }
}
};
diff --git a/vitest.config.ts b/vitest.config.ts
deleted file mode 100644
index 8662816..0000000
--- a/vitest.config.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { defineConfig } from 'vitest/config'
-
-export default defineConfig({
- test: {
- coverage: {
- provider: 'v8',
- include: ['src/**'],
- exclude: ['src/**/types.ts', 'src/**/__tests__'],
- reporter: ['text', 'json', 'html'],
- },
- globals: true,
- },
-})