diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 360adfb1..1de02b92 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -55,10 +55,14 @@ jobs: with: ref: ${{ steps.ref.outputs.tag }} + # NOTE: do NOT set `registry-url` here. setup-node would write an .npmrc + # with `_authToken=${NODE_AUTH_TOKEN}` and export a placeholder + # NODE_AUTH_TOKEN, causing npm to authenticate the publish with a bogus + # token (HTTP 404) instead of falling back to OIDC Trusted Publishing. + # The registry is set via the repo .npmrc and the package's publishConfig. - uses: actions/setup-node@v4 with: node-version: '22' # Trusted Publishing needs Node >= 22.14.0 - registry-url: 'https://registry.npmjs.org' - name: Ensure npm supports Trusted Publishing (>= 11.5.1) run: npm install -g npm@latest @@ -82,6 +86,13 @@ jobs: exit 1 fi + # specs-schema is a workspace member whose dist/ is gitignored, and the CLI + # imports runtime values from it (e.g. DEFAULT_CONFIG). Build it before the + # CLI build/test or module resolution fails in CI. specs-from-figma is an + # external (registry) dependency and ships its own built+obfuscated dist. + - name: Build schema (workspace dependency) + run: npm run build --workspace=packages/schema + - name: Build run: npm run build --workspace=packages/cli diff --git a/adr/051-platform-token-syntax.md b/adr/051-platform-token-syntax.md new file mode 100644 index 00000000..c569aa20 --- /dev/null +++ b/adr/051-platform-token-syntax.md @@ -0,0 +1,209 @@ +# ADR: Platform Code-Syntax Token Profiles + +**Branch**: `051-platform-token-syntax` +**Created**: 2026-05-20 +**Status**: ACCEPTED +**Deciders**: Nathan Curtis (author) +**Supersedes**: *(none)* + +--- + +## Context + +`format.tokens` is the token-reference serialization profile on `Config` (and its +resolved counterpart `ResolvedConfig`). It currently accepts: + +```yaml +format: + tokens: 'TOKEN' | 'TOKEN_NAME' | 'TOKEN_FIGMA_EXTENSIONS' | 'FIGMA_NAME' | 'CUSTOM' # default: TOKEN +``` + +`TOKEN` (the default) emits platform-neutral token references (a `$token` path +plus `$type`). None of the existing profiles expose Figma's **per-platform code +syntax** — the developer-facing token name a designer assigns for `WEB`, +`ANDROID`, and `iOS` via Figma's `codeSyntax` API +([CodeSyntaxPlatform](https://developers.figma.com/docs/plugins/api/CodeSyntaxPlatform/#code-syntax-platform)). + +Specs Classic already offered platform code-syntax selection as its +`CODE_SYNTAX` property. Specs 2 has no equivalent, so the plugin cannot reach +parity (tracked in [issue #103](https://github.com/DirectedEdges/specs/issues/103)). +This ADR records the **contract** change needed to close that gap: new +serialization profiles selecting a platform's code syntax, with a graceful +fall back to the `TOKEN` default when a token has no code syntax defined for the +chosen platform. + +--- + +## Decision Drivers + +- **Additive only**: Existing `format.tokens` values and the `TOKEN` default must + be untouched, so the change is `MINOR` and breaks no downstream consumer. +- **Type ↔ schema parity**: The enum must change identically in `types/Config.ts` + and `schema/component.schema.json` (Constitution I). +- **No logic in schema (Constitution II)**: The fall-back-to-`TOKEN` behaviour is + a transformer responsibility; the schema only enumerates the selectable + profiles. `DEFAULT_CONFIG` stays `TOKEN`. +- **Naming governance (Constitution VI)**: The feature is intrinsically a Figma + concept (Figma's `codeSyntax` per `CodeSyntaxPlatform`). No code platform owns + the term, and re-modelling it away from Figma's `WEB`/`ANDROID`/`iOS` + vocabulary would lose round-trip fidelity — Rule 3 (defer to Figma) applies. +- **Parity with Specs Classic**: The profiles must map cleanly to Classic's + `CODE_SYNTAX` selection so plugin behaviour is equivalent. + +--- + +## Options Considered + +### Option A: Add three platform profiles to `format.tokens` *(Selected)* + +Extend the existing `format.tokens` enum with `FIGMA_SYNTAX_WEB`, +`FIGMA_SYNTAX_IOS`, and `FIGMA_SYNTAX_ANDROID`. Each selects the corresponding +platform's Figma `codeSyntax` value, falling back to the `TOKEN` profile's +output when that platform has no code syntax defined. + +**Pros**: +- Additive — no existing value or default changes (`MINOR`). +- Reuses the one knob designers already understand for token serialization. +- Mirrors the structure of the existing `TOKEN_*` / `FIGMA_NAME` family. + +**Cons / Trade-offs**: +- Couples platform selection into a single enum rather than a dedicated field; + acceptable because all values are mutually exclusive serialization profiles. + +--- + +### Option B: New dedicated `format.codeSyntaxPlatform` field *(Rejected)* + +Add a separate `format.codeSyntaxPlatform?: 'WEB' | 'IOS' | 'ANDROID'` field +orthogonal to `format.tokens`. + +**Rejected because**: It creates two interacting knobs (which wins when both are +set?), expanding the contract surface and the fall-back matrix the transformer +must define — more complexity for no expressive gain over Option A. The values +are mutually exclusive profiles, which `format.tokens` already models. + +--- + +### Option C: Single `FIGMA_SYNTAX` profile resolving platform at runtime *(Rejected)* + +One enum value whose target platform is inferred from environment/context. + +**Rejected because**: It pushes platform selection into runtime logic and hides +intent from the serialized config, undermining the explicit, mechanically +verifiable contract. Parity with Classic's explicit per-platform selection would +be lost. + +--- + +## Decision + +Add three platform code-syntax profiles to the `format.tokens` enum. The default +remains `TOKEN`. When the selected platform has no `codeSyntax` for a given +token, the transformer emits the `TOKEN`-profile output (documented behaviour, +not a schema constraint). + +### Type changes (`types/`) + +| File | Change | Bump | +|------|--------|------| +| `Config.ts` | Add `FIGMA_SYNTAX_WEB`, `FIGMA_SYNTAX_IOS`, `FIGMA_SYNTAX_ANDROID` to the `format.tokens` union on `Config` | MINOR | +| `Config.ts` | Add the same three members to the `format.tokens` union on `ResolvedConfig` | MINOR | + +**Example — new shape** (`types/Config.ts`): +```yaml +# Before +format: + tokens?: 'TOKEN' | 'TOKEN_NAME' | 'TOKEN_FIGMA_EXTENSIONS' | 'FIGMA_NAME' | 'CUSTOM' + +# After +format: + tokens?: 'TOKEN' | 'TOKEN_NAME' | 'TOKEN_FIGMA_EXTENSIONS' | 'FIGMA_NAME' | 'CUSTOM' + | 'FIGMA_SYNTAX_WEB' | 'FIGMA_SYNTAX_IOS' | 'FIGMA_SYNTAX_ANDROID' +# default unchanged: TOKEN +``` + +`DEFAULT_CONFIG.format.tokens` stays `'TOKEN'` — unchanged. + +### Schema changes (`schema/`) + +| File | Change | Bump | +|------|--------|------| +| `component.schema.json` | Append `FIGMA_SYNTAX_WEB`, `FIGMA_SYNTAX_IOS`, `FIGMA_SYNTAX_ANDROID` to `#/definitions/Config/.../tokens.enum` | MINOR | + +**Example — new shape** (`schema/component.schema.json`, `#/definitions/Config` → `format.tokens`): +```yaml +tokens: + type: string + enum: + - TOKEN + - TOKEN_NAME + - TOKEN_FIGMA_EXTENSIONS + - FIGMA_NAME + - CUSTOM + - FIGMA_SYNTAX_WEB # new + - FIGMA_SYNTAX_IOS # new + - FIGMA_SYNTAX_ANDROID # new + default: TOKEN + description: > + Token reference serialization profile. Optional; defaults to TOKEN. + FIGMA_SYNTAX_WEB | _IOS | _ANDROID emit the platform's Figma codeSyntax, + falling back to TOKEN output when the platform has no code syntax defined. +``` + +### Notes + +- Member naming uses `FIGMA_SYNTAX_` in `SCREAMING_CASE`, matching the + existing `TOKEN_FIGMA_EXTENSIONS` / `FIGMA_NAME` members and citing + Constitution VI Rule 3 (Figma-owned `codeSyntax` concept). +- `IOS` follows Figma's `iOS` platform key, upper-cased to fit the enum casing + convention; `WEB` and `ANDROID` match Figma's keys directly. +- The empty-code-syntax fall-back is a transformer behaviour, kept out of the + schema to honour Constitution II. + +--- + +## Type ↔ Schema Impact + +- **Symmetric**: Yes — identical three members added to the `format.tokens` union + in `Config.ts` (two interfaces) and to the `tokens.enum` array in + `component.schema.json`. +- **Parity check**: `Config.format.tokens` ↔ `#/definitions/Config/properties/format/properties/tokens/enum`. + +--- + +## Downstream Impact + +| Consumer | Impact | Action required | +|----------|--------|-----------------| +| `specs-schema` | Enum widened; additive `MINOR`-class change | Fold into the in-progress unreleased `0.21.0`, update CHANGELOG | +| `specs-from-figma` | Must read Figma `codeSyntax` per `CodeSyntaxPlatform` and project tokens for the three new profiles, with fall-back to `TOKEN` output | Implement the new projections; recompile against new types | +| `specs-cli` | Recompile; surface the new values as selectable token formats | Update config validation/help to accept the new enum values | +| `specs-plugin-2` | Recompile; expose the three platform profiles in plugin UI to reach Specs Classic `CODE_SYNTAX` parity | Add UI selection mapping to the new enum values | + +--- + +## Semver Decision + +**Version**: lands in the in-progress unreleased `0.21.0` (released baseline +`0.20.0 → 0.21.0`, `MINOR`). No separate bump — `package.json` is already at +`0.21.0` and this change folds into that release's `Unreleased` CHANGELOG scaffold. + +**Justification**: Purely additive — three new optional enum members on an +existing optional field; no value removed or renamed and the default is +unchanged. Per Constitution Versioning, additive type/schema changes are `MINOR`; +the 0.21.0 development cycle already carries that minor bump, so this ADR +contributes to it rather than opening a new version. + +--- + +## Consequences + +- Consumers can select platform-specific code-syntax token serialization + (`WEB`, `iOS`, `ANDROID`), bringing Specs 2 to parity with Specs Classic's + `CODE_SYNTAX` feature. +- Tokens without a defined code syntax for the chosen platform degrade gracefully + to the existing `TOKEN` output, so the profiles are always safe to select. +- Tools validating against `component.schema.json` must adopt `0.21.0` to accept + the new values. +- The transformer (`specs-from-figma`) becomes the owner of the documented + fall-back semantics; this ADR does not add logic to `specs-schema`. diff --git a/adr/INDEX.md b/adr/INDEX.md index 761e29ab..24d50416 100644 --- a/adr/INDEX.md +++ b/adr/INDEX.md @@ -27,6 +27,7 @@ | # | Title | Highlights | |---|-------|------------| +| 051 | Platform Code-Syntax Token Profiles | Add `FIGMA_SYNTAX_WEB`/`_IOS`/`_ANDROID` to `Config.format.tokens`, emitting per-platform Figma code syntax with fallback to `TOKEN` | | 043 | Custom Color Format Configuration | Add `Config.format.color` with 9-format enum (HEX default); rename `ColorValue` → `ColorObject`; widen color types with `string` arm | | 041 | Layout Positioning — Constraint-Based Naming | Replace `x`/`y`/`layoutPositioning` with constraint-based `position`, `start`, `end`, `top`, `bottom`, center offsets | | 040 | Replace `primaryAxisAlignItems` and `counterAxisAlignItems` with `mainAxisAlignment` and `crossAxisAlignment` | Rename to platform-neutral names; add `MainAxisAlignment` and `CrossAxisAlignment` enums; not token-bindable | diff --git a/package-lock.json b/package-lock.json index 0c364eed..234bf115 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,42 +10,28 @@ "packages/cli" ], "dependencies": { - "@directededges/specs-from-figma": "file:../specs-from-figma" + "@directededges/specs-from-figma": "^0.19.0" }, "devDependencies": { "typescript": "^5.3.3", "vitest": "^3.1.1" } }, - "../specs-from-figma": { - "name": "@directededges/specs-from-figma", + "node_modules/@directededges/specs-cli": { + "resolved": "packages/cli", + "link": true + }, + "node_modules/@directededges/specs-from-figma": { "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@directededges/specs-from-figma/-/specs-from-figma-0.19.0.tgz", + "integrity": "sha512-p+vHuBkUPdLAaSLi3T8WmEETs6Kt9cxI5BeJwxUkZqDDX/yJ5MDoSZpNCCigKf4mXIkoop8zOYLLf5aPOwZavw==", "license": "PolyForm-Internal-Use-1.0.0", "dependencies": { - "@directededges/specs-schema": "file:../specs/packages/schema", + "@directededges/specs-schema": "^0.21.0", "fs-extra": "^11.3.3", "yaml": "^2.8.0" - }, - "devDependencies": { - "@figma/plugin-typings": "^1.123.0", - "@figma/rest-api-spec": "^0.36.0", - "@types/fs-extra": "^11.0.4", - "@types/node": "^20.0.0", - "dts-bundle-generator": "^9.5.1", - "esbuild": "^0.25.5", - "javascript-obfuscator": "^5.3.0", - "typescript": "^5.3.2", - "vitest": "^4.0.17" } }, - "node_modules/@directededges/specs-cli": { - "resolved": "packages/cli", - "link": true - }, - "node_modules/@directededges/specs-from-figma": { - "resolved": "../specs-from-figma", - "link": true - }, "node_modules/@directededges/specs-schema": { "resolved": "packages/schema", "link": true @@ -120,8 +106,6 @@ }, "node_modules/@esbuild/darwin-arm64": { "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "cpu": [ "arm64" ], @@ -494,8 +478,6 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, @@ -529,8 +511,6 @@ }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", - "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", "cpu": [ "arm64" ], @@ -851,8 +831,6 @@ }, "node_modules/@types/chai": { "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { @@ -862,22 +840,16 @@ }, "node_modules/@types/deep-eql": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, "node_modules/@types/fs-extra": { "version": "11.0.4", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", - "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -887,8 +859,6 @@ }, "node_modules/@types/jsonfile": { "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", - "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", "dev": true, "license": "MIT", "dependencies": { @@ -897,8 +867,6 @@ }, "node_modules/@types/node": { "version": "20.19.39", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", - "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", "dev": true, "license": "MIT", "dependencies": { @@ -907,8 +875,6 @@ }, "node_modules/@vitest/expect": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", "dev": true, "license": "MIT", "dependencies": { @@ -924,8 +890,6 @@ }, "node_modules/@vitest/mocker": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", "dev": true, "license": "MIT", "dependencies": { @@ -951,8 +915,6 @@ }, "node_modules/@vitest/pretty-format": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", "dev": true, "license": "MIT", "dependencies": { @@ -964,8 +926,6 @@ }, "node_modules/@vitest/runner": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -979,8 +939,6 @@ }, "node_modules/@vitest/snapshot": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, "license": "MIT", "dependencies": { @@ -994,8 +952,6 @@ }, "node_modules/@vitest/spy": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", "dev": true, "license": "MIT", "dependencies": { @@ -1007,8 +963,6 @@ }, "node_modules/@vitest/utils": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", "dev": true, "license": "MIT", "dependencies": { @@ -1022,8 +976,6 @@ }, "node_modules/assertion-error": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", "engines": { @@ -1032,8 +984,6 @@ }, "node_modules/cac": { "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, "license": "MIT", "engines": { @@ -1042,8 +992,6 @@ }, "node_modules/chai": { "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", "dev": true, "license": "MIT", "dependencies": { @@ -1059,8 +1007,6 @@ }, "node_modules/check-error": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", - "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", "dev": true, "license": "MIT", "engines": { @@ -1069,8 +1015,6 @@ }, "node_modules/commander": { "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "license": "MIT", "engines": { "node": ">=16" @@ -1078,8 +1022,6 @@ }, "node_modules/debug": { "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -1096,8 +1038,6 @@ }, "node_modules/deep-eql": { "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, "license": "MIT", "engines": { @@ -1106,15 +1046,11 @@ }, "node_modules/es-module-lexer": { "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", "dev": true, "license": "MIT" }, "node_modules/esbuild": { "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1155,8 +1091,6 @@ }, "node_modules/estree-walker": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", "dependencies": { @@ -1165,8 +1099,6 @@ }, "node_modules/expect-type": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1175,8 +1107,6 @@ }, "node_modules/fdir": { "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { @@ -1193,8 +1123,6 @@ }, "node_modules/fs-extra": { "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -1207,10 +1135,7 @@ }, "node_modules/fsevents": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ @@ -1222,21 +1147,15 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, "node_modules/js-tokens": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", "dev": true, "license": "MIT" }, "node_modules/jsonfile": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -1247,15 +1166,11 @@ }, "node_modules/loupe": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", "dev": true, "license": "MIT" }, "node_modules/magic-string": { "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1264,15 +1179,11 @@ }, "node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -1290,15 +1201,11 @@ }, "node_modules/pathe": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, "node_modules/pathval": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, "license": "MIT", "engines": { @@ -1307,15 +1214,11 @@ }, "node_modules/picocolors": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -1327,8 +1230,6 @@ }, "node_modules/postcss": { "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "dev": true, "funding": [ { @@ -1356,8 +1257,6 @@ }, "node_modules/rollup": { "version": "4.60.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", - "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", "dev": true, "license": "MIT", "dependencies": { @@ -1401,15 +1300,11 @@ }, "node_modules/siginfo": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, "license": "ISC" }, "node_modules/source-map-js": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -1418,22 +1313,16 @@ }, "node_modules/stackback": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, "license": "MIT" }, "node_modules/std-env": { "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "dev": true, "license": "MIT" }, "node_modules/strip-literal": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", - "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", "dev": true, "license": "MIT", "dependencies": { @@ -1445,22 +1334,16 @@ }, "node_modules/tinybench": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true, "license": "MIT" }, "node_modules/tinyexec": { "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", "dev": true, "license": "MIT" }, "node_modules/tinyglobby": { "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1476,8 +1359,6 @@ }, "node_modules/tinypool": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", "dev": true, "license": "MIT", "engines": { @@ -1486,8 +1367,6 @@ }, "node_modules/tinyrainbow": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, "license": "MIT", "engines": { @@ -1496,8 +1375,6 @@ }, "node_modules/tinyspy": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", - "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", "dev": true, "license": "MIT", "engines": { @@ -1506,14 +1383,10 @@ }, "node_modules/tslib": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, "node_modules/typescript": { "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1526,15 +1399,11 @@ }, "node_modules/undici-types": { "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" }, "node_modules/universalify": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "license": "MIT", "engines": { "node": ">= 10.0.0" @@ -1542,8 +1411,6 @@ }, "node_modules/vite": { "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", "dependencies": { @@ -1617,8 +1484,6 @@ }, "node_modules/vite-node": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", "dev": true, "license": "MIT", "dependencies": { @@ -1640,8 +1505,6 @@ }, "node_modules/vitest": { "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", "dependencies": { @@ -1713,8 +1576,6 @@ }, "node_modules/why-is-node-running": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", "dependencies": { @@ -1730,8 +1591,6 @@ }, "node_modules/yaml": { "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -1745,11 +1604,11 @@ }, "packages/cli": { "name": "@directededges/specs-cli", - "version": "0.15.1", + "version": "0.16.0", "license": "MIT", "dependencies": { - "@directededges/specs-from-figma": "^0.18.0", - "@directededges/specs-schema": "^0.20.0", + "@directededges/specs-from-figma": "^0.19.0", + "@directededges/specs-schema": "^0.21.0", "commander": "^11.1.0", "fs-extra": "^11.2.0", "tslib": "^2.6.2", @@ -1766,23 +1625,6 @@ "vitest": "^3.0.0" } }, - "packages/cli/node_modules/@directededges/specs-from-figma": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@directededges/specs-from-figma/-/specs-from-figma-0.18.0.tgz", - "integrity": "sha512-MHme66c2TZBMiAD8aetyaeDlzNNQYo7OUqZgjWAjyIw7t1/vmPBFvI+c9D6A7G3AiQHfRtxEAKWadSb0+KYlyg==", - "license": "PolyForm-Internal-Use-1.0.0", - "dependencies": { - "@directededges/specs-schema": "^0.20.0", - "fs-extra": "^11.3.3", - "yaml": "^2.8.0" - } - }, - "packages/cli/node_modules/@directededges/specs-schema": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@directededges/specs-schema/-/specs-schema-0.20.0.tgz", - "integrity": "sha512-6H3MZ7EEpMDOWprApTtZYodoooE40W42DOP1q1aNBpFKg35aBPykvFKAUcmK+k1iFxnmhP5Yx08HCtMM2rF5+Q==", - "license": "CC-BY-4.0" - }, "packages/cli/node_modules/@esbuild/aix-ppc64": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", @@ -2159,8 +2001,6 @@ }, "packages/cli/node_modules/esbuild": { "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2198,8 +2038,6 @@ }, "packages/cli/node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", "cpu": [ "arm64" ], diff --git a/package.json b/package.json index 86dc1a22..7d800f53 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,6 @@ "vitest": "^3.1.1" }, "dependencies": { - "@directededges/specs-from-figma": "file:../specs-from-figma" + "@directededges/specs-from-figma": "^0.19.0" } } diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index dda1e4db..6deb17d2 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -5,6 +5,20 @@ All notable changes to `@directededges/specs-cli` are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.16.0] - 2026-05-22 + +Adds the platform code-syntax token profiles (`FIGMA_SYNTAX_WEB/IOS/ANDROID`) to config loading and templates, so specs can emit each Figma variable's per-platform code syntax. Picks up upstream transformer improvements: conditional-visibility boolean prop exposure, detection of subcomponents nested inside sections/frames, and a fix for SLOT properties that previously leaked raw GUID objects into output. + +### Added + +- **Platform code-syntax token profiles (ADR-051, DirectedEdges/specs#103)** — `format.tokens` now accepts `FIGMA_SYNTAX_WEB`, `FIGMA_SYNTAX_IOS`, and `FIGMA_SYNTAX_ANDROID` in config loading and templates, surfacing the platform code-syntax profiles to CLI users. The transformer (specs-from-figma) emits each variable's Figma `codeSyntax` for the selected platform, falling back to the standard token output when a platform has no code syntax defined. + +### Dependency updates + +- **`@directededges/specs-schema` ^0.20.0 → ^0.21.0** — adds the `FIGMA_SYNTAX_WEB/IOS/ANDROID` `format.tokens` profiles. +- **`@directededges/specs-from-figma` ^0.18.0 → ^0.19.0** — serializes the new platform code-syntax profiles from Figma `codeSyntax`; specs now expose a boolean-prop reference for conditional visibility bindings; subcomponents nested inside sections/frames are now detected under `subcomponents.scope: PAGE`; SLOT properties no longer emit raw GUID objects into output. + + ## [0.15.1] - 2026-05-20 Patch release fixing the `scan` → `generate` round-trip. `generate` now accepts the v2 (markdown-table) manifests that `scan` has emitted since 0.15.0, restoring the documented workflow. diff --git a/packages/cli/package.json b/packages/cli/package.json index 51005962..61c6c515 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@directededges/specs-cli", - "version": "0.15.1", + "version": "0.16.0", "description": "Command-line interface for Specs design system operations", "type": "module", "main": "./dist/index.js", @@ -20,8 +20,8 @@ "test": "cd ../.. && vitest -c vitest.config.ts packages/cli/tests" }, "dependencies": { - "@directededges/specs-schema": "^0.20.0", - "@directededges/specs-from-figma": "^0.18.0", + "@directededges/specs-schema": "^0.21.0", + "@directededges/specs-from-figma": "^0.19.0", "commander": "^11.1.0", "fs-extra": "^11.2.0", "yaml": "^2.3.4", diff --git a/packages/cli/src/Config/ConfigLoader.ts b/packages/cli/src/Config/ConfigLoader.ts index 726f8a89..96b82fac 100644 --- a/packages/cli/src/Config/ConfigLoader.ts +++ b/packages/cli/src/Config/ConfigLoader.ts @@ -193,7 +193,7 @@ export class ConfigLoader { const validKeys = ['SAFE', 'CAMEL', 'SNAKE', 'KEBAB', 'PASCAL', 'TRAIN']; const validOutputs = ['JSON', 'YAML']; const validLayouts = ['LAYOUT', 'PARENT_CHILDREN', 'BOTH']; - const validTokens = ['TOKEN', 'TOKEN_NAME', 'TOKEN_FIGMA_EXTENSIONS', 'FIGMA_NAME', 'CUSTOM']; + const validTokens = ['TOKEN', 'TOKEN_NAME', 'TOKEN_FIGMA_EXTENSIONS', 'FIGMA_NAME', 'CUSTOM', 'FIGMA_SYNTAX_WEB', 'FIGMA_SYNTAX_IOS', 'FIGMA_SYNTAX_ANDROID']; const validColors = ['HEX', 'HEXA', 'RGB', 'RGBA', 'HSLA', 'HSB', 'OKLCH', 'OKLAB', 'OBJECT']; // Normalize format values to uppercase before validation diff --git a/packages/cli/src/Config/ConfigTemplates.ts b/packages/cli/src/Config/ConfigTemplates.ts index 27bb3fd3..f315dcb3 100644 --- a/packages/cli/src/Config/ConfigTemplates.ts +++ b/packages/cli/src/Config/ConfigTemplates.ts @@ -66,7 +66,8 @@ config: # See: https://directededges.github.io/specs/config/layout/ layout: LAYOUT - # Token reference format: TOKEN, TOKEN_NAME, TOKEN_FIGMA_EXTENSIONS, FIGMA_NAME, or CUSTOM + # Token reference format: TOKEN, TOKEN_NAME, TOKEN_FIGMA_EXTENSIONS, FIGMA_NAME, CUSTOM, + # FIGMA_SYNTAX_WEB, FIGMA_SYNTAX_IOS, or FIGMA_SYNTAX_ANDROID # See: https://directededges.github.io/specs/config/tokens/ # Requires a license key to resolve token references in output. tokens: TOKEN diff --git a/packages/cli/tests/unit/config/ConfigLoader.test.ts b/packages/cli/tests/unit/config/ConfigLoader.test.ts index f14bf813..812f96e4 100644 --- a/packages/cli/tests/unit/config/ConfigLoader.test.ts +++ b/packages/cli/tests/unit/config/ConfigLoader.test.ts @@ -216,6 +216,26 @@ sources: expect(config.config.format.tokens).toBe('TOKEN'); // Default }); + it('should accept all valid format.tokens values', () => { + const validValues = ['TOKEN', 'TOKEN_NAME', 'TOKEN_FIGMA_EXTENSIONS', 'FIGMA_NAME', 'CUSTOM', 'FIGMA_SYNTAX_WEB', 'FIGMA_SYNTAX_IOS', 'FIGMA_SYNTAX_ANDROID']; + + validValues.forEach(value => { + const configPath = path.join(testDir, 'specs.config.yaml'); + fs.writeFileSync(configPath, `config:\n format:\n tokens: ${value}`); + + const config = configLoader.load(); + expect(config.config.format.tokens).toBe(value); + }); + }); + + it('should normalize lowercase format.tokens to uppercase', () => { + const configPath = path.join(testDir, 'specs.config.yaml'); + fs.writeFileSync(configPath, 'config:\n format:\n tokens: figma_syntax_ios'); + + const config = configLoader.load(); + expect(config.config.format.tokens).toBe('FIGMA_SYNTAX_IOS'); + }); + it('should validate format.color and use default for invalid values', () => { const configPath = path.join(testDir, 'specs.config.yaml'); fs.writeFileSync(configPath, 'config:\n format:\n color: INVALID'); diff --git a/packages/schema/CHANGELOG.md b/packages/schema/CHANGELOG.md index be3dfb0f..f0857016 100644 --- a/packages/schema/CHANGELOG.md +++ b/packages/schema/CHANGELOG.md @@ -5,13 +5,13 @@ All notable changes to the Specs schema will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.21.0] - Unreleased +## [0.21.0] - 2026-05-22 -### Added +Extends `Config.format.tokens` with per-platform Figma code syntax profiles. The new `FIGMA_SYNTAX_WEB`, `FIGMA_SYNTAX_IOS`, and `FIGMA_SYNTAX_ANDROID` options emit the platform-specific code syntax authored on Figma variables, falling back to the resolved `TOKEN` reference when no syntax is defined. -### Changed +### Added -### Removed +- `Config.format.tokens` — adds `FIGMA_SYNTAX_WEB`, `FIGMA_SYNTAX_IOS`, `FIGMA_SYNTAX_ANDROID` profiles emitting per-platform Figma code syntax, falling back to `TOKEN` ## [0.20.0] - 2026-05-06 diff --git a/packages/schema/schema/component.schema.json b/packages/schema/schema/component.schema.json index 5224f411..0460301d 100644 --- a/packages/schema/schema/component.schema.json +++ b/packages/schema/schema/component.schema.json @@ -857,10 +857,13 @@ "TOKEN_NAME", "TOKEN_FIGMA_EXTENSIONS", "FIGMA_NAME", - "CUSTOM" + "CUSTOM", + "FIGMA_SYNTAX_WEB", + "FIGMA_SYNTAX_IOS", + "FIGMA_SYNTAX_ANDROID" ], "default": "TOKEN", - "description": "Token reference serialization profile. Optional; defaults to TOKEN. CUSTOM delegates projection entirely to the transformer." + "description": "Token reference serialization profile. Optional; defaults to TOKEN. CUSTOM delegates projection entirely to the transformer. FIGMA_SYNTAX_WEB, FIGMA_SYNTAX_IOS, and FIGMA_SYNTAX_ANDROID emit the token's Figma codeSyntax for that platform, falling back to TOKEN output when no code syntax is defined for the platform." }, "color": { "type": "string", diff --git a/packages/schema/tests/Config.test-d.ts b/packages/schema/tests/Config.test-d.ts index 4fe34821..0b5220e8 100644 --- a/packages/schema/tests/Config.test-d.ts +++ b/packages/schema/tests/Config.test-d.ts @@ -151,6 +151,12 @@ const tokenNameConfig: Config = { ...fullConfig, format: { ...fullConfig.format, const tokenFigmaExtConfig: Config = { ...fullConfig, format: { ...fullConfig.format, tokens: 'TOKEN_FIGMA_EXTENSIONS' } }; const figmaNameConfig: Config = { ...fullConfig, format: { ...fullConfig.format, tokens: 'FIGMA_NAME' } }; const customConfig: Config = { ...fullConfig, format: { ...fullConfig.format, tokens: 'CUSTOM' } }; +const figmaSyntaxWebConfig: Config = { ...fullConfig, format: { ...fullConfig.format, tokens: 'FIGMA_SYNTAX_WEB' } }; +const figmaSyntaxIosConfig: Config = { ...fullConfig, format: { ...fullConfig.format, tokens: 'FIGMA_SYNTAX_IOS' } }; +const figmaSyntaxAndroidConfig: Config = { ...fullConfig, format: { ...fullConfig.format, tokens: 'FIGMA_SYNTAX_ANDROID' } }; + +// @ts-expect-error — invalid tokens profile value +const _badTokens: Config['format']['tokens'] = 'FIGMA_SYNTAX_DESKTOP'; // ─── DEFAULT_CONFIG is a valid ResolvedConfig ──────────────────────────────── @@ -197,7 +203,7 @@ const _kRequired: _KRequired = true; type _LRequired = ResolvedConfig['format']['layout'] extends ('LAYOUT' | 'PARENT_CHILDREN' | 'BOTH') ? true : never; const _lRequired: _LRequired = true; -type _TRequired = ResolvedConfig['format']['tokens'] extends ('TOKEN' | 'TOKEN_NAME' | 'TOKEN_FIGMA_EXTENSIONS' | 'FIGMA_NAME' | 'CUSTOM') ? true : never; +type _TRequired = ResolvedConfig['format']['tokens'] extends ('TOKEN' | 'TOKEN_NAME' | 'TOKEN_FIGMA_EXTENSIONS' | 'FIGMA_NAME' | 'CUSTOM' | 'FIGMA_SYNTAX_WEB' | 'FIGMA_SYNTAX_IOS' | 'FIGMA_SYNTAX_ANDROID') ? true : never; const _tRequired: _TRequired = true; // include diff --git a/packages/schema/types/Config.ts b/packages/schema/types/Config.ts index 3a291908..93ab0766 100644 --- a/packages/schema/types/Config.ts +++ b/packages/schema/types/Config.ts @@ -53,8 +53,13 @@ export interface Config { keys?: 'SAFE' | 'CAMEL' | 'SNAKE' | 'KEBAB' | 'PASCAL' | 'TRAIN'; /** Layout representation format. Optional; defaults to LAYOUT. */ layout?: 'LAYOUT' | 'PARENT_CHILDREN' | 'BOTH'; - /** Token reference serialization profile. Optional; defaults to TOKEN. */ - tokens?: 'TOKEN' | 'TOKEN_NAME' | 'TOKEN_FIGMA_EXTENSIONS' | 'FIGMA_NAME' | 'CUSTOM'; + /** + * Token reference serialization profile. Optional; defaults to TOKEN. + * `FIGMA_SYNTAX_WEB`, `FIGMA_SYNTAX_IOS`, and `FIGMA_SYNTAX_ANDROID` emit the + * token's Figma `codeSyntax` for that platform, falling back to the `TOKEN` + * profile's output when no code syntax is defined for the platform. @since 0.21.0 + */ + tokens?: 'TOKEN' | 'TOKEN_NAME' | 'TOKEN_FIGMA_EXTENSIONS' | 'FIGMA_NAME' | 'CUSTOM' | 'FIGMA_SYNTAX_WEB' | 'FIGMA_SYNTAX_IOS' | 'FIGMA_SYNTAX_ANDROID'; /** Color value output format. Optional; defaults to HEX. @since 0.20.0 */ color?: ColorFormat; }; @@ -110,7 +115,7 @@ export interface ResolvedConfig { /** Layout representation format. */ layout: 'LAYOUT' | 'PARENT_CHILDREN' | 'BOTH'; /** Token reference serialization profile. */ - tokens: 'TOKEN' | 'TOKEN_NAME' | 'TOKEN_FIGMA_EXTENSIONS' | 'FIGMA_NAME' | 'CUSTOM'; + tokens: 'TOKEN' | 'TOKEN_NAME' | 'TOKEN_FIGMA_EXTENSIONS' | 'FIGMA_NAME' | 'CUSTOM' | 'FIGMA_SYNTAX_WEB' | 'FIGMA_SYNTAX_IOS' | 'FIGMA_SYNTAX_ANDROID'; /** Color value output format. */ color: ColorFormat; }; diff --git a/site/src/content/docs/config/tokens.md b/site/src/content/docs/config/tokens.md index c783b918..0a0f8d3b 100644 --- a/site/src/content/docs/config/tokens.md +++ b/site/src/content/docs/config/tokens.md @@ -16,6 +16,9 @@ Token reference format profile. - `TOKEN_FIGMA_EXTENSIONS` - Token with Figma-specific extension data - `FIGMA_NAME` - Raw Figma variable/style names as-is - `CUSTOM` - Custom token objects injected via `applyCustomTokens`. Variables/styles with `$custom` use that object verbatim as the property value; those without fall back to `TOKEN_FIGMA_EXTENSIONS` format. + - `FIGMA_SYNTAX_WEB` - The token's Figma code syntax for the Web platform. Tokens without a Web code syntax fall back to `TOKEN` output. + - `FIGMA_SYNTAX_IOS` - The token's Figma code syntax for the iOS platform. Tokens without an iOS code syntax fall back to `TOKEN` output. + - `FIGMA_SYNTAX_ANDROID` - The token's Figma code syntax for the Android platform. Tokens without an Android code syntax fall back to `TOKEN` output. > **Using CUSTOM**: First run `specs applyCustomTokens ` to inject `$custom` objects into your fetched data files, then run `batch` or `generate`. The `applyCustomTokens` command auto-discovers variables/styles files from `dataDirectory` and `sources` in this config, or accepts explicit `-v`/`-s` paths. See [applyCustomTokens command](/specs/cli/commands/apply-custom-tokens/) for details. diff --git a/site/src/content/docs/guides/token-format.md b/site/src/content/docs/guides/token-format.md index f64ff75b..fcb85874 100644 --- a/site/src/content/docs/guides/token-format.md +++ b/site/src/content/docs/guides/token-format.md @@ -90,6 +90,18 @@ borderColor: collectionName: DS Color ``` +#### `FIGMA_SYNTAX_WEB` / `FIGMA_SYNTAX_IOS` / `FIGMA_SYNTAX_ANDROID` + +The developer-facing token name a designer assigned for a specific platform via Figma's [code syntax](https://developers.figma.com/docs/plugins/api/CodeSyntaxPlatform/) — `WEB`, `iOS`, or `ANDROID`. Each profile emits that platform's code syntax string: + +```yaml +backgroundColor: --ds-color-text-primary # FIGMA_SYNTAX_WEB +backgroundColor: DSColor.textPrimary # FIGMA_SYNTAX_IOS +backgroundColor: R.color.ds_text_primary # FIGMA_SYNTAX_ANDROID +``` + +When a token has no code syntax defined for the chosen platform, the profile **falls back to the `TOKEN` output** for that reference, so these profiles are always safe to select. Use them to match the token naming your platform code already expects. + ### Profile Comparison | Profile | Output shape | Includes type | Includes Figma IDs | Custom mapping | @@ -99,6 +111,9 @@ borderColor: | `TOKEN_FIGMA_EXTENSIONS` | `{ $token, $type, $extensions }` | Yes | Yes | No | | `FIGMA_NAME` | `"Collection/Path"` | No | No | No | | `CUSTOM` | User-defined or fallback | Varies | Fallback only | Yes | +| `FIGMA_SYNTAX_WEB` | Platform code syntax string, or `TOKEN` fallback | Fallback only | No | No | +| `FIGMA_SYNTAX_IOS` | Platform code syntax string, or `TOKEN` fallback | Fallback only | No | No | +| `FIGMA_SYNTAX_ANDROID` | Platform code syntax string, or `TOKEN` fallback | Fallback only | No | No | ## When to Use Each Profile @@ -107,6 +122,7 @@ borderColor: - **`TOKEN_FIGMA_EXTENSIONS`** — Use when consumers need to trace tokens back to their Figma source — variable IDs, raw resolved values, collection names. Useful for debugging, Figma plugin integrations, or migration tooling. - **`FIGMA_NAME`** — Use when consumers expect Figma-native naming with slash delimiters. Good for teams whose token systems mirror the Figma variable structure directly. - **`CUSTOM`** — Use when your team has a bespoke token format (e.g., Style Dictionary references, custom JSON shapes). Requires running `specs applyCustomTokens` first to inject `$custom` objects into your fetched data files. +- **`FIGMA_SYNTAX_WEB` / `FIGMA_SYNTAX_IOS` / `FIGMA_SYNTAX_ANDROID`** — Use when consumers want the platform-specific token name designers set in Figma's code syntax for Web, iOS, or Android. Tokens lacking code syntax for the chosen platform fall back to `TOKEN` output. ## Configuration diff --git a/site/src/content/docs/schema/config.md b/site/src/content/docs/schema/config.md index 7bcc14f4..ee4caf1d 100644 --- a/site/src/content/docs/schema/config.md +++ b/site/src/content/docs/schema/config.md @@ -24,7 +24,7 @@ Controls how specs are generated. See the [feature guides](/specs/features/) for | `output` | `'JSON' \| 'YAML'` | `'JSON'` | Output file format | | `keys` | `'SAFE' \| 'CAMEL' \| 'SNAKE' \| 'KEBAB' \| 'PASCAL' \| 'TRAIN'` | `'SAFE'` | Key casing style | | `layout` | `'LAYOUT' \| 'PARENT_CHILDREN' \| 'BOTH'` | `'LAYOUT'` | Element hierarchy representation | -| `tokens` | `'TOKEN' \| 'TOKEN_NAME' \| 'TOKEN_FIGMA_EXTENSIONS' \| 'FIGMA_NAME' \| 'CUSTOM'` | `'TOKEN'` | Token reference output format | +| `tokens` | `'TOKEN' \| 'TOKEN_NAME' \| 'TOKEN_FIGMA_EXTENSIONS' \| 'FIGMA_NAME' \| 'CUSTOM' \| 'FIGMA_SYNTAX_WEB' \| 'FIGMA_SYNTAX_IOS' \| 'FIGMA_SYNTAX_ANDROID'` | `'TOKEN'` | Token reference output format — `FIGMA_SYNTAX_*` emit per-platform Figma code syntax, falling back to `TOKEN` | | `color` | `ColorFormat` | `'HEX'` | Color value output format — `HEX`, `HEXA`, `RGB`, `RGBA`, `HSLA`, `HSB`, `OKLCH`, `OKLAB`, or `OBJECT` | ## `include`