From 678fa6692b11617e2f8cc723b8dc230fd8ce791b Mon Sep 17 00:00:00 2001 From: Ken <26967723+KenAJoh@users.noreply.github.com> Date: Fri, 4 Oct 2024 11:19:43 +0200 Subject: [PATCH] [Darkside] Figma plugin for updating variables (#3191) * feat: Implemented figma plugin * feat: Can now detect production/development build * feat: Now supports remote fetch for config * misc: Stop reset from running by default * feat:Semantic value handling * feat: Pre-semantic implementation * feat: Finished plugin API for semantic variables * feat: Use this for Figma plugin extension * bug: Fixed semantic collection naming * bug: Handle reset correctly * misc: avoid resetting when updating * memo: Added comments * bug: Ignore plugin buildfile from lint * bug: Fix support for dynamic-page load in figma * :memo: Better naming, simplified code-def syntax * feat: Scoped ignorePattern for plugin.js * memo: better logs for completing update * Update @navikt/core/tokens/darkside/figma/plugin/AkselVariablesInterface.ts Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com> * Update @navikt/core/tokens/darkside/figma/plugin/AkselVariablesInterface.ts Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com> * memo: Removed extra space for info logging * refactor: Simplify for-loop for semantic color modes * refactor: Inline creation of mode, collection and variable * refactor: readable mapping variables * feat: Better error-handling if variable is null * refactor: Remove redundant getModes function * refactor: Remove redundant getModes function * refactor: Remove plugin watch function --------- Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com> --- .eslintrc.js | 1 + @navikt/core/tokens/.gitignore | 3 +- .../tokens/darkside/figma/create-tokens.ts | 2 +- .../darkside/figma/figma-config.types.ts | 16 +- @navikt/core/tokens/darkside/figma/index.ts | 42 ++- .../figma/plugin/AkselVariablesInterface.ts | 234 ++++++++++++++++ .../figma/plugin/FigmaPluginInterface.ts | 210 +++++++++++++++ .../darkside/figma/plugin/manifest.json | 13 + .../tokens/darkside/figma/plugin/plugin.ts | 17 ++ @navikt/core/tokens/package.json | 5 +- package.json | 3 +- yarn.lock | 252 ++++++++++++++++++ 12 files changed, 773 insertions(+), 25 deletions(-) create mode 100644 @navikt/core/tokens/darkside/figma/plugin/AkselVariablesInterface.ts create mode 100644 @navikt/core/tokens/darkside/figma/plugin/FigmaPluginInterface.ts create mode 100644 @navikt/core/tokens/darkside/figma/plugin/manifest.json create mode 100644 @navikt/core/tokens/darkside/figma/plugin/plugin.ts diff --git a/.eslintrc.js b/.eslintrc.js index 8a42505589..d699afe887 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -156,5 +156,6 @@ module.exports = { "**/codemod/**/*.js", "!.storybook", "**/playwright-report/**", + "**/tokens/**/plugin.js", ], }; diff --git a/@navikt/core/tokens/.gitignore b/@navikt/core/tokens/.gitignore index 71f7879aee..c212b8a285 100644 --- a/@navikt/core/tokens/.gitignore +++ b/@navikt/core/tokens/.gitignore @@ -1 +1,2 @@ -figma-config.json \ No newline at end of file +figma-config.json +plugin.js diff --git a/@navikt/core/tokens/darkside/figma/create-tokens.ts b/@navikt/core/tokens/darkside/figma/create-tokens.ts index 88d445ae58..15d1ea2714 100644 --- a/@navikt/core/tokens/darkside/figma/create-tokens.ts +++ b/@navikt/core/tokens/darkside/figma/create-tokens.ts @@ -77,7 +77,7 @@ function prepareToken( comment: token.comment, group: token.group, code: { - web: `var(${cssVariable.trim().split(": ")[0]})`, + WEB: `var(${cssVariable.trim().split(": ")[0]})`, }, ...figmaSettings(token), }; diff --git a/@navikt/core/tokens/darkside/figma/figma-config.types.ts b/@navikt/core/tokens/darkside/figma/figma-config.types.ts index 9e34062b44..1c491cce93 100644 --- a/@navikt/core/tokens/darkside/figma/figma-config.types.ts +++ b/@navikt/core/tokens/darkside/figma/figma-config.types.ts @@ -3,15 +3,13 @@ import { StyleDictionaryToken, TokenTypes } from "../util"; export type FigmaToken = Omit, "value"> & { name: string; alias?: string; - code: { - web: string; - }; + code: Variable["codeSyntax"]; value: string | number; figmaType: VariableResolvedDataType; scopes: VariableScope[]; }; -type FigmaConfigEntry = { +export type FigmaConfigEntry = { name: string; hideFromPublishing: boolean; tokens: FigmaToken[]; @@ -20,9 +18,13 @@ type FigmaConfigEntry = { export type FigmaTokenConfig = { version: string; timestamp: string; - globalLight: FigmaConfigEntry; - globalDark: FigmaConfigEntry; - semanticColors: FigmaConfigEntry; + colors: { light: ColorThemeEntry; dark: ColorThemeEntry }; radius: FigmaConfigEntry; spacing: FigmaConfigEntry; }; + +type ColorThemeEntry = { + name: "light" | "dark"; + global: FigmaConfigEntry; + semantic: FigmaConfigEntry; +}; diff --git a/@navikt/core/tokens/darkside/figma/index.ts b/@navikt/core/tokens/darkside/figma/index.ts index 585443b87b..aa799a0eb5 100644 --- a/@navikt/core/tokens/darkside/figma/index.ts +++ b/@navikt/core/tokens/darkside/figma/index.ts @@ -26,20 +26,34 @@ async function buildFigmaConfig() { timestamp: new Date().toLocaleString("no-NO", { timeZone: "Europe/Oslo", }), - globalLight: { - name: "Global colors light", - hideFromPublishing: true, - tokens: lightTokens.filter(isGlobalColor), - }, - globalDark: { - name: "Global colors dark", - hideFromPublishing: true, - tokens: darkTokens.filter(isGlobalColor), - }, - semanticColors: { - name: "Semantic colors", - hideFromPublishing: false, - tokens: lightTokens.filter(isSemanticColor), + + colors: { + light: { + name: "light", + global: { + name: "Global colors light", + hideFromPublishing: true, + tokens: lightTokens.filter(isGlobalColor), + }, + semantic: { + name: "Semantic colors", + hideFromPublishing: false, + tokens: lightTokens.filter(isSemanticColor), + }, + }, + dark: { + name: "dark", + global: { + name: "Global colors dark", + hideFromPublishing: true, + tokens: darkTokens.filter(isGlobalColor), + }, + semantic: { + name: "Semantic colors", + hideFromPublishing: false, + tokens: darkTokens.filter(isSemanticColor), + }, + }, }, radius: { name: "Radius", diff --git a/@navikt/core/tokens/darkside/figma/plugin/AkselVariablesInterface.ts b/@navikt/core/tokens/darkside/figma/plugin/AkselVariablesInterface.ts new file mode 100644 index 0000000000..35b90721ef --- /dev/null +++ b/@navikt/core/tokens/darkside/figma/plugin/AkselVariablesInterface.ts @@ -0,0 +1,234 @@ +import _config from "../../../figma-config.json"; +import { FigmaConfigEntry, FigmaTokenConfig } from "../figma-config.types"; +import { FigmaPluginInterface } from "./FigmaPluginInterface"; + +type ScopedFigmaTokenConfig = Omit; + +export class AkselVariablesInterface { + private Figma: FigmaPluginInterface; + private config: FigmaTokenConfig; + private meta: Pick; + private remoteConfigURL = + "https://cdn.nav.no/designsystem/@navikt/tokens/figma-config.json"; + + constructor() { + const config = _config as FigmaTokenConfig; + this.config = config; + this.meta = { timestamp: config.timestamp, version: config.version }; + this.Figma = new FigmaPluginInterface(); + } + + /** + * Because of "dynamic-page" loading in Figma, + * we need to initialize the plugin trough async methods. + * This method should be called before any other methods. + */ + async init(): Promise { + await this.Figma.init(); + } + + async useRemoteConfig(): Promise { + const newConfig = await fetch(this.remoteConfigURL) + .then((res) => res.json()) + .catch((err) => { + console.error(err); + throw new Error("Error fetching config from CDN 😱"); + }); + this.meta = { timestamp: newConfig.timestamp, version: newConfig.version }; + + this.config = newConfig; + } + + updateVariables(): void { + this.updateGlobalColorCollection(this.config.colors.light.global); + this.updateGlobalColorCollection(this.config.colors.dark.global); + this.updateSemanticColorCollection(this.config.colors); + this.updateScaleCollection(this.config.radius); + this.updateScaleCollection(this.config.spacing); + console.info("Variables updated!"); + } + + private updateGlobalColorCollection( + globalColorTheme: FigmaConfigEntry, + ): void { + const collection = + this.Figma.getCollection(globalColorTheme.name) ?? + this.Figma.createCollection(globalColorTheme.name); + + /** + * Correctly sorts the token-scale for global colors + * 000 - 100 - 200... etc. + */ + const getLastNumber = (name: string) => { + const matches = name.match(/\d+/g); + return matches ? parseInt(matches[matches.length - 1], 10) : 0; + }; + const sortedTokens = globalColorTheme.tokens.sort( + (a, b) => getLastNumber(a.name) - getLastNumber(b.name), + ); + + for (const token of sortedTokens) { + /* Color values can only be defined by strings */ + if (typeof token.value !== "string") { + throw new Error(`Token value is not a string: ${token}`); + } + + const variable = + this.Figma.getVariable(token.name, collection.id) ?? + this.Figma.createVariable(token.name, collection, token.figmaType); + + this.Figma.setVariableValue( + variable, + figma.util.rgba(token.value), + collection.defaultModeId, + ); + + this.Figma.setVariableMetadata(variable, { + codeSyntax: token.code, + description: token.comment ?? "", + hiddenFromPublishing: collection.hiddenFromPublishing, + scopes: token.scopes, + }); + } + + console.info("Updated collection:", collection.name); + } + + private updateScaleCollection( + globalScale: + | ScopedFigmaTokenConfig["radius"] + | ScopedFigmaTokenConfig["spacing"], + ): void { + const collection = + this.Figma.getCollection(globalScale.name) ?? + this.Figma.createCollection(globalScale.name); + + const sortedTokens = globalScale.tokens.sort((a, b) => { + if (typeof a.value === "number" && typeof b.value === "number") { + return a.value - b.value; + } + return 0; + }); + + for (const token of sortedTokens) { + const variable = + this.Figma.getVariable(token.name, collection.id) ?? + this.Figma.createVariable(token.name, collection, token.figmaType); + + this.Figma.setVariableValue( + variable, + token.value, + collection.defaultModeId, + ); + + this.Figma.setVariableMetadata(variable, { + codeSyntax: token.code, + description: token.comment ?? "", + hiddenFromPublishing: collection.hiddenFromPublishing, + scopes: token.scopes, + }); + } + + console.info("Updated collection:", collection.name); + } + + private updateSemanticColorCollection( + colorsConfig: ScopedFigmaTokenConfig["colors"], + ): void { + const semanticCollectionName = colorsConfig.light.semantic.name; + + const collection = + this.Figma.getCollection(semanticCollectionName) ?? + this.Figma.createCollection(semanticCollectionName); + + for (const colorEntry of Object.values(colorsConfig)) { + const modeName = colorEntry.name; + + const globalCollection = this.Figma.getCollection( + colorsConfig[modeName].global.name, + ); + + if (!globalCollection) { + throw new Error( + `Global collection not found for: ${colorsConfig[modeName].global.name}`, + ); + } + + const mode = + this.Figma.getModeWithName(modeName, collection) ?? + this.Figma.createMode(modeName, collection); + + for (const token of colorsConfig[modeName].semantic.tokens) { + const variable = + this.Figma.getVariable(token.name, collection.id) ?? + this.Figma.createVariable(token.name, collection, token.figmaType); + + if (!token.alias) { + if (typeof token.value !== "string") { + throw new Error( + `Semantic tokens without alias requires value to be string: ${token}`, + ); + } + + this.Figma.setVariableValue( + variable, + figma.util.rgba(token.value), + mode.modeId, + ); + continue; + } + + const globalVariable = this.Figma.getVariable( + token.alias, + globalCollection.id, + ); + + if (!globalVariable) { + throw new Error( + `Global variable not found for alias: ${token.alias}`, + ); + } + + this.Figma.setVariableValue( + variable, + this.Figma.createVariableAlias(globalVariable), + mode.modeId, + ); + + this.Figma.setVariableMetadata(variable, { + codeSyntax: token.code, + description: token.comment ?? "", + hiddenFromPublishing: collection.hiddenFromPublishing, + scopes: token.scopes, + }); + } + + /* Make sure to remove "default" modes if they exist */ + this.Figma.removeNonMatchingModes( + Object.values(colorsConfig).map((colorConfig) => colorConfig.name), + collection, + ); + } + + console.info("Updated collection:", collection.name); + } + + /** + * Deletes all local collections in the Figma file. + * @important Use with caution! If the current variables are not published in figma, + * they will be lost forever! + */ + resetVariables(): void { + this.Figma.resetVariables(); + } + + exitWithMessage(message: string): void { + this.Figma.exit(message); + } + + exit(): void { + this.Figma.exit( + `Finished updating variables for version ${this.meta.version}, last updated at ${this.meta.timestamp}`, + ); + } +} diff --git a/@navikt/core/tokens/darkside/figma/plugin/FigmaPluginInterface.ts b/@navikt/core/tokens/darkside/figma/plugin/FigmaPluginInterface.ts new file mode 100644 index 0000000000..247fc9993a --- /dev/null +++ b/@navikt/core/tokens/darkside/figma/plugin/FigmaPluginInterface.ts @@ -0,0 +1,210 @@ +/** + * Acts as a wrapper for interfacing with the Figma PluginAPI + * @see https://www.figma.com/plugin-docs/api/api-reference/ + */ +export class FigmaPluginInterface { + private collections: VariableCollection[]; + private variables: Variable[]; + + constructor() { + this.collections = []; + this.variables = []; + } + + async init() { + console.info("Initializing plugin..."); + const collections = + await figma.variables.getLocalVariableCollectionsAsync(); + const variables: Variable[] = []; + + for (const collection of collections) { + for (const id of collection.variableIds) { + const variable = await figma.variables.getVariableByIdAsync(id); + if (!variable) { + throw new Error( + `Variable with id ${id} not found in collection ${collection.name}`, + ); + } + variables.push(variable); + } + } + + this.collections = collections; + this.variables = variables; + console.info("Plugin initialized!"); + } + + /* -------------------------------------------------------------------------- */ + /* Collection handling */ + /* -------------------------------------------------------------------------- */ + getCollection(name: string): VariableCollection | undefined { + return this.collections.find((col) => col.name === name); + } + + /** + * Creates a new collection if it doesn't already exist. + * If it does exist, it will return the existing collection. + */ + createCollection(name: string): VariableCollection { + const existingCollection = this.getCollection(name); + if (existingCollection) { + console.info("Collection already exists, skipping creation: ", name); + return existingCollection; + } + + const collection = figma.variables.createVariableCollection(name); + this.collections.push(collection); + console.info("Creating new collection: ", name); + + return collection; + } + + /* -------------------------------------------------------------------------- */ + /* Variable handling */ + /* -------------------------------------------------------------------------- */ + /** + * Returns a variable if it exists in the specified collection. + * Since variables can have the same name in different collections, the collectionId is required. + */ + getVariable(name: string, collectionId: string): Variable | undefined { + return this.variables.find( + (figmaVariable) => + figmaVariable.name === name && + figmaVariable.variableCollectionId === collectionId, + ); + } + + /** + * Creates a new variable if it doesn't already exist. + * If it does exist, it will return the existing variable. + */ + createVariable( + name: string, + collection: VariableCollection, + type: VariableResolvedDataType, + ): Variable { + const existingVariable = this.getVariable(name, collection.id); + if (existingVariable) { + console.info("Variable already exists, skipping creation: ", name); + return existingVariable; + } + + const variable = figma.variables.createVariable(name, collection, type); + this.variables.push(variable); + return variable; + } + + createVariableAlias(variable: Variable) { + return figma.variables.createVariableAlias(variable); + } + + setVariableValue( + variable: Variable, + value: VariableValue, + modeId: string, + ): void { + variable.setValueForMode(modeId, value); + } + + setVariableMetadata( + variable: Variable, + metadata: Pick< + Variable, + "scopes" | "codeSyntax" | "hiddenFromPublishing" | "description" + >, + ): void { + const platforms: CodeSyntaxPlatform[] = ["WEB", "ANDROID", "iOS"]; + platforms.forEach((platform: CodeSyntaxPlatform) => { + if (metadata.codeSyntax?.[platform]) { + variable.setVariableCodeSyntax(platform, metadata.codeSyntax[platform]); + } + }); + + variable.scopes = metadata.scopes; + variable.description = metadata.description ?? ""; + variable.hiddenFromPublishing = metadata.hiddenFromPublishing; + } + + /* -------------------------------------------------------------------------- */ + /* Mode handling */ + /* -------------------------------------------------------------------------- */ + + getModeWithName( + name: T, + collection: VariableCollection, + ): + | { + modeId: string; + name: T; + } + | undefined { + const foundMode = collection.modes.find((mode) => mode.name === name); + + return foundMode ? { modeId: foundMode.modeId, name } : undefined; + } + + /** + * Creates a new mode if it doesn't already exist. + * If it does exist, it will return the existing mode. + */ + createMode( + name: T, + collection: VariableCollection, + ): { + modeId: string; + name: T; + } { + const existingMode = collection.modes.find((mode) => mode.name === name); + if (existingMode) { + console.info("Mode already exists, skipping creation: ", name); + return { modeId: existingMode.modeId, name }; + } + + return { + modeId: collection.addMode(name), + name, + }; + } + + /** + * Removes all modes that are not in the provided list. + * Usefull for removing "default" modes, while preserving the custom modes. + */ + removeNonMatchingModes( + modes: string[], + collection: VariableCollection, + ): { + modeId: string; + name: string; + }[] { + const existingModes = collection.modes; + + for (const mode of existingModes) { + if (!modes.find((m) => m === mode.name)) { + collection.removeMode(mode.modeId); + } + } + + return collection.modes; + } + + /* -------------------------------------------------------------------------- */ + /* Utilities */ + /* -------------------------------------------------------------------------- */ + /** + * Resets all variables and collections in the document. Use with caution! + */ + resetVariables() { + this.collections.forEach((collection) => { + console.info("Deleting collection: ", collection.name); + collection.remove(); + }); + this.collections = []; + this.variables = []; + } + + exit(message?: string) { + console.info("Exiting plugin..."); + figma.closePlugin(message); + } +} diff --git a/@navikt/core/tokens/darkside/figma/plugin/manifest.json b/@navikt/core/tokens/darkside/figma/plugin/manifest.json new file mode 100644 index 0000000000..aa429271f3 --- /dev/null +++ b/@navikt/core/tokens/darkside/figma/plugin/manifest.json @@ -0,0 +1,13 @@ +{ + "name": "Aksel tokens to variables", + "id": "1416051171368451170", + "api": "1.0.0", + "main": "plugin.js", + "capabilities": [], + "enableProposedApi": false, + "editorType": ["figma"], + "documentAccess": "dynamic-page", + "networkAccess": { + "allowedDomains": ["https://cdn.nav.no"] + } +} diff --git a/@navikt/core/tokens/darkside/figma/plugin/plugin.ts b/@navikt/core/tokens/darkside/figma/plugin/plugin.ts new file mode 100644 index 0000000000..3e709633af --- /dev/null +++ b/@navikt/core/tokens/darkside/figma/plugin/plugin.ts @@ -0,0 +1,17 @@ +import { AkselVariablesInterface } from "./AkselVariablesInterface"; + +const plugin = new AkselVariablesInterface(); + +main().catch((err) => + plugin.exitWithMessage(`Error updating local variables: ${err.message}`), +); + +async function main() { + await plugin.init(); + if (process.env.NODE_ENV === "production") { + await plugin.useRemoteConfig(); + } + + plugin.updateVariables(); + plugin.exit(); +} diff --git a/@navikt/core/tokens/package.json b/@navikt/core/tokens/package.json index adbd0b9290..8685742422 100644 --- a/@navikt/core/tokens/package.json +++ b/@navikt/core/tokens/package.json @@ -19,8 +19,10 @@ ], "scripts": { "build": "tsx ./config/build.ts > /dev/null && tsx ./config/version-tag.ts && yarn build:darkside", - "build:darkside": "tsx ./darkside && yarn build:figma-config", + "build:darkside": "tsx ./darkside && yarn build:figma-config && yarn build:plugin", "build:figma-config": "tsx ./darkside/figma", + "build:plugin-dev": "esbuild darkside/figma/plugin/plugin.ts --target=es2016 --bundle --define:process.env.NODE_ENV=\\\"development\\\" --outfile='darkside/figma/plugin/plugin.js'", + "build:plugin": "esbuild darkside/figma/plugin/plugin.ts --target=es2016 --bundle --define:process.env.NODE_ENV=\\\"production\\\" --outfile='darkside/figma/plugin/plugin.js'", "test": "vitest run", "test:watch": "vitest watch" }, @@ -33,6 +35,7 @@ "@adobe/leonardo-contrast-colors": "^1.0.0", "@figma/plugin-typings": "^1.100.2", "color": "4.2.3", + "esbuild": "^0.24.0", "lightningcss": "^1.27.0", "lodash": "^4.17.21", "style-dictionary": "^4.1.1", diff --git a/package.json b/package.json index 99118082e1..9cc3b5387d 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,8 @@ "**/dist/**", "**/@navikt/codemod/**", "**/@navikt/aksel/**", - "**/examples/**" + "**/examples/**", + "plugin.js" ] }, "repository": { diff --git a/yarn.lock b/yarn.lock index b1e18cbffe..2a0d32a9e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2582,6 +2582,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/aix-ppc64@npm:0.24.0" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-arm64@npm:0.18.20" @@ -2603,6 +2610,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/android-arm64@npm:0.24.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-arm@npm:0.18.20" @@ -2624,6 +2638,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/android-arm@npm:0.24.0" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-x64@npm:0.18.20" @@ -2645,6 +2666,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/android-x64@npm:0.24.0" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/darwin-arm64@npm:0.18.20" @@ -2666,6 +2694,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/darwin-arm64@npm:0.24.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/darwin-x64@npm:0.18.20" @@ -2687,6 +2722,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/darwin-x64@npm:0.24.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/freebsd-arm64@npm:0.18.20" @@ -2708,6 +2750,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/freebsd-arm64@npm:0.24.0" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/freebsd-x64@npm:0.18.20" @@ -2729,6 +2778,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/freebsd-x64@npm:0.24.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-arm64@npm:0.18.20" @@ -2750,6 +2806,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-arm64@npm:0.24.0" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-arm@npm:0.18.20" @@ -2771,6 +2834,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-arm@npm:0.24.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-ia32@npm:0.18.20" @@ -2792,6 +2862,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-ia32@npm:0.24.0" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-loong64@npm:0.18.20" @@ -2813,6 +2890,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-loong64@npm:0.24.0" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-mips64el@npm:0.18.20" @@ -2834,6 +2918,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-mips64el@npm:0.24.0" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-ppc64@npm:0.18.20" @@ -2855,6 +2946,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-ppc64@npm:0.24.0" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-riscv64@npm:0.18.20" @@ -2876,6 +2974,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-riscv64@npm:0.24.0" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-s390x@npm:0.18.20" @@ -2897,6 +3002,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-s390x@npm:0.24.0" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-x64@npm:0.18.20" @@ -2918,6 +3030,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/linux-x64@npm:0.24.0" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/netbsd-x64@npm:0.18.20" @@ -2939,6 +3058,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/netbsd-x64@npm:0.24.0" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-arm64@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/openbsd-arm64@npm:0.23.1" @@ -2946,6 +3072,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-arm64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/openbsd-arm64@npm:0.24.0" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/openbsd-x64@npm:0.18.20" @@ -2967,6 +3100,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/openbsd-x64@npm:0.24.0" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/sunos-x64@npm:0.18.20" @@ -2988,6 +3128,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/sunos-x64@npm:0.24.0" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/win32-arm64@npm:0.18.20" @@ -3009,6 +3156,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/win32-arm64@npm:0.24.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/win32-ia32@npm:0.18.20" @@ -3030,6 +3184,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/win32-ia32@npm:0.24.0" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/win32-x64@npm:0.18.20" @@ -3051,6 +3212,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.24.0": + version: 0.24.0 + resolution: "@esbuild/win32-x64@npm:0.24.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": version: 4.4.0 resolution: "@eslint-community/eslint-utils@npm:4.4.0" @@ -3866,6 +4034,7 @@ __metadata: "@adobe/leonardo-contrast-colors": "npm:^1.0.0" "@figma/plugin-typings": "npm:^1.100.2" color: "npm:4.2.3" + esbuild: "npm:^0.24.0" lightningcss: "npm:^1.27.0" lodash: "npm:^4.17.21" style-dictionary: "npm:^4.1.1" @@ -11647,6 +11816,89 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.24.0": + version: 0.24.0 + resolution: "esbuild@npm:0.24.0" + dependencies: + "@esbuild/aix-ppc64": "npm:0.24.0" + "@esbuild/android-arm": "npm:0.24.0" + "@esbuild/android-arm64": "npm:0.24.0" + "@esbuild/android-x64": "npm:0.24.0" + "@esbuild/darwin-arm64": "npm:0.24.0" + "@esbuild/darwin-x64": "npm:0.24.0" + "@esbuild/freebsd-arm64": "npm:0.24.0" + "@esbuild/freebsd-x64": "npm:0.24.0" + "@esbuild/linux-arm": "npm:0.24.0" + "@esbuild/linux-arm64": "npm:0.24.0" + "@esbuild/linux-ia32": "npm:0.24.0" + "@esbuild/linux-loong64": "npm:0.24.0" + "@esbuild/linux-mips64el": "npm:0.24.0" + "@esbuild/linux-ppc64": "npm:0.24.0" + "@esbuild/linux-riscv64": "npm:0.24.0" + "@esbuild/linux-s390x": "npm:0.24.0" + "@esbuild/linux-x64": "npm:0.24.0" + "@esbuild/netbsd-x64": "npm:0.24.0" + "@esbuild/openbsd-arm64": "npm:0.24.0" + "@esbuild/openbsd-x64": "npm:0.24.0" + "@esbuild/sunos-x64": "npm:0.24.0" + "@esbuild/win32-arm64": "npm:0.24.0" + "@esbuild/win32-ia32": "npm:0.24.0" + "@esbuild/win32-x64": "npm:0.24.0" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10/500f83a1216d6548053007b85c070d8293395db344605b17418c6cf1217e5e8d338fa77fc8af27c23faa121c5528e5b0004d46d3a0cdeb87d48f1b5fa0164bc5 + languageName: node + linkType: hard + "escalade@npm:^3.1.1, escalade@npm:^3.1.2": version: 3.2.0 resolution: "escalade@npm:3.2.0"