diff --git a/.changeset/cuddly-walls-pull.md b/.changeset/cuddly-walls-pull.md new file mode 100644 index 00000000..f0e96015 --- /dev/null +++ b/.changeset/cuddly-walls-pull.md @@ -0,0 +1,6 @@ +--- +"@cobalt-ui/plugin-css": patch +"@cobalt-ui/utils": patch +--- + +Move glob matching to @cobalt-ui/utils diff --git a/.changeset/ninety-ducks-explain.md b/.changeset/ninety-ducks-explain.md new file mode 100644 index 00000000..605c98b8 --- /dev/null +++ b/.changeset/ninety-ducks-explain.md @@ -0,0 +1,5 @@ +--- +"@cobalt-ui/cli": patch +--- + +Remove accidental console.log diff --git a/.changeset/sharp-parents-poke.md b/.changeset/sharp-parents-poke.md new file mode 100644 index 00000000..13504e06 --- /dev/null +++ b/.changeset/sharp-parents-poke.md @@ -0,0 +1,6 @@ +--- +"@cobalt-ui/core": minor +"@cobalt-ui/cli": minor +--- + +Add Figma Variable support natively diff --git a/docs/integrations/figma.md b/docs/integrations/figma.md index d499935d..ba081457 100644 --- a/docs/integrations/figma.md +++ b/docs/integrations/figma.md @@ -4,17 +4,152 @@ title: Figma Integration # Figma Integration -Because Figma doesn’t have a way to export the [Design Tokens Community Group format (DTCG)](https://designtokens.org) directly, you’ll need a plugin to export your styles to the DTCG format. +Cobalt supports the following export methods from Figma: -The plugin we recommend for now is [Tokens Studio for Figma](https://tokens.studio). Though it doesn’t support DTCG directly either, it does allow you to export your design tokens in a format Cobalt can read. +1. **Styles**: no support¹. Variables are recommended. +2. **Variables**: [see docs](#figma-variables) +3. **Tokens Studio**: [see docs](#tokens-studio) ::: info -This only allows syncing _from_ Figma. Syncing _to_ Figma isn’t possible today, but the Cobalt team is actively building something to make this possible. Stay tuned! 📺 +¹ Beta versions of Cobalt had Styles support, before Variables were announced. But Figma’s API made it not only difficult to convert Styles to DTCG, it also required more setup and headache for the designer and developer. However, Variables were built with the DTCG spec in mind, and couldn’t be easier. ::: -## Exporting from Tokens Studio +## Figma Variables + +::: warning + +Using the Figma Variables API currently [requires an Enterprise plan](https://www.figma.com/developers/api#variables) in Figma. + +::: + +[Figma Variables](https://help.figma.com/hc/en-us/articles/15339657135383-Guide-to-variables-in-Figma) are currently in beta, but were designed to match the DTCG token format 1:1, and are expected to follow changes. Currently, the supported token types are: + +| Figma Type | DTCG Type | Notes | +| :--------: | :---------: | :------------------------------ | +| `color` | `color` | Coverts to sRGB Hex by default. | +| `number` | `dimension` | Uses `px` by default. | +| `string` | | Ignored if no `type` specified. | +| `boolean` | | Ignored if no `type` specified. | + +_Note: [typography variables](https://help.figma.com/hc/en-us/articles/4406787442711#variables) have been announced, but aren’t released yet. Cobalt will add support when they arrive._ + +### Setup + +In your `tokens.config.js` file, add the Figma [share URL](https://help.figma.com/hc/en-us/articles/360040531773-Share-files-and-prototypes) as a token source: + +```ts +/** @type {import('@cobalt-ui/core').Config} */ +export default { + tokens: ['https://www.figma.com/file/OkPWSU0cusQTumCNno7dm8/Design-System?…'], +}; +``` + +Next, you’ll need to create a [Figma Access Token](https://www.figma.com/developers/api#access-tokens) with the `file:read` and `file_variables:read` scopes and expose it as `FIGMA_ACCESS_TOKEN` in your `.zshrc` or `.bashrc` file (or in CI you can add this to [GitHub Actions Secrets](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions)) + +```sh +export FIGMA_API_TOKEN=abc123… +``` + +Then run `co build` as you would normally, and Cobalt will operate as if the Variables pulled from Figma existed in a local `tokens.json` file. + +### Overrides + +Figma Variables can be a **Color**, **Number**, **String**, or **Boolean.** Color translates directly to the DTCG [Color](/tokens/color) type, so those will work automatically. But for everything else, you’ll need to set up overrides to specify what token type each Figma Variable should be. To do so, specify selectors in a mapping in `figma.overrides` where the key is a glob pattern (or specific ID), and the value is an object with your desired DTCG type: + +```ts +/** @type {import('@cobalt-ui/core').Config} */ +export default { + tokens: ['https://www.figma.com/file/OkPWSU0cusQTumCNno7dm8/Design-System?…'], + figma: { + overrides: { + 'size/*': {$type: 'dimension'}, + 'timing/*': {$type: 'duration'}, + }, + }, +}; +``` + +::: tip + +If both a glob pattern and specific ID are provided, the specific ID takes priority. + +::: + +#### Advanced Overrides + +`figma.overrides` also accepts 2 callback utilities to provide futher control over transforming Variables: + +##### `rename()` + +By default, tokens will keep the same name as your Figma Variables, but with `/` converted into `.`, e.g. `color/base/blue/500` → `color.base.blue.500`. But to rename certain tokens, you can provide a `transformID()` utility: + +```ts +/** @type {import('@cobalt-ui/core').Config} */ +export default { + tokens: ['https://www.figma.com/file/OkPWSU0cusQTumCNno7dm8/Design-System?…'], + figma: { + overrides: { + 'color/*': { + // rename color/base/purple → color/base/violet + rename(id) { + return id.replace('color/base/purple', 'color/base/violet'); + }, + }, + }, + }, +}; +``` + +You can choose to keep the `/`s from Figma, or convert to `.` separators like DTCG requires; up to you. They both work the same way. + +::: tip + +If you return `undefined` or an empty string, it’ll keep its original name. + +::: + +##### `transform()` + +This is useful when either `$type` isn’t enough, or you want to provide additional conversions. Here, for example, is how you’d take `px`-based number Variables and convert to `rem`s: + +```ts +/** @type {import('@cobalt-ui/core').Config} */ +export default { + tokens: ['https://www.figma.com/file/OkPWSU0cusQTumCNno7dm8/Design-System?…'], + figma: { + overrides: { + 'size/*': { + $type: 'dimension', + // convert px → rem + transform({variable, collection, mode}) { + const rawValue = variable.valuesByMode[mode.modeId]; + if (typeof rawValue === 'number') { + return `${rawValue / 16}rem`; + } + // remember rawValue may be an alias of another Variable! + // in that case, `typeof rawValue === "object" && rawValue.type === 'VARIABLE_ALIAS'` + }, + }, + }, + }, +}; +``` + +::: info + +`transform()` will only run a maximum of 1× per variable (you can’t do multiple runs with multiple matching globs). + +::: + +::: tip + +You can even create aliases on-the-fly by either returning a DTCG alias string `"{color.base.blue}"`, or a Figma Variable alias type (`{ type: "VARIABLE_ALIAS", id: "xxxxxxx…" }`). + +::: + +## Tokens Studio Once your design tokens are in Tokens Studio ([docs](https://docs.tokens.studio/tokens/creating-tokens)), use [any of the approved sync methods](https://docs.tokens.studio/sync/sync) to export a `tokens.json` file. Then use Cobalt as you would normally: @@ -31,7 +166,7 @@ export default { Once your sync method is set up, it should be a snap to re-export that `tokens.json` file every time something updates. -## Support +### Support | Tokens Studio Type | Supported | Notes | | :-------------------------------------------------------------------------------- | :-------: | :----------------------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore index 41b9df30..e60bc89c 100644 --- a/packages/cli/.gitignore +++ b/packages/cli/.gitignore @@ -1,3 +1,5 @@ **/tokens/**/* test/fixtures/bundle-default/given +test/fixtures/figma-error/given.json +test/fixtures/figma-success/given.jso test/fixtures/style-dictionary/given.json diff --git a/packages/cli/bin/cli.js b/packages/cli/bin/cli.js index a5bb0738..1092ffb1 100755 --- a/packages/cli/bin/cli.js +++ b/packages/cli/bin/cli.js @@ -57,7 +57,7 @@ const flags = parser(args, { }); /** `tokens` CLI command */ -async function main() { +export default async function main() { const start = performance.now(); // --- @@ -191,7 +191,6 @@ async function main() { process.exit(1); } - console.log(process.argv, args); const input = JSON.parse(fs.readFileSync(new URL(args[0], cwd), 'utf8')); const {errors, warnings, result} = await convert(input); if (errors) { @@ -294,12 +293,39 @@ async function loadTokens(tokenPaths) { const isYAMLExt = pathname.endsWith('.yaml') || pathname.endsWith('.yml'); if (filepath.protocol === 'http:' || filepath.protocol === 'https:') { try { - const res = await globalThis.fetch(filepath, {method: 'GET', headers: {Accept: '*/*', 'User-Agent': 'Mozilla/5.0 Gecko/20100101 Firefox/116.0'}}); + // if Figma URL + if (filepath.host === 'figma.com' || filepath.host === 'www.figma.com') { + const [_, fileKeyword, fileKey] = filepath.pathname.split('/'); + if (fileKeyword !== 'file' || !fileKey) { + printErrors(`Unexpected Figma URL. Expected "https://www.figma.com/file/:file_key/:file_name?…", received "${filepath.href}"`); + process.exit(1); + } + const headers = new Headers({Accept: '*/*', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:123.0) Gecko/20100101 Firefox/123.0'}); + if (process.env.FIGMA_ACCESS_TOKEN) { + headers.set('X-FIGMA-TOKEN', process.env.FIGMA_ACCESS_TOKEN); + } else { + printWarnings(`FIGMA_ACCESS_TOKEN not set`); + } + const res = await fetch(`https://api.figma.com/v1/files/${fileKey}/variables/local`, {method: 'GET', headers}); + if (res.ok) { + const data = await res.json(); + rawTokens.push(data.meta); + continue; + } + const message = res.status !== 404 ? JSON.stringify(await res.json(), undefined, 2) : ''; + printErrors(`Figma responded with ${res.status}${message ? `:\n${message}` : ''}`); + process.exit(1); + break; + } + + // otherwise, expect YAML/JSON + const res = await fetch(filepath, {method: 'GET', headers: {Accept: '*/*', 'User-Agent': 'Mozilla/5.0 Gecko/20100101 Firefox/123.0'}}); const raw = await res.text(); - if (isYAMLExt || res.headers.get('content-type').includes('yaml')) { - rawTokens.push(yaml.load(raw)); - } else { + // if the 1st character is '{', it’s JSON (“if it’s dumb but it works…”) + if (raw[0].trim() === '{') { rawTokens.push(JSON.parse(raw)); + } else { + rawTokens.push(yaml.load(raw)); } } catch (err) { printErrors(`${filepath.href}: ${err}`); diff --git a/packages/cli/package.json b/packages/cli/package.json index ba91d2ee..e679f622 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -6,7 +6,20 @@ "name": "Drew Powers", "email": "drew@pow.rs" }, - "keywords": ["design tokens", "design tokens community group", "design tokens format module", "dtcg", "cli", "w3c design tokens", "design system", "typescript", "sass", "css", "style tokens", "style system"], + "keywords": [ + "design tokens", + "design tokens community group", + "design tokens format module", + "dtcg", + "cli", + "w3c design tokens", + "design system", + "typescript", + "sass", + "css", + "style tokens", + "style system" + ], "engines": { "node": ">=18.0.0" }, @@ -46,6 +59,7 @@ "@types/culori": "^2.0.4", "@types/node": "^20.11.16", "execa": "^7.2.0", + "msw": "^2.1.5", "vitest": "^1.2.2" } } diff --git a/packages/cli/test/check.test.js b/packages/cli/test/check.test.js index 502141ed..2914bf12 100644 --- a/packages/cli/test/check.test.js +++ b/packages/cli/test/check.test.js @@ -18,7 +18,7 @@ describe('co check', () => { }); it('URL', async () => { - const result = await execa('node', ['./bin/cli.js', 'check', 'https://raw.githubusercontent.com/drwpow/cobalt-ui/main/packages/cli/it/fixtures/check-default/tokens.json']); + const result = await execa('node', ['./bin/cli.js', 'check', 'https://raw.githubusercontent.com/drwpow/cobalt-ui/main/packages/cli/test/fixtures/check-default/tokens.json']); expect(result.exitCode).toBe(0); }); diff --git a/packages/cli/test/figma.test.js b/packages/cli/test/figma.test.js new file mode 100644 index 00000000..fd246bdc --- /dev/null +++ b/packages/cli/test/figma.test.js @@ -0,0 +1,50 @@ +import fs from 'node:fs'; +import os from 'node:os'; +import {fileURLToPath} from 'node:url'; +import {http, HttpResponse} from 'msw'; +import {setupServer} from 'msw/node'; +import {describe, expect, it, vi} from 'vitest'; +import FIGMA_VARIABLE_API_RESPONSE from './fixtures/figma-success/api_v1_response.json'; + +const FILE_KEY = 'OkPWSU0cusQTumCNno7dm8'; + +// note: running execa like in the other tests doesn’t let us mock the fetch() call with msw. +// so we mock the CLI variables first, then load the JS module in the text scope + +// bug in Node VM on Windows: importing module blows up (errs after `new Script node:vm:99:7, createScript node:vm:255:10, Object.runInThisContext node:vm:303:10`) + +describe('Figma import', () => { + it.skipIf(os.platform() === 'win32')('parses valid syntax correctly', async () => { + const cwd = new URL('./fixtures/figma-success/', import.meta.url); + const server = setupServer(http.get(`https://api.figma.com/v1/files/${FILE_KEY}/variables/local`, () => HttpResponse.json(FIGMA_VARIABLE_API_RESPONSE))); + server.listen(); + + // run CLI + process.argv = ['node', 'bin/cli.js', 'build', '--config', fileURLToPath(new URL('./tokens.config.js', cwd))]; + process.exit = vi.fn(); + const mod = await import('../bin/cli.js'); + await mod.default(); + + expect(fs.readFileSync(new URL('./given.json', cwd), 'utf8')).toMatchFileSnapshot(fileURLToPath(new URL('./want.json', cwd))); + + // clean up + server.close(); + }); + + it.skipIf(os.platform() === 'win32')('throws errors on invalid response', async () => { + const cwd = new URL('./fixtures/figma-error/', import.meta.url); + const server = setupServer(http.get(`https://api.figma.com/v1/files/${FILE_KEY}/variables/local`, () => HttpResponse.json({status: 401, error: true}, {status: 401}))); + server.listen(); + + // run CLI + process.argv = ['node', 'bin/cli.js', 'build', '--config', fileURLToPath(new URL('./tokens.config.js', cwd))]; + process.exit = vi.fn(); + const mod = await import('../bin/cli.js'); + await mod.default(); + + expect(process.exit).toHaveBeenCalledWith(1); + + // clean up + server.close(); + }); +}); diff --git a/packages/cli/test/fixtures/figma-error/tokens.config.js b/packages/cli/test/fixtures/figma-error/tokens.config.js new file mode 100644 index 00000000..776f3587 --- /dev/null +++ b/packages/cli/test/fixtures/figma-error/tokens.config.js @@ -0,0 +1,12 @@ +import pluginJS from '../../../../../packages/plugin-js/dist/index.js'; + +/** @type {import('@cobalt-ui/core').Config} */ +export default { + tokens: ['https://www.figma.com/file/OkPWSU0cusQTumCNno7dm8/Variable-Export?type=design&node-id=0%3A1&mode=design&t=zxhnYAf1FASSHySQ-1'], + outDir: '.', + plugins: [ + pluginJS({ + json: './given.json', + }), + ], +}; diff --git a/packages/cli/test/fixtures/figma-success/api_v1_response.json b/packages/cli/test/fixtures/figma-success/api_v1_response.json new file mode 100644 index 00000000..2eae93fc --- /dev/null +++ b/packages/cli/test/fixtures/figma-success/api_v1_response.json @@ -0,0 +1,522 @@ +{ + "status": 200, + "error": false, + "meta": { + "variables": { + "colorGray1ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "colorGray1ID", + "key": "colorGray1Key", + "name": "color/gray/1", + "remote": false, + "resolvedType": "COLOR", + "valuesByMode": { + "lightModeID": { + "r": 0.9882352941176471, + "g": 0.9882352941176471, + "b": 0.9882352941176471, + "a": 1 + }, + "darkModeID": { + "r": 0.06666666666666667, + "g": 0.06666666666666667, + "b": 0.06666666666666667, + "a": 1 + } + }, + "variableCollectionId": "defaultCollectionID" + }, + "colorGray2ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "colorGray2ID", + "key": "colorGray2Key", + "name": "color/gray/2", + "remote": false, + "resolvedType": "COLOR", + "valuesByMode": { + "lightModeID": { + "r": 0.9764705882352941, + "g": 0.9764705882352941, + "b": 0.9764705882352941, + "a": 1 + }, + "darkModeID": { + "r": 0.09803921568627451, + "g": 0.09803921568627451, + "b": 0.09803921568627451, + "a": 1 + } + }, + "variableCollectionId": "defaultCollectionID" + }, + "colorGray3ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "colorGray3ID", + "key": "colorGray3Key", + "name": "color/gray/3", + "remote": false, + "resolvedType": "COLOR", + "valuesByMode": { + "lightModeID": { + "r": 0.9411764705882353, + "g": 0.9411764705882353, + "b": 0.9411764705882353, + "a": 1 + }, + "darkModeID": { + "r": 0.13333333333333333, + "g": 0.13333333333333333, + "b": 0.13333333333333333, + "a": 1 + } + }, + "variableCollectionId": "defaultCollectionID" + }, + "colorGray4ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "colorGray4ID", + "key": "colorGray4Key", + "name": "color/gray/4", + "remote": false, + "resolvedType": "COLOR", + "valuesByMode": { + "lightModeID": { + "r": 0.9098039215686274, + "g": 0.9098039215686274, + "b": 0.9098039215686274, + "a": 1 + }, + "darkModeID": { + "r": 0.16470588235294117, + "g": 0.16470588235294117, + "b": 0.16470588235294117, + "a": 1 + } + }, + "variableCollectionId": "defaultCollectionID" + }, + "colorGray5ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "colorGray5ID", + "key": "colorGray5Key", + "name": "color/gray/5", + "remote": false, + "resolvedType": "COLOR", + "valuesByMode": { + "lightModeID": { + "r": 0.8784313725490196, + "g": 0.8784313725490196, + "b": 0.8784313725490196, + "a": 1 + }, + "darkModeID": { + "r": 0.19215686274509805, + "g": 0.19215686274509805, + "b": 0.19215686274509805, + "a": 1 + } + }, + "variableCollectionId": "defaultCollectionID" + }, + "colorGray6ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "colorGray6ID", + "key": "colorGray6Key", + "name": "color/gray/6", + "remote": false, + "resolvedType": "COLOR", + "valuesByMode": { + "lightModeID": { + "r": 0.8509803921568627, + "g": 0.8509803921568627, + "b": 0.8509803921568627, + "a": 1 + }, + "darkModeID": { + "r": 0.22745098039215686, + "g": 0.22745098039215686, + "b": 0.22745098039215686, + "a": 1 + } + }, + "variableCollectionId": "defaultCollectionID" + }, + "colorGray7ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "colorGray7ID", + "key": "colorGray7Key", + "name": "color/gray/7", + "remote": false, + "resolvedType": "COLOR", + "valuesByMode": { + "lightModeID": { + "r": 0.807843137254902, + "g": 0.807843137254902, + "b": 0.807843137254902, + "a": 1 + }, + "darkModeID": { + "r": 0.2823529411764706, + "g": 0.2823529411764706, + "b": 0.2823529411764706, + "a": 1 + } + }, + "variableCollectionId": "defaultCollectionID" + }, + "colorGray8ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "colorGray8ID", + "key": "color/gray/8", + "name": "color/gray/8", + "remote": false, + "resolvedType": "COLOR", + "valuesByMode": { + "lightModeID": { + "r": 0.7333333333333333, + "g": 0.7333333333333333, + "b": 0.7333333333333333, + "a": 1 + }, + "darkModeID": { + "r": 0.3764705882352941, + "g": 0.3764705882352941, + "b": 0.3764705882352941, + "a": 1 + } + }, + "variableCollectionId": "defaultCollectionID" + }, + "colorGray9ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "colorGray9ID", + "key": "colorGray9Key", + "name": "color/gray/9", + "remote": false, + "resolvedType": "COLOR", + "valuesByMode": { + "lightModeID": { + "r": 0.5529411764705883, + "g": 0.5529411764705883, + "b": 0.5529411764705883, + "a": 1 + }, + "darkModeID": { + "r": 0.43137254901960786, + "g": 0.43137254901960786, + "b": 0.43137254901960786, + "a": 1 + } + }, + "variableCollectionId": "defaultCollectionID" + }, + "colorGray10ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "colorGray10ID", + "key": "colorGray10Key", + "name": "color/gray/10", + "remote": false, + "resolvedType": "COLOR", + "valuesByMode": { + "lightModeID": { + "r": 0.5137254901960784, + "g": 0.5137254901960784, + "b": 0.5137254901960784, + "a": 1 + }, + "darkModeID": { + "r": 0.4823529411764706, + "g": 0.4823529411764706, + "b": 0.4823529411764706, + "a": 1 + } + }, + "variableCollectionId": "defaultCollectionID" + }, + "colorGray11ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "colorGray11ID", + "key": "colorGray11Key", + "name": "color/gray/11", + "remote": false, + "resolvedType": "COLOR", + "valuesByMode": { + "lightModeID": { + "r": 0.39215686274509803, + "g": 0.39215686274509803, + "b": 0.39215686274509803, + "a": 1 + }, + "darkModeID": { + "r": 0.7058823529411765, + "g": 0.7058823529411765, + "b": 0.7058823529411765, + "a": 1 + } + }, + "variableCollectionId": "defaultCollectionID" + }, + "colorGray12ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "colorGray12ID", + "key": "colorGray12Key", + "name": "color/gray/12", + "remote": false, + "resolvedType": "COLOR", + "valuesByMode": { + "lightModeID": { + "r": 0.12549019607843137, + "g": 0.12549019607843137, + "b": 0.12549019607843137, + "a": 1 + }, + "darkModeID": { + "r": 0.9333333333333333, + "g": 0.9333333333333333, + "b": 0.9333333333333333, + "a": 1 + } + }, + "variableCollectionId": "defaultCollectionID" + }, + "colorUIBGID": { + "id": "colorUIBGID", + "key": "colorUIBGKey", + "name": "color/ui/bg", + "description": "", + "resolvedType": "COLOR", + "valuesByMode": { + "lightModeID": { + "type": "VARIABLE_ALIAS", + "id": "colorGray1ID" + }, + "darkModeID": { + "type": "VARIABLE_ALIAS", + "id": "colorGray1ID" + } + }, + "remote": false, + "hiddenFromPublishing": false, + "variableCollectionId": "defaultCollectionID" + }, + "colorUITextID": { + "id": "colorUITextID", + "key": "colorUITextKey", + "name": "color/ui/text", + "description": "", + "resolvedType": "COLOR", + "valuesByMode": { + "lightModeID": { + "type": "VARIABLE_ALIAS", + "id": "colorGray12ID" + }, + "darkModeID": { + "type": "VARIABLE_ALIAS", + "id": "colorGray12ID" + } + }, + "remote": false, + "hiddenFromPublishing": false, + "variableCollectionId": "defaultCollectionID" + }, + "space1ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "space1ID", + "key": "space1Key", + "name": "space/1", + "remote": false, + "resolvedType": "FLOAT", + "valuesByMode": { + "lightModeID": 2, + "darkModeID": 2 + }, + "variableCollectionId": "defaultCollectionID" + }, + "space2ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "space2ID", + "key": "space2Key", + "name": "space/2", + "remote": false, + "resolvedType": "FLOAT", + "valuesByMode": { + "lightModeID": 4, + "darkModeID": 4 + }, + "variableCollectionId": "defaultCollectionID" + }, + "space3ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "space3ID", + "key": "space3Key", + "name": "space/3", + "remote": false, + "resolvedType": "FLOAT", + "valuesByMode": { + "lightModeID": 8, + "darkModeID": 8 + }, + "variableCollectionId": "defaultCollectionID" + }, + "space4ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "space4ID", + "key": "space4Key", + "name": "space/4", + "remote": false, + "resolvedType": "FLOAT", + "valuesByMode": { + "lightModeID": 12, + "darkModeID": 12 + }, + "variableCollectionId": "defaultCollectionID" + }, + "space5ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "space5ID", + "key": "space5Key", + "name": "space/5", + "remote": false, + "resolvedType": "FLOAT", + "valuesByMode": { + "lightModeID": 16, + "darkModeID": 16 + }, + "variableCollectionId": "defaultCollectionID" + }, + "space6ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "space6ID", + "key": "space6Key", + "name": "space/6", + "remote": false, + "resolvedType": "FLOAT", + "valuesByMode": { + "lightModeID": 24, + "darkModeID": 24 + }, + "variableCollectionId": "defaultCollectionID" + }, + "space7ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "space7ID", + "key": "space7Key", + "name": "space/7", + "remote": false, + "resolvedType": "FLOAT", + "valuesByMode": { + "lightModeID": 32, + "darkModeID": 32 + }, + "variableCollectionId": "defaultCollectionID" + }, + "space8ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "space8ID", + "key": "space8Key", + "name": "space/8", + "remote": false, + "resolvedType": "FLOAT", + "valuesByMode": { + "lightModeID": 40, + "darkModeID": 40 + }, + "variableCollectionId": "defaultCollectionID" + }, + "space9ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "space9ID", + "key": "space9Key", + "name": "space/9", + "remote": false, + "resolvedType": "FLOAT", + "valuesByMode": { + "lightModeID": 48, + "darkModeID": 48 + }, + "variableCollectionId": "defaultCollectionID" + }, + "space10ID": { + "description": "", + "hiddenFromPublishing": false, + "id": "space10ID", + "key": "space10Key", + "name": "space/10", + "remote": false, + "resolvedType": "FLOAT", + "valuesByMode": { + "lightModeID": 64, + "darkModeID": 64 + }, + "variableCollectionId": "defaultCollectionID" + } + }, + "variableCollections": { + "defaultCollectionID": { + "id": "defaultCollectionID", + "name": "Default", + "key": "defaultCollectionKey", + "modes": [ + { + "modeId": "lightModeID", + "name": "light" + }, + { + "modeId": "darkModeID", + "name": "dark" + } + ], + "defaultModeId": "lightModeID", + "remote": false, + "hiddenFromPublishing": false, + "variableIds": [ + "colorGray1ID", + "colorGray2ID", + "colorGray3ID", + "colorGray4ID", + "colorGray5ID", + "colorGray6ID", + "colorGray7ID", + "colorGray8ID", + "colorGray9ID", + "colorGray10ID", + "colorGray11ID", + "colorUIBGID", + "colorUITextID", + "space1ID", + "space1ID", + "space2ID", + "space3ID", + "space4ID", + "space5ID", + "space6ID", + "space7ID", + "space8ID", + "space9ID", + "space10ID" + ] + } + } + } +} diff --git a/packages/cli/test/fixtures/figma-success/tokens.config.js b/packages/cli/test/fixtures/figma-success/tokens.config.js new file mode 100644 index 00000000..776f3587 --- /dev/null +++ b/packages/cli/test/fixtures/figma-success/tokens.config.js @@ -0,0 +1,12 @@ +import pluginJS from '../../../../../packages/plugin-js/dist/index.js'; + +/** @type {import('@cobalt-ui/core').Config} */ +export default { + tokens: ['https://www.figma.com/file/OkPWSU0cusQTumCNno7dm8/Variable-Export?type=design&node-id=0%3A1&mode=design&t=zxhnYAf1FASSHySQ-1'], + outDir: '.', + plugins: [ + pluginJS({ + json: './given.json', + }), + ], +}; diff --git a/packages/cli/test/fixtures/figma-success/want.json b/packages/cli/test/fixtures/figma-success/want.json new file mode 100644 index 00000000..024442bd --- /dev/null +++ b/packages/cli/test/fixtures/figma-success/want.json @@ -0,0 +1,776 @@ +{ + "tokens": { + "color.gray.1": "#fcfcfc", + "color.gray.2": "#f9f9f9", + "color.gray.3": "#f0f0f0", + "color.gray.4": "#e8e8e8", + "color.gray.5": "#e0e0e0", + "color.gray.6": "#d9d9d9", + "color.gray.7": "#cecece", + "color.gray.8": "#bbbbbb", + "color.gray.9": "#8d8d8d", + "color.gray.10": "#838383", + "color.gray.11": "#646464", + "color.gray.12": "#202020", + "color.ui.bg": "#fcfcfc", + "color.ui.text": "#202020", + "space.1": 2, + "space.2": 4, + "space.3": 8, + "space.4": 12, + "space.5": 16, + "space.6": 24, + "space.7": 32, + "space.8": 40, + "space.9": 48, + "space.10": 64 + }, + "meta": { + "color.gray.1": { + "_original": { + "$type": "color", + "$extensions": { + "mode": { + "light": "#fcfcfc", + "dark": "#111111" + } + }, + "$value": "#fcfcfc" + }, + "_group": { + "id": "color.gray", + "$extensions": { + "requiredModes": [] + } + }, + "id": "color.gray.1", + "$type": "color", + "$extensions": { + "mode": { + "light": "#fcfcfc", + "dark": "#111111" + } + }, + "$value": "#fcfcfc" + }, + "color.gray.2": { + "_original": { + "$type": "color", + "$extensions": { + "mode": { + "light": "#f9f9f9", + "dark": "#191919" + } + }, + "$value": "#f9f9f9" + }, + "_group": { + "id": "color.gray", + "$extensions": { + "requiredModes": [] + } + }, + "id": "color.gray.2", + "$type": "color", + "$extensions": { + "mode": { + "light": "#f9f9f9", + "dark": "#191919" + } + }, + "$value": "#f9f9f9" + }, + "color.gray.3": { + "_original": { + "$type": "color", + "$extensions": { + "mode": { + "light": "#f0f0f0", + "dark": "#222222" + } + }, + "$value": "#f0f0f0" + }, + "_group": { + "id": "color.gray", + "$extensions": { + "requiredModes": [] + } + }, + "id": "color.gray.3", + "$type": "color", + "$extensions": { + "mode": { + "light": "#f0f0f0", + "dark": "#222222" + } + }, + "$value": "#f0f0f0" + }, + "color.gray.4": { + "_original": { + "$type": "color", + "$extensions": { + "mode": { + "light": "#e8e8e8", + "dark": "#2a2a2a" + } + }, + "$value": "#e8e8e8" + }, + "_group": { + "id": "color.gray", + "$extensions": { + "requiredModes": [] + } + }, + "id": "color.gray.4", + "$type": "color", + "$extensions": { + "mode": { + "light": "#e8e8e8", + "dark": "#2a2a2a" + } + }, + "$value": "#e8e8e8" + }, + "color.gray.5": { + "_original": { + "$type": "color", + "$extensions": { + "mode": { + "light": "#e0e0e0", + "dark": "#313131" + } + }, + "$value": "#e0e0e0" + }, + "_group": { + "id": "color.gray", + "$extensions": { + "requiredModes": [] + } + }, + "id": "color.gray.5", + "$type": "color", + "$extensions": { + "mode": { + "light": "#e0e0e0", + "dark": "#313131" + } + }, + "$value": "#e0e0e0" + }, + "color.gray.6": { + "_original": { + "$type": "color", + "$extensions": { + "mode": { + "light": "#d9d9d9", + "dark": "#3a3a3a" + } + }, + "$value": "#d9d9d9" + }, + "_group": { + "id": "color.gray", + "$extensions": { + "requiredModes": [] + } + }, + "id": "color.gray.6", + "$type": "color", + "$extensions": { + "mode": { + "light": "#d9d9d9", + "dark": "#3a3a3a" + } + }, + "$value": "#d9d9d9" + }, + "color.gray.7": { + "_original": { + "$type": "color", + "$extensions": { + "mode": { + "light": "#cecece", + "dark": "#484848" + } + }, + "$value": "#cecece" + }, + "_group": { + "id": "color.gray", + "$extensions": { + "requiredModes": [] + } + }, + "id": "color.gray.7", + "$type": "color", + "$extensions": { + "mode": { + "light": "#cecece", + "dark": "#484848" + } + }, + "$value": "#cecece" + }, + "color.gray.8": { + "_original": { + "$type": "color", + "$extensions": { + "mode": { + "light": "#bbbbbb", + "dark": "#606060" + } + }, + "$value": "#bbbbbb" + }, + "_group": { + "id": "color.gray", + "$extensions": { + "requiredModes": [] + } + }, + "id": "color.gray.8", + "$type": "color", + "$extensions": { + "mode": { + "light": "#bbbbbb", + "dark": "#606060" + } + }, + "$value": "#bbbbbb" + }, + "color.gray.9": { + "_original": { + "$type": "color", + "$extensions": { + "mode": { + "light": "#8d8d8d", + "dark": "#6e6e6e" + } + }, + "$value": "#8d8d8d" + }, + "_group": { + "id": "color.gray", + "$extensions": { + "requiredModes": [] + } + }, + "id": "color.gray.9", + "$type": "color", + "$extensions": { + "mode": { + "light": "#8d8d8d", + "dark": "#6e6e6e" + } + }, + "$value": "#8d8d8d" + }, + "color.gray.10": { + "_original": { + "$type": "color", + "$extensions": { + "mode": { + "light": "#838383", + "dark": "#7b7b7b" + } + }, + "$value": "#838383" + }, + "_group": { + "id": "color.gray", + "$extensions": { + "requiredModes": [] + } + }, + "id": "color.gray.10", + "$type": "color", + "$extensions": { + "mode": { + "light": "#838383", + "dark": "#7b7b7b" + } + }, + "$value": "#838383" + }, + "color.gray.11": { + "_original": { + "$type": "color", + "$extensions": { + "mode": { + "light": "#646464", + "dark": "#b4b4b4" + } + }, + "$value": "#646464" + }, + "_group": { + "id": "color.gray", + "$extensions": { + "requiredModes": [] + } + }, + "id": "color.gray.11", + "$type": "color", + "$extensions": { + "mode": { + "light": "#646464", + "dark": "#b4b4b4" + } + }, + "$value": "#646464" + }, + "color.gray.12": { + "_original": { + "$type": "color", + "$extensions": { + "mode": { + "light": "#202020", + "dark": "#eeeeee" + } + }, + "$value": "#202020" + }, + "_group": { + "id": "color.gray", + "$extensions": { + "requiredModes": [] + } + }, + "id": "color.gray.12", + "$type": "color", + "$extensions": { + "mode": { + "light": "#202020", + "dark": "#eeeeee" + } + }, + "$value": "#202020" + }, + "color.ui.bg": { + "_original": { + "$type": "color", + "$extensions": { + "mode": { + "light": "{color.gray.1#light}", + "dark": "{color.gray.1#dark}" + } + }, + "$value": "{color.gray.1}" + }, + "_group": { + "id": "color.ui", + "$extensions": { + "requiredModes": [] + } + }, + "id": "color.ui.bg", + "$type": "color", + "$extensions": { + "mode": { + "light": "#fcfcfc", + "dark": "#111111" + } + }, + "$value": "#fcfcfc" + }, + "color.ui.text": { + "_original": { + "$type": "color", + "$extensions": { + "mode": { + "light": "{color.gray.12#light}", + "dark": "{color.gray.12#dark}" + } + }, + "$value": "{color.gray.12}" + }, + "_group": { + "id": "color.ui", + "$extensions": { + "requiredModes": [] + } + }, + "id": "color.ui.text", + "$type": "color", + "$extensions": { + "mode": { + "light": "#202020", + "dark": "#eeeeee" + } + }, + "$value": "#202020" + }, + "space.1": { + "_original": { + "$type": "number", + "$extensions": { + "mode": { + "light": 2, + "dark": 2 + } + }, + "$value": 2 + }, + "_group": { + "id": "space", + "$extensions": { + "requiredModes": [] + } + }, + "id": "space.1", + "$type": "number", + "$extensions": { + "mode": { + "light": 2, + "dark": 2 + } + }, + "$value": 2 + }, + "space.2": { + "_original": { + "$type": "number", + "$extensions": { + "mode": { + "light": 4, + "dark": 4 + } + }, + "$value": 4 + }, + "_group": { + "id": "space", + "$extensions": { + "requiredModes": [] + } + }, + "id": "space.2", + "$type": "number", + "$extensions": { + "mode": { + "light": 4, + "dark": 4 + } + }, + "$value": 4 + }, + "space.3": { + "_original": { + "$type": "number", + "$extensions": { + "mode": { + "light": 8, + "dark": 8 + } + }, + "$value": 8 + }, + "_group": { + "id": "space", + "$extensions": { + "requiredModes": [] + } + }, + "id": "space.3", + "$type": "number", + "$extensions": { + "mode": { + "light": 8, + "dark": 8 + } + }, + "$value": 8 + }, + "space.4": { + "_original": { + "$type": "number", + "$extensions": { + "mode": { + "light": 12, + "dark": 12 + } + }, + "$value": 12 + }, + "_group": { + "id": "space", + "$extensions": { + "requiredModes": [] + } + }, + "id": "space.4", + "$type": "number", + "$extensions": { + "mode": { + "light": 12, + "dark": 12 + } + }, + "$value": 12 + }, + "space.5": { + "_original": { + "$type": "number", + "$extensions": { + "mode": { + "light": 16, + "dark": 16 + } + }, + "$value": 16 + }, + "_group": { + "id": "space", + "$extensions": { + "requiredModes": [] + } + }, + "id": "space.5", + "$type": "number", + "$extensions": { + "mode": { + "light": 16, + "dark": 16 + } + }, + "$value": 16 + }, + "space.6": { + "_original": { + "$type": "number", + "$extensions": { + "mode": { + "light": 24, + "dark": 24 + } + }, + "$value": 24 + }, + "_group": { + "id": "space", + "$extensions": { + "requiredModes": [] + } + }, + "id": "space.6", + "$type": "number", + "$extensions": { + "mode": { + "light": 24, + "dark": 24 + } + }, + "$value": 24 + }, + "space.7": { + "_original": { + "$type": "number", + "$extensions": { + "mode": { + "light": 32, + "dark": 32 + } + }, + "$value": 32 + }, + "_group": { + "id": "space", + "$extensions": { + "requiredModes": [] + } + }, + "id": "space.7", + "$type": "number", + "$extensions": { + "mode": { + "light": 32, + "dark": 32 + } + }, + "$value": 32 + }, + "space.8": { + "_original": { + "$type": "number", + "$extensions": { + "mode": { + "light": 40, + "dark": 40 + } + }, + "$value": 40 + }, + "_group": { + "id": "space", + "$extensions": { + "requiredModes": [] + } + }, + "id": "space.8", + "$type": "number", + "$extensions": { + "mode": { + "light": 40, + "dark": 40 + } + }, + "$value": 40 + }, + "space.9": { + "_original": { + "$type": "number", + "$extensions": { + "mode": { + "light": 48, + "dark": 48 + } + }, + "$value": 48 + }, + "_group": { + "id": "space", + "$extensions": { + "requiredModes": [] + } + }, + "id": "space.9", + "$type": "number", + "$extensions": { + "mode": { + "light": 48, + "dark": 48 + } + }, + "$value": 48 + }, + "space.10": { + "_original": { + "$type": "number", + "$extensions": { + "mode": { + "light": 64, + "dark": 64 + } + }, + "$value": 64 + }, + "_group": { + "id": "space", + "$extensions": { + "requiredModes": [] + } + }, + "id": "space.10", + "$type": "number", + "$extensions": { + "mode": { + "light": 64, + "dark": 64 + } + }, + "$value": 64 + } + }, + "modes": { + "color.gray.1": { + "light": "#fcfcfc", + "dark": "#111111" + }, + "color.gray.2": { + "light": "#f9f9f9", + "dark": "#191919" + }, + "color.gray.3": { + "light": "#f0f0f0", + "dark": "#222222" + }, + "color.gray.4": { + "light": "#e8e8e8", + "dark": "#2a2a2a" + }, + "color.gray.5": { + "light": "#e0e0e0", + "dark": "#313131" + }, + "color.gray.6": { + "light": "#d9d9d9", + "dark": "#3a3a3a" + }, + "color.gray.7": { + "light": "#cecece", + "dark": "#484848" + }, + "color.gray.8": { + "light": "#bbbbbb", + "dark": "#606060" + }, + "color.gray.9": { + "light": "#8d8d8d", + "dark": "#6e6e6e" + }, + "color.gray.10": { + "light": "#838383", + "dark": "#7b7b7b" + }, + "color.gray.11": { + "light": "#646464", + "dark": "#b4b4b4" + }, + "color.gray.12": { + "light": "#202020", + "dark": "#eeeeee" + }, + "color.ui.bg": { + "light": "#fcfcfc", + "dark": "#111111" + }, + "color.ui.text": { + "light": "#202020", + "dark": "#eeeeee" + }, + "space.1": { + "light": 2, + "dark": 2 + }, + "space.2": { + "light": 4, + "dark": 4 + }, + "space.3": { + "light": 8, + "dark": 8 + }, + "space.4": { + "light": 12, + "dark": 12 + }, + "space.5": { + "light": 16, + "dark": 16 + }, + "space.6": { + "light": 24, + "dark": 24 + }, + "space.7": { + "light": 32, + "dark": 32 + }, + "space.8": { + "light": 40, + "dark": 40 + }, + "space.9": { + "light": 48, + "dark": 48 + }, + "space.10": { + "light": 64, + "dark": 64 + } + } +} diff --git a/packages/core/src/parse/figma.ts b/packages/core/src/parse/figma.ts new file mode 100644 index 00000000..5f939027 --- /dev/null +++ b/packages/core/src/parse/figma.ts @@ -0,0 +1,315 @@ +import {formatHex, formatHex8, oklch} from 'culori'; +import type {Group, Token, TokenType} from '../token.js'; +import {invalidTokenIDError, isTokenMatch} from '@cobalt-ui/utils'; + +export interface FigmaParseOptions { + overrides: Record; +} + +export interface FigmaOverride { + /** Change the output type */ + $type?: Token['$type']; + /** Rename a token (will also update aliases) */ + rename?: (id: string) => string | undefined | null; + /** Convert value (e.g. from px to rem) */ + transform?: (options: {variable: Variable; collection: VariableCollection; mode: VariableMode}) => Token['$value'] | undefined | null; +} + +/** sRGB color */ +export interface FigmaColor { + r: number; + g: number; + b: number; + a: number; +} + +export interface VariableMode { + modeId: string; + name: string; +} + +export interface VariableCollection { + /** The unique identifier of this variable collection. */ + id: string; + /** The name of this variable collection. */ + name: string; + /** The key of the variable collection. */ + key: string; + /** The list of modes defined for this variable collection. */ + modes: VariableMode[]; + /** The id of the default mode. */ + defaultModeId: string; + /** Whether the variable collection is remote. */ + remote: boolean; + /** Whether this variable collection is hidden when publishing the current file as a library. */ + hiddenFromPublishing: boolean; + /** + * The ids of the variables in the collection. Note that the order of these + * variables is roughly the same as what is shown in Figma Design, however it + * does not account for groups. As a result, the order of these variables may + * not exactly reflect the exact ordering and grouping shown in the authoring + * UI. + */ + variableIds: string[]; +} + +export interface VariableBase { + /** The unique identifier of this variable. */ + id: string; + /** The name of this variable. */ + name: string; + /** The key of the variable. */ + key: string; + /** The id of the variable collection that contains this variable. */ + variableCollectionId: string; + /** Whether the variable is remote. */ + remote: boolean; + /** Description of this variable. */ + description?: string; + /** Whether this variable is hidden when publishing the current file as a library. */ + hiddenFromPublishing: boolean; +} + +export interface BooleanVariable extends VariableBase { + /** The resolved type of the variable. */ + resolvedType: 'BOOLEAN'; + /** The values for each mode of this variable. */ + valuesByMode: {[modeId: string]: boolean | VariableAlias}; +} + +export interface NumberVariable extends VariableBase { + /** The resolved type of the variable. */ + resolvedType: 'FLOAT'; + /** The values for each mode of this variable. */ + valuesByMode: {[modeId: string]: number | VariableAlias}; +} + +export interface StringVariable extends VariableBase { + /** The resolved type of the variable. */ + resolvedType: 'STRING'; + /** The values for each mode of this variable. */ + valuesByMode: {[modeId: string]: string | VariableAlias}; +} + +export interface ColorVariable extends VariableBase { + /** The resolved type of the variable. */ + resolvedType: 'COLOR'; + /** The values for each mode of this variable. */ + valuesByMode: {[modeId: string]: FigmaColor | VariableAlias}; +} + +export type Variable = BooleanVariable | NumberVariable | StringVariable | ColorVariable; + +export interface VariableAlias { + type: 'VARIABLE_ALIAS'; + /** The id of the variable that this alias points to. In the POST variables endpoint, you can use the temporary id of a variable. */ + id: string; +} + +export interface FigmaVariableManifest { + variables: Record; + variableCollections: Record; +} + +export const SLASH_RE = /\//g; +export const DOT_RE = /\./g; + +export const DEFAULT_OUTPUT_TYPE: Partial> = { + COLOR: 'color', + FLOAT: 'number', +}; + +export function figmaColorToHex(color: FigmaColor): string { + const c = oklch({mode: 'rgb', r: color.r, g: color.g, b: color.b, alpha: color.a}); + if (!c.h) { + c.h = 0; + } + return c.alpha === 1 ? formatHex(c) : formatHex8(c); +} + +export function figmaIDToDTCGID(id: string) { + return id.replace(SLASH_RE, '.'); +} + +export function convertFigmaVariablesFormat(manifest: FigmaVariableManifest, options?: FigmaParseOptions) { + const warnings: string[] = []; + const errors: string[] = []; + const dtcgTokens: Group = {}; + if (!manifest || !('variables' in manifest) || typeof manifest.variables !== 'object') { + throw new Error(`Expected ID:Variable mapping in manifest.variables; received "${typeof manifest?.variables}"`); + } + if (!('variableCollections' in manifest) || typeof manifest.variableCollections !== 'object') { + throw new Error(`Expected ID:Collection mapping in manifest.variableCollections; received "${typeof manifest.variableCollections}"`); + } + + const tokenIDToNameMap: Record = {}; + const tokens: Record = {}; + + const globs = Object.keys(options?.overrides ?? {}).map((glob) => glob.replace(SLASH_RE, '.')); + + // 1. build shallow token manifest of IDs -> Tokens (aliases will depend on IDs) + for (const [id, variable] of Object.entries(manifest.variables)) { + // find best override, if any + let override: FigmaOverride | undefined = undefined; + // exact matches always take precedence + + if (options?.overrides[id]) { + override = options.overrides[id]; + } else { + const match = isTokenMatch(variable.name.replace(SLASH_RE, '.'), globs); + if (match) { + override = options!.overrides[match]! || options!.overrides[match.replace(DOT_RE, '/')]; + } + } + + const collection = manifest.variableCollections[variable.variableCollectionId]; + if (!collection) { + errors.push(`Collection ID "${variable.variableCollectionId}" missing in data.`); + break; + } + + const transformResults = transformToken({variable, collection, override}); + warnings.push(...(transformResults.warnings ?? [])); + errors.push(...(transformResults.errors ?? [])); + if (transformResults.result) { + let overrideName = override?.rename?.(variable.name); + if (overrideName) { + overrideName = overrideName.replace(SLASH_RE, '.'); // sanitize user input + if (invalidTokenIDError(overrideName)) { + errors.push(invalidTokenIDError(overrideName)!); + break; + } + } + const name = overrideName || figmaIDToDTCGID(variable.name); + + tokenIDToNameMap[variable.id] = name; // note: as tokens get renamed, this will end up with the correct names in the end + tokens[variable.id] = transformResults.result; + } + } + + // 2. resolve IDs -> friendly names (including aliases—we didn’t want to resolve those before all the renaming had settled) + const dtcgFlat: Record = {}; + for (const [id, token] of Object.entries(tokens)) { + dtcgFlat[tokenIDToNameMap[id]!] = token; + + // resolve Figma aliases to DTCG aliases + if (typeof token.$value === 'object' && 'type' in token.$value && token.$value.type === 'VARIABLE_ALIAS') { + token.$value = `{${tokenIDToNameMap[(token.$value as VariableAlias).id]}}`; + } + if (token.$extensions?.mode) { + for (const [k, v] of Object.entries(token.$extensions?.mode)) { + if (typeof v === 'object' && 'type' in v && v.type === 'VARIABLE_ALIAS') { + token.$extensions.mode[k] = `{${tokenIDToNameMap[(v as VariableAlias).id]}#${k}}`; + } + } + } + } + + // 3. explode flat structure into nested structure + for (const [id, token] of Object.entries(dtcgFlat)) { + const parts = id.split('.'); + let node = dtcgTokens; + const localName = parts.pop()!; + for (const p of parts) { + if (!node[p]) { + node[p] = {}; + } + node = node[p] as Group; + } + node[localName] = token; + } + + return { + errors: errors.length ? errors : undefined, + warnings: warnings.length ? warnings : undefined, + result: dtcgTokens, + }; +} + +export function transformToken({variable, collection, override}: {variable: Variable; collection: VariableCollection; override?: FigmaOverride}): {warnings?: string[]; errors?: string[]; result: Token | undefined} { + const token = {} as Token; + const errors: string[] = []; + if (variable.description) { + token.$description = variable.description; + } + const $type = override?.$type || DEFAULT_OUTPUT_TYPE[variable.resolvedType]; + if (!$type) { + return { + warnings: [`Couldn’t determine output type for variable "${variable.name}" (${variable.id})`], + result: undefined, + }; + } + token.$type = $type; + for (const [modeId, rawValue] of Object.entries(variable.valuesByMode)) { + const isDefaultMode = modeId === collection.defaultModeId; + const isMultiModal = Object.values(variable.valuesByMode).length > 1; + + // make sure $extensions.mode exists if there are multiple modes + if (isMultiModal && !token.$extensions) { + token.$extensions = {mode: {}}; + } + + const collectionMode = collection.modes.find((mode) => mode.modeId === modeId); + if (!collectionMode) { + errors.push(`Collection ID "${variable.variableCollectionId}" missing mode ID "${modeId}"`); + break; + } + + // skip alias resolution till later + if (typeof rawValue === 'object' && 'type' in rawValue && rawValue.type === 'VARIABLE_ALIAS') { + if (isDefaultMode) { + token.$value = rawValue; + } + if (isMultiModal) { + token.$extensions!.mode![collectionMode.name] = rawValue; + } + continue; + } + + // transform token + try { + let transformedValue = override?.transform?.({variable, collection, mode: collectionMode}); + if (transformedValue === undefined || transformedValue == null) { + switch ($type) { + case 'color': { + transformedValue = figmaColorToHex(rawValue as FigmaColor); + break; + } + case 'number': { + transformedValue = Number(rawValue); + break; + } + case 'duration': { + transformedValue = typeof rawValue === 'string' ? rawValue : `${rawValue}ms`; + break; + } + case 'dimension': { + transformedValue = typeof rawValue === 'string' ? rawValue : `${rawValue}px`; + break; + } + default: { + transformedValue = rawValue; + break; + } + } + } + if (isDefaultMode) { + token.$value = transformedValue!; + } + if (isMultiModal) { + token.$extensions!.mode![collectionMode.name] = transformedValue!; + } + } catch (err) { + errors.push(String(err)); + } + } + + return { + errors: errors.length ? errors : undefined, + result: token, + }; +} + +export function isFigmaVariablesFormat(manifest: unknown): boolean { + return !!manifest && typeof manifest === 'object' && 'variables' in manifest && typeof manifest.variables === 'object' && 'variableCollections' in manifest && typeof manifest.variableCollections === 'object'; +} diff --git a/packages/core/src/parse/index.ts b/packages/core/src/parse/index.ts index 98ae1926..3c2cab13 100644 --- a/packages/core/src/parse/index.ts +++ b/packages/core/src/parse/index.ts @@ -1,4 +1,4 @@ -import {cloneDeep, FG_YELLOW, getAliasID, isAlias, RESET} from '@cobalt-ui/utils'; +import {cloneDeep, FG_YELLOW, getAliasID, invalidTokenIDError, isAlias, RESET} from '@cobalt-ui/utils'; import type {Group, ParsedToken, TokenType, TokenOrGroup} from '../token.js'; import {isEmpty, isObj, splitType} from '../util.js'; import {normalizeColorValue, ParseColorOptions} from './tokens/color.js'; @@ -16,6 +16,7 @@ import {normalizeTypographyValue} from './tokens/typography.js'; import {normalizeFontWeightValue} from './tokens/fontWeight.js'; import {normalizeNumberValue} from './tokens/number.js'; import {convertTokensStudioFormat, isTokensStudioFormat} from './tokens-studio.js'; +import {convertFigmaVariablesFormat, isFigmaVariablesFormat, type FigmaParseOptions, FigmaVariableManifest} from './figma.js'; export interface ParseResult { errors?: string[]; @@ -29,6 +30,7 @@ export interface ParseResult { export interface ParseOptions { /** Configure transformations for color tokens */ color: ParseColorOptions; + figma?: FigmaParseOptions; } interface InheritedGroup { @@ -50,10 +52,18 @@ export function parse(rawTokens: unknown, options: ParseOptions): ParseResult { return result; } - // 0. handle Tokens Studio for Figma format let schema = rawTokens as Group; - if (isTokensStudioFormat(rawTokens)) { + // 0. handle Figma Variables format + if (isFigmaVariablesFormat(rawTokens)) { + const figmaTokensResult = convertFigmaVariablesFormat(rawTokens as FigmaVariableManifest, options?.figma); + errors.push(...(figmaTokensResult.errors ?? [])); + warnings.push(...(figmaTokensResult.warnings ?? [])); + schema = figmaTokensResult.result; + } + + // 0. handle Tokens Studio for Figma format + else if (isTokensStudioFormat(rawTokens)) { const tokensStudioResult = convertTokensStudioFormat(rawTokens as Group); errors.push(...(tokensStudioResult.errors ?? [])); warnings.push(...(tokensStudioResult.warnings ?? [])); @@ -69,8 +79,13 @@ export function parse(rawTokens: unknown, options: ParseOptions): ParseResult { errors.push(`${k}: unexpected token format "${v}"`); continue; } - if (k.includes('.') || k.includes('{') || k.includes('}') || k.includes('#')) { - errors.push(`${k}: IDs can’t include any of the following: .{}#`); + const tokenValidationError = invalidTokenIDError(k); + if (tokenValidationError) { + errors.push(`${k}: tokenValidationError`); + continue; + } + if (k.includes('.')) { + errors.push(`${k}: IDs can’t contain periods`); continue; } if (!Object.keys(v).length) { diff --git a/packages/core/test/figma.test.ts b/packages/core/test/figma.test.ts new file mode 100644 index 00000000..f7b8d889 --- /dev/null +++ b/packages/core/test/figma.test.ts @@ -0,0 +1,485 @@ +import {rgb} from 'culori'; +import {describe, it, expect} from 'vitest'; +import {type FigmaColor, type FigmaVariableManifest} from '../src/parse/figma.js'; +import {parse} from '../src/parse/index.js'; + +function hexToRgb(hex: string): FigmaColor { + const c = rgb(hex); + return c ? {r: c.r, g: c.g, b: c.b, a: c.alpha ?? 1} : {r: 0, g: 0, b: 0, a: 0}; +} + +const TOKENS: FigmaVariableManifest = { + variables: { + colorGray1ID: { + description: '', + hiddenFromPublishing: false, + id: 'colorGray1ID', + key: 'colorGray1Key', + name: 'color/gray/1', + remote: false, + resolvedType: 'COLOR', + valuesByMode: { + lightModeID: hexToRgb('#fcfcfc'), + darkModeID: hexToRgb('#111111'), + }, + variableCollectionId: 'defaultCollectionID', + }, + colorGray2ID: { + description: '', + hiddenFromPublishing: false, + id: 'colorGray2ID', + key: 'colorGray2Key', + name: 'color/gray/2', + remote: false, + resolvedType: 'COLOR', + valuesByMode: { + lightModeID: hexToRgb('#f9f9f9'), + darkModeID: hexToRgb('#191919'), + }, + variableCollectionId: 'defaultCollectionID', + }, + colorGray3ID: { + description: '', + hiddenFromPublishing: false, + id: 'colorGray3ID', + key: 'colorGray3Key', + name: 'color/gray/3', + remote: false, + resolvedType: 'COLOR', + valuesByMode: { + lightModeID: hexToRgb('#f0f0f0'), + darkModeID: hexToRgb('#222222'), + }, + variableCollectionId: 'defaultCollectionID', + }, + colorGray4ID: { + description: '', + hiddenFromPublishing: false, + id: 'colorGray4ID', + key: 'colorGray4Key', + name: 'color/gray/4', + remote: false, + resolvedType: 'COLOR', + valuesByMode: { + lightModeID: hexToRgb('#e8e8e8'), + darkModeID: hexToRgb('#2a2a2a'), + }, + variableCollectionId: 'defaultCollectionID', + }, + colorGray5ID: { + description: '', + hiddenFromPublishing: false, + id: 'colorGray5ID', + key: 'colorGray5Key', + name: 'color/gray/5', + remote: false, + resolvedType: 'COLOR', + valuesByMode: { + lightModeID: hexToRgb('#e0e0e0'), + darkModeID: hexToRgb('#313131'), + }, + variableCollectionId: 'defaultCollectionID', + }, + colorGray6ID: { + description: '', + hiddenFromPublishing: false, + id: 'colorGray6ID', + key: 'colorGray6Key', + name: 'color/gray/6', + remote: false, + resolvedType: 'COLOR', + valuesByMode: { + lightModeID: hexToRgb('#d9d9d9'), + darkModeID: hexToRgb('#3a3a3a'), + }, + variableCollectionId: 'defaultCollectionID', + }, + colorGray7ID: { + description: '', + hiddenFromPublishing: false, + id: 'colorGray7ID', + key: 'colorGray7Key', + name: 'color/gray/7', + remote: false, + resolvedType: 'COLOR', + valuesByMode: { + lightModeID: hexToRgb('#cecece'), + darkModeID: hexToRgb('#484848'), + }, + variableCollectionId: 'defaultCollectionID', + }, + colorGray8ID: { + description: '', + hiddenFromPublishing: false, + id: 'colorGray8ID', + key: 'color/gray/8', + name: 'color/gray/8', + remote: false, + resolvedType: 'COLOR', + valuesByMode: { + lightModeID: hexToRgb('#bbbbbb'), + darkModeID: hexToRgb('#606060'), + }, + variableCollectionId: 'defaultCollectionID', + }, + colorGray9ID: { + description: '', + hiddenFromPublishing: false, + id: 'colorGray9ID', + key: 'colorGray9Key', + name: 'color/gray/9', + remote: false, + resolvedType: 'COLOR', + valuesByMode: { + lightModeID: hexToRgb('#8d8d8d'), + darkModeID: hexToRgb('#6e6e6e'), + }, + variableCollectionId: 'defaultCollectionID', + }, + colorGray10ID: { + description: '', + hiddenFromPublishing: false, + id: 'colorGray10ID', + key: 'colorGray10Key', + name: 'color/gray/10', + remote: false, + resolvedType: 'COLOR', + valuesByMode: { + lightModeID: hexToRgb('#838383'), + darkModeID: hexToRgb('#7b7b7b'), + }, + variableCollectionId: 'defaultCollectionID', + }, + colorGray11ID: { + description: '', + hiddenFromPublishing: false, + id: 'colorGray11ID', + key: 'colorGray11Key', + name: 'color/gray/11', + remote: false, + resolvedType: 'COLOR', + valuesByMode: { + lightModeID: hexToRgb('#646464'), + darkModeID: hexToRgb('#b4b4b4'), + }, + variableCollectionId: 'defaultCollectionID', + }, + colorGray12ID: { + description: '', + hiddenFromPublishing: false, + id: 'colorGray12ID', + key: 'colorGray12Key', + name: 'color/gray/12', + remote: false, + resolvedType: 'COLOR', + valuesByMode: { + lightModeID: hexToRgb('#202020'), + darkModeID: hexToRgb('#eeeeee'), + }, + variableCollectionId: 'defaultCollectionID', + }, + colorUIBGID: { + id: 'colorUIBGID', + key: 'colorUIBGKey', + name: 'color/ui/bg', + description: '', + resolvedType: 'COLOR', + valuesByMode: {lightModeID: {type: 'VARIABLE_ALIAS', id: 'colorGray1ID'}, darkModeID: {type: 'VARIABLE_ALIAS', id: 'colorGray1ID'}}, + remote: false, + hiddenFromPublishing: false, + variableCollectionId: 'defaultCollectionID', + }, + colorUITextID: { + id: 'colorUITextID', + key: 'colorUITextKey', + name: 'color/ui/text', + description: '', + resolvedType: 'COLOR', + valuesByMode: {lightModeID: {type: 'VARIABLE_ALIAS', id: 'colorGray12ID'}, darkModeID: {type: 'VARIABLE_ALIAS', id: 'colorGray12ID'}}, + remote: false, + hiddenFromPublishing: false, + variableCollectionId: 'defaultCollectionID', + }, + space1ID: { + description: '', + hiddenFromPublishing: false, + id: 'space1ID', + key: 'space1Key', + name: 'space/1', + remote: false, + resolvedType: 'FLOAT', + valuesByMode: { + lightModeID: 2.0, + darkModeID: 2.0, + }, + variableCollectionId: 'defaultCollectionID', + }, + space2ID: { + description: '', + hiddenFromPublishing: false, + id: 'space2ID', + key: 'space2Key', + name: 'space/2', + remote: false, + resolvedType: 'FLOAT', + valuesByMode: { + lightModeID: 4.0, + darkModeID: 4.0, + }, + variableCollectionId: 'defaultCollectionID', + }, + space3ID: { + description: '', + hiddenFromPublishing: false, + id: 'space3ID', + key: 'space3Key', + name: 'space/3', + remote: false, + resolvedType: 'FLOAT', + valuesByMode: { + lightModeID: 8.0, + darkModeID: 8.0, + }, + variableCollectionId: 'defaultCollectionID', + }, + space4ID: { + description: '', + hiddenFromPublishing: false, + id: 'space4ID', + key: 'space4Key', + name: 'space/4', + remote: false, + resolvedType: 'FLOAT', + valuesByMode: { + lightModeID: 12.0, + darkModeID: 12.0, + }, + variableCollectionId: 'defaultCollectionID', + }, + space5ID: { + description: '', + hiddenFromPublishing: false, + id: 'space5ID', + key: 'space5Key', + name: 'space/5', + remote: false, + resolvedType: 'FLOAT', + valuesByMode: { + lightModeID: 16.0, + darkModeID: 16.0, + }, + variableCollectionId: 'defaultCollectionID', + }, + space6ID: { + description: '', + hiddenFromPublishing: false, + id: 'space6ID', + key: 'space6Key', + name: 'space/6', + remote: false, + resolvedType: 'FLOAT', + valuesByMode: { + lightModeID: 24.0, + darkModeID: 24.0, + }, + variableCollectionId: 'defaultCollectionID', + }, + space7ID: { + description: '', + hiddenFromPublishing: false, + id: 'space7ID', + key: 'space7Key', + name: 'space/7', + remote: false, + resolvedType: 'FLOAT', + valuesByMode: { + lightModeID: 32.0, + darkModeID: 32.0, + }, + variableCollectionId: 'defaultCollectionID', + }, + space8ID: { + description: '', + hiddenFromPublishing: false, + id: 'space8ID', + key: 'space8Key', + name: 'space/8', + remote: false, + resolvedType: 'FLOAT', + valuesByMode: { + lightModeID: 40.0, + darkModeID: 40.0, + }, + variableCollectionId: 'defaultCollectionID', + }, + space9ID: { + description: '', + hiddenFromPublishing: false, + id: 'space9ID', + key: 'space9Key', + name: 'space/9', + remote: false, + resolvedType: 'FLOAT', + valuesByMode: { + lightModeID: 48.0, + darkModeID: 48.0, + }, + variableCollectionId: 'defaultCollectionID', + }, + space10ID: { + description: '', + hiddenFromPublishing: false, + id: 'space10ID', + key: 'space10Key', + name: 'space/10', + remote: false, + resolvedType: 'FLOAT', + valuesByMode: { + lightModeID: 64.0, + darkModeID: 64.0, + }, + variableCollectionId: 'defaultCollectionID', + }, + }, + variableCollections: { + defaultCollectionID: { + id: 'defaultCollectionID', + name: 'Default', + key: 'defaultCollectionKey', + modes: [ + {modeId: 'lightModeID', name: 'light'}, + {modeId: 'darkModeID', name: 'dark'}, + ], + defaultModeId: 'lightModeID', + remote: false, + hiddenFromPublishing: false, + variableIds: [ + 'colorGray1ID', + 'colorGray2ID', + 'colorGray3ID', + 'colorGray4ID', + 'colorGray5ID', + 'colorGray6ID', + 'colorGray7ID', + 'colorGray8ID', + 'colorGray9ID', + 'colorGray10ID', + 'colorGray11ID', + 'colorUIBGID', + 'colorUITextID', + 'space1ID', + 'space1ID', + 'space2ID', + 'space3ID', + 'space4ID', + 'space5ID', + 'space6ID', + 'space7ID', + 'space8ID', + 'space9ID', + 'space10ID', + ], + }, + }, +}; + +describe('figma', () => { + it('default values', () => { + const {result, warnings, errors} = parse(TOKENS, {color: {}}); + + expect(warnings).toBeUndefined(); + expect(errors).toBeUndefined(); + + expect(result.tokens).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: 'color.gray.1', + $type: 'color', + $value: '#fcfcfc', + $extensions: {mode: {dark: '#111111', light: '#fcfcfc'}}, + }), + expect.objectContaining({ + id: 'color.ui.text', + $type: 'color', + $value: '#202020', + $extensions: {mode: {dark: '#eeeeee', light: '#202020'}}, + _original: { + $extensions: {mode: {dark: '{color.gray.12#dark}', light: '{color.gray.12#light}'}}, // check aliases generated correctly + $type: 'color', + $value: '{color.gray.12}', + }, + }), + expect.objectContaining({ + id: 'space.1', + $type: 'number', + $value: 2, + $extensions: {mode: {dark: 2, light: 2}}, + }), + ]), + ); + }); + + it('transforming values', () => { + const {result, warnings, errors} = parse(TOKENS, { + color: {}, + figma: { + overrides: { + 'space/*': { + $type: 'dimension', + transform({variable, collection, mode}) { + const rawValue = variable.valuesByMode[mode.modeId]; + if (typeof rawValue === 'number') { + return `${rawValue / 16}rem`; + } + }, + }, + }, + }, + }); + + expect(warnings).toBeUndefined(); + expect(errors).toBeUndefined(); + + expect(result.tokens).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: 'space.5', + $type: 'dimension', + $value: '1rem', + $extensions: {mode: {dark: '1rem', light: '1rem'}}, + }), + ]), + ); + }); + + it('renaming values', () => { + const {result, warnings, errors} = parse(TOKENS, { + color: {}, + figma: { + overrides: { + 'color/*': { + rename(id) { + if (id.includes('gray')) { + return id.replace('gray', 'grey'); + } + }, + }, + }, + }, + }); + + expect(warnings).toBeUndefined(); + expect(errors).toBeUndefined(); + + expect(result.tokens).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: 'color.grey.5', + $type: 'color', + $value: '#e0e0e0', + $extensions: {mode: {dark: '#313131', light: '#e0e0e0'}}, + }), + ]), + ); + }); +}); diff --git a/packages/plugin-css/src/index.ts b/packages/plugin-css/src/index.ts index d2612193..f3c20598 100644 --- a/packages/plugin-css/src/index.ts +++ b/packages/plugin-css/src/index.ts @@ -1,8 +1,8 @@ import type {BuildResult, ParsedToken, Plugin, ResolvedConfig} from '@cobalt-ui/core'; -import {indent, FG_YELLOW, RESET} from '@cobalt-ui/utils'; +import {indent, isTokenMatch, FG_YELLOW, RESET} from '@cobalt-ui/utils'; import {formatCss} from 'culori'; import defaultTransformer, {type ColorFormat, toRGB} from './transform/index.js'; -import {CustomNameGenerator, isTokenMatch, makeNameGenerator} from './utils/token.js'; +import {CustomNameGenerator, makeNameGenerator} from './utils/token.js'; import {encode} from './utils/encode.js'; import generateUtilityCSS, {type UtilityCSSGroup} from './utils/utility-css.js'; diff --git a/packages/plugin-css/src/utils/token.ts b/packages/plugin-css/src/utils/token.ts index fe9a96e2..d64be585 100644 --- a/packages/plugin-css/src/utils/token.ts +++ b/packages/plugin-css/src/utils/token.ts @@ -1,6 +1,7 @@ import {ParsedToken} from '@cobalt-ui/core'; import {isAlias} from '@cobalt-ui/utils'; -import wcmatch from '../wildcard-match.js'; + +export {isTokenMatch} from '@cobalt-ui/utils'; const DASH_PREFIX_RE = /^-+/; const DASH_SUFFIX_RE = /-+$/; @@ -18,15 +19,6 @@ export function getMode=18'} + dev: true + + /@mswjs/interceptors@0.25.15: + resolution: {integrity: sha512-s4jdyxmq1eeftfDXJ7MUiK/jlvYaU8Sr75+42hHCVBrYez0k51RHbMitKIKdmsF92Q6gwhp8Sm1MmvdA9llpcg==} + engines: {node: '>=18'} + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.2 + strict-event-emitter: 0.5.1 + dev: true + /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1221,6 +1253,21 @@ packages: fastq: 1.17.0 dev: true + /@open-draft/deferred-promise@2.2.0: + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + dev: true + + /@open-draft/logger@0.3.0: + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.2 + dev: true + + /@open-draft/until@2.1.0: + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + dev: true + /@pkgjs/parseargs@0.11.0: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1378,6 +1425,10 @@ packages: engines: {node: '>=10.13.0'} dev: false + /@types/cookie@0.6.0: + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + dev: true + /@types/culori@2.0.4: resolution: {integrity: sha512-GeLW8+KBRkwqIgeGrU8EnNbBE2D7waYbQHkx2xnI5exlzSGTMpjWtDaHzLWK1PTYmyJN9u6dPvMYumFevDe+VA==} @@ -1434,6 +1485,10 @@ packages: resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} dev: true + /@types/statuses@2.0.4: + resolution: {integrity: sha512-eqNDvZsCNY49OAXB0Firg/Sc2BgoWsntsLUdybGFOhAfCD6QJ2n9HXUIHGqt5qjrxmMv4wS8WLAw43ZkKcJ8Pw==} + dev: true + /@types/web-bluetooth@0.0.20: resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} dev: true @@ -1932,6 +1987,13 @@ packages: engines: {node: '>=6'} dev: true + /ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.21.3 + dev: true + /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -2046,7 +2108,6 @@ packages: /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: false /better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} @@ -2059,6 +2120,14 @@ packages: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} + /bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: true + /boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} dev: false @@ -2088,6 +2157,13 @@ packages: wcwidth: 1.0.1 dev: true + /buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: true + /cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -2206,6 +2282,23 @@ packages: escape-string-regexp: 5.0.0 dev: true + /cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + dependencies: + restore-cursor: 3.1.0 + dev: true + + /cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + dev: true + + /cli-width@3.0.0: + resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} + engines: {node: '>= 10'} + dev: true + /cliui@6.0.0: resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} dependencies: @@ -2264,6 +2357,11 @@ packages: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true + /cookie@0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + dev: true + /cross-spawn@5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} dependencies: @@ -2899,6 +2997,13 @@ packages: reusify: 1.0.4 dev: true + /figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} + dependencies: + escape-string-regexp: 1.0.5 + dev: true + /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -3155,6 +3260,11 @@ packages: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true + /graphql@16.8.1: + resolution: {integrity: sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + dev: true + /hard-rejection@2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} @@ -3216,6 +3326,10 @@ packages: resolution: {integrity: sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==} dev: false + /headers-polyfill@4.0.2: + resolution: {integrity: sha512-EWGTfnTqAO2L/j5HZgoM/3z82L7necsJ0pO9Tp0X1wil3PDLrkypTBRgVO2ExehEEvUycejZD3FuRaXpZZc3kw==} + dev: true + /hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} dev: true @@ -3252,6 +3366,10 @@ packages: safer-buffer: 2.1.2 dev: true + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: true + /ignore@5.3.0: resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} engines: {node: '>= 4'} @@ -3296,6 +3414,27 @@ packages: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} dev: true + /inquirer@8.2.6: + resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} + engines: {node: '>=12.0.0'} + dependencies: + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-width: 3.0.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.17.21 + mute-stream: 0.0.8 + ora: 5.4.1 + run-async: 2.4.1 + rxjs: 7.8.1 + string-width: 4.2.3 + strip-ansi: 6.0.1 + through: 2.3.8 + wrap-ansi: 6.2.0 + dev: true + /internal-slot@1.0.6: resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==} engines: {node: '>= 0.4'} @@ -3370,11 +3509,20 @@ packages: dependencies: is-extglob: 2.1.1 + /is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + dev: true + /is-negative-zero@2.0.2: resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} engines: {node: '>= 0.4'} dev: true + /is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + dev: true + /is-number-object@1.0.7: resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} engines: {node: '>= 0.4'} @@ -3453,6 +3601,11 @@ packages: which-typed-array: 1.1.14 dev: true + /is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + dev: true + /is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: @@ -3608,6 +3761,18 @@ packages: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} dev: true + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: true + + /log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + dev: true + /loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} dependencies: @@ -3727,6 +3892,11 @@ packages: hasBin: true dev: false + /mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + dev: true + /mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} @@ -3799,6 +3969,42 @@ packages: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} dev: true + /msw@2.1.5(typescript@5.3.3): + resolution: {integrity: sha512-r39AZk4taMmUEYwtzDAgFy38feqJy1yaKykvo0QE8q7H7c28yH/WIlOmE7oatjkC3dMgpTYfND8MaxeywgU+Yg==} + engines: {node: '>=18'} + hasBin: true + requiresBuild: true + peerDependencies: + typescript: '>= 4.7.x <= 5.3.x' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@bundled-es-modules/cookie': 2.0.0 + '@bundled-es-modules/statuses': 1.0.1 + '@mswjs/cookies': 1.1.0 + '@mswjs/interceptors': 0.25.15 + '@open-draft/until': 2.1.0 + '@types/cookie': 0.6.0 + '@types/statuses': 2.0.4 + chalk: 4.1.2 + chokidar: 3.5.3 + graphql: 16.8.1 + headers-polyfill: 4.0.2 + inquirer: 8.2.6 + is-node-process: 1.2.0 + outvariant: 1.4.2 + path-to-regexp: 6.2.1 + strict-event-emitter: 0.5.1 + type-fest: 4.10.2 + typescript: 5.3.3 + yargs: 17.7.2 + dev: true + + /mute-stream@0.0.8: + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + dev: true + /mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} dependencies: @@ -3928,6 +4134,13 @@ packages: wrappy: 1.0.2 dev: true + /onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + dependencies: + mimic-fn: 2.1.0 + dev: true + /onetime@6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} @@ -3947,6 +4160,21 @@ packages: type-check: 0.4.0 dev: true + /ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + dev: true + /os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} @@ -3956,6 +4184,10 @@ packages: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} dev: true + /outvariant@1.4.2: + resolution: {integrity: sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ==} + dev: true + /p-filter@2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} @@ -4068,6 +4300,10 @@ packages: minipass: 7.0.4 dev: true + /path-to-regexp@6.2.1: + resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} + dev: true + /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -4399,6 +4635,15 @@ packages: strip-bom: 3.0.0 dev: true + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + dev: true + /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -4462,6 +4707,14 @@ packages: supports-preserve-symlinks-flag: 1.0.0 dev: true + /restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + dev: true + /reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -4501,12 +4754,23 @@ packages: fsevents: 2.3.3 dev: true + /run-async@2.4.1: + resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} + engines: {node: '>=0.12.0'} + dev: true + /run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: queue-microtask: 1.2.3 dev: true + /rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + dependencies: + tslib: 2.6.2 + dev: true + /safe-array-concat@1.1.0: resolution: {integrity: sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==} engines: {node: '>=0.4'} @@ -4517,6 +4781,10 @@ packages: isarray: 2.0.5 dev: true + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: true + /safe-regex-test@1.0.2: resolution: {integrity: sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ==} engines: {node: '>= 0.4'} @@ -4691,6 +4959,11 @@ packages: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} dev: true + /statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + dev: true + /std-env@3.7.0: resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} dev: true @@ -4701,6 +4974,10 @@ packages: mixme: 0.5.10 dev: true + /strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + dev: true + /string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -4744,6 +5021,12 @@ packages: es-abstract: 1.22.3 dev: true + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + dev: true + /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -4950,6 +5233,10 @@ packages: any-promise: 1.3.0 dev: true + /through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + dev: true + /tinybench@2.6.0: resolution: {integrity: sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==} dev: true @@ -5048,6 +5335,11 @@ packages: engines: {node: '>=10'} dev: true + /type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + dev: true + /type-fest@0.6.0: resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} engines: {node: '>=8'} @@ -5063,6 +5355,11 @@ packages: engines: {node: '>=10'} dev: true + /type-fest@4.10.2: + resolution: {integrity: sha512-anpAG63wSpdEbLwOqH8L84urkL6PiVIov3EMmgIhhThevh9aiMQov+6Btx0wldNcvm4wV+e2/Rt1QdDwKHFbHw==} + engines: {node: '>=16'} + dev: true + /typed-array-buffer@1.0.0: resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} engines: {node: '>= 0.4'}