@halftone-ds/tokens 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/theme-cli.mjs CHANGED
@@ -126,17 +126,20 @@ function readRecipe(arg, root) {
126
126
  fail(`"${arg}" is not a theme code, a preset (${PRESETS.map((p) => p.id).join(', ')}), a recipe file or a shadcn preset code`, 2);
127
127
  }
128
128
 
129
- const describe = (input) => {
129
+ /** A recipe in a few lines. `families`: a system's own fonts (any Google Fonts family), which win over the recipe's. */
130
+ const describe = (input, families = {}) => {
130
131
  // A recipe recorded by an older halftone-tokens has no typeset or styles: it gets the defaults.
131
132
  const recipe = normalizeRecipe(input);
132
- const font = (id) => {
133
+ const font = (role) => {
134
+ if (families[role]) return families[role];
135
+ const id = recipe.font[role];
133
136
  const f = fontById(id);
134
137
  return f?.system ? (f.category === 'mono' ? 'system mono' : 'system') : (f?.family ?? id);
135
138
  };
136
139
  return [
137
140
  `brand ${recipe.brand} · neutral ${recipe.neutral} · radius ${recipe.radius} · controls ${recipe.controls}`,
138
- `type ${font(recipe.font.heading)} / ${font(recipe.font.body)} · stroke ${recipe.stroke} · depth ${recipe.depth} · density ${recipe.density} · border ${recipe.border}`,
139
- `typeset ${recipe.size}px · leading ${recipe.leading} · flow ${recipe.flow} · measure ${recipe.measure}ch · mono ${font(recipe.font.mono)}`,
141
+ `type ${font('heading')} / ${font('body')} · stroke ${recipe.stroke} · depth ${recipe.depth} · density ${recipe.density} · border ${recipe.border}`,
142
+ `typeset ${recipe.size}px · leading ${recipe.leading} · flow ${recipe.flow} · measure ${recipe.measure}ch · mono ${font('mono')}`,
140
143
  `styles: ${recipe.button} buttons · ${recipe.field} fields`,
141
144
  ].join('\n ');
142
145
  };
@@ -167,6 +170,18 @@ function installFonts(root, config, theme, flags) {
167
170
  if (unused.length) console.log(`\nNo longer used by the theme (remove when you like): npm uninstall ${unused.join(' ')}`);
168
171
  }
169
172
 
173
+ /**
174
+ * A kit's registry:build writes which components draw which token (registry/generated/token-usage.json and the docs
175
+ * from it), and that follows the colour roles a system maps, so applying one rebuilds it: its --check stays green.
176
+ */
177
+ function rebuildRegistry(root) {
178
+ const file = join(root, 'package.json');
179
+ if (!existsSync(file) || !JSON.parse(readFileSync(file, 'utf8')).scripts?.['registry:build']) return;
180
+ console.log('\nregistry:build (token usage follows the colour roles)…');
181
+ const r = spawnSync('npm', ['run', '--silent', 'registry:build'], { cwd: root, stdio: 'inherit' });
182
+ if (r.status !== 0) console.log('registry:build failed: run `npm run registry:build` yourself.');
183
+ }
184
+
170
185
  export async function themeCommand({ positional, flags, root, sourcePath, config, build }) {
171
186
  const [sub = 'show', arg] = positional;
172
187
  const tokens = () => JSON.parse(readFileSync(sourcePath, 'utf8'));
@@ -253,7 +268,7 @@ export async function themeCommand({ positional, flags, root, sourcePath, config
253
268
  if (status.system) {
254
269
  const { system } = status;
255
270
  const overrides = describeOverrides(system);
256
- console.log(`System "${system.name}"${status.code ? ` ${status.code}` : ' (no theme code: it has overrides)'}\n ${describe(status.recipe)}`);
271
+ console.log(`System "${system.name}"${status.code ? ` ${status.code}` : ' (no theme code: it has overrides)'}\n ${describe(status.recipe, system.fonts)}`);
257
272
  if (overrides.length) console.log(`\nOverrides:\n${overrides.map((o) => ` ${o}`).join('\n')}`);
258
273
  printWarnings(tokens().$extensions?.[THEME_EXTENSION]?.warnings);
259
274
  } else {
@@ -282,7 +297,7 @@ export async function themeCommand({ positional, flags, root, sourcePath, config
282
297
  if (system) {
283
298
  const code = systemCode(system);
284
299
  const overrides = describeOverrides(system);
285
- console.log(`System "${system.name}"${code ? ` ${code}` : ''}\n ${describe(theme.recipe)}`);
300
+ console.log(`System "${system.name}"${code ? ` ${code}` : ''}\n ${describe(theme.recipe, system.fonts)}`);
286
301
  if (overrides.length) console.log(`\nOverrides (no theme code carries these; tokens.json records the document):\n${overrides.map((o) => ` ${o}`).join('\n')}`);
287
302
  } else console.log(`Theme ${theme.code}${recipe.preset ? ` (${presetById(recipe.preset)?.name})` : ''}\n ${describe(theme.recipe)}`);
288
303
  if (theme.adjustments.length) {
@@ -292,7 +307,9 @@ export async function themeCommand({ positional, flags, root, sourcePath, config
292
307
  printWarnings(theme.warnings);
293
308
  const cvd = cvdWarnings(theme.resolved);
294
309
  if (cvd.length) {
295
- console.log(`\nColour vision: ${cvd.length} pair(s) of tones closer than ΔE ${MIN_DELTA_E} (a warning; give them an icon or a label as well):`);
310
+ // Counted as the Studio's Accessibility tab counts them: pairs of tones, each listed in every view it's close in.
311
+ const pairs = new Set(cvd.map((w) => `${w.a}/${w.b}`)).size;
312
+ console.log(`\nColour vision: ${pairs} pair(s) of tones close together, in ${cvd.length} view(s) below ΔE ${MIN_DELTA_E} (a warning; give them an icon or a label as well):`);
296
313
  console.log(describeCvdWarnings(cvd).join('\n'));
297
314
  }
298
315
  if (flags['dry-run']) {
@@ -303,6 +320,7 @@ export async function themeCommand({ positional, flags, root, sourcePath, config
303
320
  console.log(`\nWrote ${sourcePath.slice(root.length + 1)}.`);
304
321
  installFonts(root, config, theme, flags);
305
322
  await build();
323
+ rebuildRegistry(root);
306
324
  return;
307
325
  }
308
326
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@halftone-ds/tokens",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Design tokens (W3C DTCG) to NativeWind, Tailwind 4 / shadcn, TypeScript, DESIGN.md and Figma, with a WCAG AA contrast gate. The token build behind Halftone.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -12,7 +12,8 @@ function themeInfo(source) {
12
12
  const status = themeStatus(source);
13
13
  if (!status.applied) return 'null';
14
14
  const preset = presetById(status.recipe.preset ?? '');
15
- const name = preset && sameRecipe(preset.recipe, status.recipe) ? preset.name : 'Custom';
15
+ // A system is named after itself; its code is null when it has overrides a code can't carry.
16
+ const name = status.system?.name ?? (preset && sameRecipe(preset.recipe, status.recipe) ? preset.name : 'Custom');
16
17
  return JSON.stringify({ code: status.code, name, edited: status.drift.length > 0, recipe: status.recipe }, null, 2);
17
18
  }
18
19
 
@@ -137,7 +138,9 @@ export const COMPONENT_VARS = ${JSON.stringify(componentVarNames())} as const;
137
138
  export const THEME_INFO: ThemeInfo | null = ${themeInfo(ctx.source)};
138
139
 
139
140
  export type ThemeInfo = {
140
- code: string;
141
+ /** null for a system (\`.halftone.json\`) with overrides no theme code carries. */
142
+ code: string | null;
143
+ /** The preset's name, the system's name, or 'Custom'. */
141
144
  name: string;
142
145
  edited: boolean;
143
146
  recipe: {