Skip to content

chore: generate TypeScript tokens in the Tokens Studio release workflow - #5166

Merged
pomfrida merged 2 commits into
mainfrom
chore/tokens-studio-ts-tokens
Jul 13, 2026
Merged

chore: generate TypeScript tokens in the Tokens Studio release workflow#5166
pomfrida merged 2 commits into
mainfrom
chore/tokens-studio-ts-tokens

Conversation

@pomfrida

Copy link
Copy Markdown
Collaborator

Closes #5164

The Tokens Studio platform has no TypeScript export format, so this adds a small dependency-free codegen script that combines the two exports the release workflow already produces:

  • DTCG export (new EDS-DTCG configuration, added as a workflow step) provides the token tree structure and $type metadata — its values are unresolved {alias} references and colour formulas
  • CSS export provides the evaluated concrete values; the script dereferences the var(--eds-*) chains per dimension context (colour scheme × density)

scripts/generate-ts-tokens.mjs then emits as const TypeScript modules to packages/eds-tokens/src/tokens/ts/, mirroring the export layout. Modules that resolve differently per colour scheme (currently semantic/) are automatically split into per-scheme files.

Values are converted to React Native friendly forms for equinor/design-system-mobile (the only known TS-token consumer, see #5164):

  • oklch() → hex via CSS Color 4 §13.2 gamut mapping (chroma bisection, not naive channel clipping) — verified identical to lightningcss for all 199 unique colours in the current export
  • px dimensions → unitless numbers

Also fixes the workflow referencing the CSS export by name: the configuration was renamed EDSEDS-CSS in Tokens Studio, which would have broken the next release run. Both exports are now referenced by ID, which is rename-proof.

Verified locally against real platform exports: 10 generated modules, tsc --noEmit --strict passes. Like the CSS output, the generated TS is not yet wired into the package exports map.

Note for the mobile migration: the new density dimension is comfortable/compact/relaxed — spacious (which mobile imports today) no longer exists.

@pomfrida pomfrida self-assigned this Jul 13, 2026
@pomfrida

Copy link
Copy Markdown
Collaborator Author

@claude review

Comment thread packages/eds-tokens/scripts/generate-ts-tokens.mjs Fixed
Comment thread packages/eds-tokens/scripts/generate-ts-tokens.mjs Fixed
@claude

claude Bot commented Jul 13, 2026

Copy link
Copy Markdown

Claude finished @pomfrida's task in 4m 20s —— View job


Code review — TypeScript token codegen

  • Read changed files (workflow, package.json, script)
  • Analyse codegen logic (colour conversion, dereferencing, per-scheme split)
  • Check workflow correctness
  • Post review

Nice piece of work — the dependency-free oklch→hex gamut mapping (CSS Color 4 §13.2 chroma bisection rather than naive channel clipping), the fail-loud philosophy throughout, the clean rm(OUT_DIR) regeneration, and referencing the exports by ID instead of name are all the right calls. A few things worth considering, roughly in order of importance.


1. toTsSource string serialisation is fragile (latent correctness)

packages/eds-tokens/scripts/generate-ts-tokens.mjs:284-292 builds the source by regex-rewriting JSON.stringify output, ending with a global .replace(/"/g, "'"). That's safe for hex colours and numbers, but it breaks for any string value that itself contains a ", ', or the general shape of a font stack. The realistic case is a fontFamily token (returned verbatim by convertValue, line 233):

value:  'Equinor', "Helvetica Neue", sans-serif
JSON:   "'Equinor', \"Helvetica Neue\", sans-serif"
output: ''Equinor', 'Helvetica Neue', sans-serif'   // invalid TS

It passes tsc --strict today only because the current export's font-family values happen to be simple. The moment a stack or a quoted family appears, the generated module is malformed and the release PR gets no CI to catch it (per the workflow comment). Safer to serialise each string value through JSON.stringify on its own (which escapes correctly) and only special-case the key rewriting, rather than a blanket quote swap over the whole document. Fix this →

2. Generated files won't pass the package's own prettier:check

The output lands in src/tokens/ts/ and package.json already exposes "prettier:check": "prettier --check src/". The repo's .prettierrc.yaml sets trailingComma: all, but JSON.stringify(value, null, 2) emits no trailing commas, and src/tokens/ts is not in any .prettierignore (root .prettierignore only covers packages/eds-tokens/build/*). So prettier --check src/ will report every generated module as unformatted. singleQuote, semi: false and tabWidth: 2 all match already — trailing commas are the only gap. Either run the output through Prettier at the end of the script, or add src/tokens/ts (and likely src/tokens/css) to a prettier-ignore. Fix this →


Minor notes (no action strictly required)

  • buildContext layout coupling (lines 85-101): the CSS part list and the two-level file.split('/') assumption (line 327) are hardwired to the current saved-export shape. Extra CSS files are silently ignored and a deeper nesting would be mis-parsed. Fine given the comment tying it to the saved config, but a new folder in the export would need a code change (and it'd only fail loudly for the listed files, not for silently-dropped new ones).
  • Density uses schemes[0] (line 351): resolving density files against an arbitrary scheme assumes density tokens never depend on colour scheme. True for spacing/sizing, but if a density token ever referenced a scheme-varying colour it would silently bake in schemes[0].
  • Parser strictness: oklchToHex/rgbToHex and the numeric regex (lines 171, 210, 229) accept only the exact current forms — no oklch(... / a), deg/% hue, rem, or scientific notation. That's a deliberate fail-loud, which is good; just noting the pipeline is pinned to today's CSS export syntax.
  • Cosmetic: oklch resolves to 6-digit hex while rgb() resolves to 8-digit #rrggbbaa (alpha emitted even when opaque). Consumers on RN handle both, so harmless — just inconsistent.

Overall this looks solid and well-reasoned; #1 is the one I'd address before this pipeline runs unattended, since a malformed module there would be silent.
chore/tokens-studio-ts-tokens

@pomfrida

Copy link
Copy Markdown
Collaborator Author

@claude review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generate TypeScript tokens in the Tokens Studio pipeline

1 participant