@motion-proto/live-tokens 0.79.0 → 0.81.0
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/.claude/skills/live-tokens-check-compliance/SKILL.md +12 -30
- package/CHANGELOG.md +50 -0
- package/README.md +4 -4
- package/bin/check-component.mjs +2 -0
- package/bin/check-page.mjs +2 -0
- package/bin/cli.mjs +38 -7
- package/bin/lib/buildChecks.mjs +32 -0
- package/bin/migrate-build-script.mjs +66 -0
- package/bin/migrate.mjs +5 -0
- package/bin/rules/tokens.mjs +64 -1
- package/dist-plugin/index.cjs +38 -4
- package/dist-plugin/index.d.cts +1 -0
- package/dist-plugin/index.d.ts +1 -0
- package/dist-plugin/index.js +79 -46
- package/package.json +1 -1
- package/src/editor/skill-atlas/SkillAtlas.svelte +42 -19
- package/src/editor/skill-atlas/atlasLink.ts +43 -0
- package/src/editor/skill-atlas/skillSources.generated.ts +1 -1
- package/src/editor/skill-atlas/trees/check-compliance.ts +25 -302
- package/src/editor/skill-atlas/trees/create-component.ts +4 -17
- package/src/system/components/CollapsibleSection.svelte +15 -15
- package/template/package.json +1 -2
- package/template/vite.config.ts +3 -2
|
@@ -1,32 +1,28 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: live-tokens-check-compliance
|
|
3
|
-
description: Check an existing @motion-proto/live-tokens project against the design system and fix it until check-page and check-component both exit 0. Checks for correct use of components, properties, and tokens.
|
|
3
|
+
description: Check an existing @motion-proto/live-tokens project against the design system and fix it until check-page and check-component both exit 0. Checks for correct use of components, properties, and tokens. Runs both checkers, which bring tokens.css up to the installed package, apply every auto repair, and return each remaining finding with its own guidance, repair level, and details. Use when the user asks to check, audit, or review the project. Use when the user asks to fix the project or to make the build's design checks pass. Edits the files the checkers name. Changes tokens.css only through its migrations.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Checking and fixing a project's adherence to live-tokens
|
|
7
7
|
|
|
8
8
|
Check the project, then fix every finding of `check-page` and `check-component` until both exit 0. `check-page` checks pages. Every component comes from the catalogue, every prop is declared, and every value in page CSS is a design token. `check-component` checks the project's own components. Every token names a semantic property, and its default is the design token that property reads. Every finding carries its own `guidance`, and the repair follows it.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Both checkers are static. They read the project's source, never open a browser, and never mount a runtime. `npx live-tokens check-page <file> --tests` proves a page's rendered paint, and `npx live-tokens check-component <id> --tests` proves a component's declared behavior. **live-tokens-create-page** and **live-tokens-create-component** run those, one file or one id at a time, and their findings carry guidance the same way.
|
|
11
11
|
|
|
12
12
|
## Workflow
|
|
13
13
|
|
|
14
|
-
1. Run
|
|
15
|
-
2. Run `npx live-tokens migrate --check` to see the plan, then `npx live-tokens migrate` to apply it. The command brings `tokens.css` up to the installed package and heals the data tree. With `--write` it also rewrites the route references it lists. `--tokens <path>` names a tokens.css in an unusual place.
|
|
16
|
-
3. Run both checkers with `--json`. Each first applies every finding whose `repair` is `auto`, checks again, and returns the fixes it applied in `fix.applied` beside the findings that remain. A fix in `fix.skipped` found its text moved, and the next run applies it. `--no-fix` reports without editing, for a build or CI.
|
|
14
|
+
1. Run both checkers with `--json`. Each first brings `tokens.css` up to the installed package by applying every additive migration, then applies every finding whose `repair` is `auto`, checks again, and returns the fixes it applied in `fix.applied` beside the findings that remain. A pending breaking migration returns as a `tokens-breaking-migration` finding, since it renames design tokens the project may read. A fix in `fix.skipped` found its text moved, and the next run applies it. `--no-fix` reports without editing, for a build or CI.
|
|
17
15
|
```sh
|
|
18
16
|
npx live-tokens check-page --json
|
|
19
17
|
npx live-tokens check-component --json
|
|
20
18
|
```
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
11. Reply with:
|
|
29
|
-
- the report's picture: pending migrations, component facts, and usage
|
|
19
|
+
2. Read what remains. Each finding carries the fields under Finding fields, with a `repair` of `choice` or `authored`.
|
|
20
|
+
3. Group the findings by rule. Take the largest error group first, then the remaining errors. Take warnings only when the repair scope includes warnings.
|
|
21
|
+
4. Make every repair in the group from its `guidance`, within Scope below.
|
|
22
|
+
5. Run both checkers again. When repairable findings remain in scope, return to step 3. When no token fits a remaining finding, leave it and continue to the reply with its reason.
|
|
23
|
+
6. When the errors are clear, run both checkers with `--strict`. Report what `--strict` adds. Clear warnings within the existing request. Otherwise ask whether to clear the warnings now.
|
|
24
|
+
7. When the repair scope includes warnings, return to step 3 with `--strict`. When strict checks pass or the user defers warnings, continue to the reply.
|
|
25
|
+
8. Reply with:
|
|
30
26
|
- the fixes the checkers applied, each with its count and any visible shift
|
|
31
27
|
- the remaining changes by rule, each with its count and any visible shift
|
|
32
28
|
- the findings left, each with its reason and any config entry the user chose
|
|
@@ -34,20 +30,9 @@ The report and both checkers are static. They read the project's source, never o
|
|
|
34
30
|
|
|
35
31
|
`check-page <path>` scopes a run to one page. `check-component <id>` scopes a run to one component: its runtime, its editor, and its registration. The checkers read tokens.css from its default location.
|
|
36
32
|
|
|
37
|
-
## Report sections
|
|
38
|
-
|
|
39
|
-
| Section | Contents |
|
|
40
|
-
| --- | --- |
|
|
41
|
-
| Pending token migrations (`migrations`) | Whether `tokens.css` is behind the installed package. `status` is `pending`, `none pending`, `no tokens.css`, or `unavailable`. A stale file shows downstream as unknown tokens, so step 2 runs the migrations before either checker. |
|
|
42
|
-
| Component facts (`components[]`) | Every component the project has, each with `id`, `origin`, `file`, `registered`, and `tokens` (the count of semantic properties it declares). A component's unread tokens, missing description, and missing registration are the `unread-token`, `missing-description`, and `missing-registration` findings. |
|
|
43
|
-
| Checker findings by rule (`findings.pages`, `findings.components`) | Both checkers' findings by rule under the project's severities, and one strict total with every warning as an error. Errors fail the build today. The strict total is what a fully tokenized project would fail. |
|
|
44
|
-
| Components each page renders (`usage.byPage`) | Which component each page imports, and how many times the page renders it. When a page renders none, say whether it is chrome or markup that a shipped component covers. |
|
|
45
|
-
| Shipped components no page renders (`usage.unusedShipped`) | Information only. |
|
|
46
|
-
| Project components unused (`usage.customUnused`) | The project's own components no page renders. |
|
|
47
|
-
|
|
48
33
|
## Finding fields
|
|
49
34
|
|
|
50
|
-
Every finding from both checkers, under
|
|
35
|
+
Every finding from both checkers, under each checker's `--json`:
|
|
51
36
|
|
|
52
37
|
| Field | Value |
|
|
53
38
|
| --- | --- |
|
|
@@ -69,8 +54,5 @@ Every finding's `repair` says what moving it costs.
|
|
|
69
54
|
|
|
70
55
|
- Add no token to `tokens.css`. Map a literal with no matching token to the nearest existing token by role. When no token fits, leave the finding and say so.
|
|
71
56
|
- When the nearest token differs from the literal, use the token and name the shift in the reply, such as `14px` to `--space-16`.
|
|
57
|
+
- Apply a `tokens-breaking-migration` finding with `npx live-tokens migrate`, after `npx live-tokens migrate --check` prints the plan. `--tokens <path>` names a tokens.css in an unusual place, and `--write` also rewrites the route references the plan lists. Name each renamed design token in the reply.
|
|
72
58
|
- Any finding, at any repair level, can stay as a deliberate exception when the user chooses to keep it. Record that decision in the config entry its `exception` field names, and prefer the narrower entry. When the user has chosen to lower a rule's severity, record it in `live-tokens.config.json` under `"checks": { "rules": { "<rule>": "warn" } }`. `--off=<rule>` silences a rule for one run only.
|
|
73
|
-
|
|
74
|
-
## Build script
|
|
75
|
-
|
|
76
|
-
When `package.json` has no `check:design` script, add `"check:design": "live-tokens check-page --no-fix && live-tokens check-component --no-fix"`. `--no-fix` keeps the build from editing files. When both checkers exit 0, prepend `npm run check:design &&` to the existing build command. Preserve its other build steps.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.81.0 — Skill Atlas links name a block
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **A Skill Atlas link opens one block.** Clicking a card or badge writes its
|
|
8
|
+
link to the address bar, named after the card title and the badge label:
|
|
9
|
+
`#set-type/write-the-font-pairing/voice`. Opening the link selects that
|
|
10
|
+
block and scrolls both panes to it. `#set-type` still opens the skill, and
|
|
11
|
+
a link to a block that no longer exists opens its skill.
|
|
12
|
+
|
|
13
|
+
## 0.80.0 — The build runs the design checks
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`vite build` runs the design checks.** `themeFileApi` runs both static
|
|
18
|
+
checkers at the start of the build, under the project's severities and
|
|
19
|
+
without repairs. An error stops the build, and warnings print.
|
|
20
|
+
`themeFileApi({ checks: false })` turns the checks off. The dev server
|
|
21
|
+
runs no checks.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **A CollapsibleSection header toggles from its label.** Without `href`, the
|
|
26
|
+
chevron and the label share one button, so a click anywhere on the header
|
|
27
|
+
opens or closes the section. The label is the button's accessible name, in
|
|
28
|
+
place of "Expand section" or "Collapse section".
|
|
29
|
+
- **The template's build is `vite build`.** Its `check:design` script is
|
|
30
|
+
gone, since the plugin runs the same checks.
|
|
31
|
+
- **check-compliance leaves the build alone.** It no longer adds
|
|
32
|
+
`check:design` to `package.json`.
|
|
33
|
+
- **`migrate` removes the `check:design` build step.** It removes the script
|
|
34
|
+
and its place in `build` when both hold what live-tokens wrote and the Vite
|
|
35
|
+
config uses `themeFileApi` with its checks on. It reports any other
|
|
36
|
+
`check:design` and leaves it in place.
|
|
37
|
+
|
|
38
|
+
- **The checkers bring `tokens.css` up to the installed package.** Before
|
|
39
|
+
either checker reads the design tokens, it applies every pending additive
|
|
40
|
+
migration and lists it in `fix.applied` under `tokens-migration`. A pending
|
|
41
|
+
breaking migration is a `tokens-breaking-migration` finding with
|
|
42
|
+
`repair: 'choice'`, and `npx live-tokens migrate` still applies it.
|
|
43
|
+
`--no-fix` reports both and writes nothing.
|
|
44
|
+
- **check-compliance runs only the checkers.** It no longer runs
|
|
45
|
+
`npx live-tokens report` or `npx live-tokens migrate` first. `report` stays
|
|
46
|
+
a CLI command.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- **`migrate`'s usage text names the breaking migrations it applies.** It
|
|
51
|
+
said additive only, and it has always applied both.
|
|
52
|
+
|
|
3
53
|
## 0.79.0 — Checks fix what they find
|
|
4
54
|
|
|
5
55
|
### Added
|
package/README.md
CHANGED
|
@@ -332,7 +332,7 @@ npx @motion-proto/live-tokens <command>
|
|
|
332
332
|
| `set-type <pairing.json> [--dry-run] [--no-verify]` | Bind Google Fonts families to the theme's font stacks, verified against the API. |
|
|
333
333
|
| `set-geometry <ops.json> [--dry-run]` | Move radius, padding, gap, and border-width aliases along their token scales. |
|
|
334
334
|
| `save-theme <name> [--no-activate] [--dry-run]` | Compose the live state into `themes/<slug>.json` and open it, which clears the unsaved buffers. |
|
|
335
|
-
| `migrate [--check] [--write] [--tokens <path>]` | Reconcile the project with the installed package:
|
|
335
|
+
| `migrate [--check] [--write] [--tokens <path>]` | Reconcile the project with the installed package: every `tokens.css` migration, breaking ones included, the pre-0.48 data-tree move, and a report on source references to the routes that moved in 0.35.0. |
|
|
336
336
|
|
|
337
337
|
Once installed in a project, the same commands are available as `npx live-tokens <command>`.
|
|
338
338
|
|
|
@@ -426,13 +426,13 @@ Verify the result:
|
|
|
426
426
|
npx @motion-proto/live-tokens check-component <id>
|
|
427
427
|
```
|
|
428
428
|
|
|
429
|
-
The validator checks the file layout, the `:global(:root)` block, the token-suffix vocabulary, the state-before-property rule, the public-imports rule, the `registerComponent({ id })` call, and that every default resolves to a design token rather than a literal. Exit code 0 means the static contract is met.
|
|
429
|
+
The validator checks the file layout, the `:global(:root)` block, the token-suffix vocabulary, the state-before-property rule, the public-imports rule, the `registerComponent({ id })` call, and that every default resolves to a design token rather than a literal. Exit code 0 means the static contract is met. `themeFileApi` runs it, with `check-page`, at the start of every `vite build`, so a component or page that opts out of the theme cannot ship by accident. An error stops the build, and warnings print. `themeFileApi({ checks: false })` turns the checks off.
|
|
430
430
|
|
|
431
431
|
### `live-tokens-check-compliance`
|
|
432
432
|
|
|
433
|
-
Ask how things stand, or ask for the code to catch up: "check this project against the design system and fix it", "audit the pricing page", "make
|
|
433
|
+
Ask how things stand, or ask for the code to catch up: "check this project against the design system and fix it", "audit the pricing page", "make the build's design checks pass", "fix the design-system warnings", "replace the hex and pixel values with tokens", "review this before I upgrade".
|
|
434
434
|
|
|
435
|
-
The skill runs
|
|
435
|
+
The skill runs both checkers, which apply every pending additive `tokens.css` migration and every `auto` repair themselves and return each remaining finding with its own `guidance`. The skill groups what remains by rule, largest error group first, makes each repair from its guidance, and re-runs until both checkers exit 0. It reports what `--strict` adds and clears the warnings when the request covers them. The reply names the fixes the checkers applied, the changes by rule with any visible shift, and each finding left with its reason. It never silences a rule to pass and never adds a token to `tokens.css` to match a value a page happened to use. A finding the user chooses to keep goes into the config entry its `exception` field names.
|
|
436
436
|
|
|
437
437
|
## From edit to production
|
|
438
438
|
|
package/bin/check-component.mjs
CHANGED
package/bin/check-page.mjs
CHANGED
|
@@ -25,6 +25,8 @@ import * as tokenRules from './rules/tokens.mjs';
|
|
|
25
25
|
* three fields, same meanings, as `COMPONENT_RULES`. */
|
|
26
26
|
export const PAGE_RULES = assembleRules(
|
|
27
27
|
[
|
|
28
|
+
'tokens-migration',
|
|
29
|
+
'tokens-breaking-migration',
|
|
28
30
|
'unknown-component',
|
|
29
31
|
'unknown-prop',
|
|
30
32
|
'unknown-prop-value',
|
package/bin/cli.mjs
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// set-geometry <ops> Apply radius/padding/gap/border-width ops to the open buffer.
|
|
13
13
|
// set-type <pairing> Bind Google Fonts families to the theme's font stacks.
|
|
14
14
|
// save-theme <name> Compose the live state into themes/<slug>.json and open it.
|
|
15
|
-
// migrate [...] Reconcile tokens.css, the data tree, and
|
|
15
|
+
// migrate [...] Reconcile tokens.css, the data tree, route references, and the build script.
|
|
16
16
|
|
|
17
17
|
import { writeSync } from 'node:fs';
|
|
18
18
|
import { dirname, join, resolve } from 'node:path';
|
|
@@ -33,8 +33,10 @@ import {
|
|
|
33
33
|
formatFindings,
|
|
34
34
|
parseCheckFlags,
|
|
35
35
|
readChecksConfig,
|
|
36
|
+
resolveRuleSeverity,
|
|
36
37
|
toJson,
|
|
37
38
|
} from './lib/findings.mjs';
|
|
39
|
+
import { checkTokensCssMigrations } from './rules/tokens.mjs';
|
|
38
40
|
import {
|
|
39
41
|
runMigrate,
|
|
40
42
|
formatMigrateResult,
|
|
@@ -42,6 +44,7 @@ import {
|
|
|
42
44
|
formatMigrateDataResult,
|
|
43
45
|
} from './migrate.mjs';
|
|
44
46
|
import { runMigrateRoutes, formatRouteResult } from './migrate-routes.mjs';
|
|
47
|
+
import { runMigrateBuildScript, formatBuildScriptResult } from './migrate-build-script.mjs';
|
|
45
48
|
import { runCreate, formatCreateResult } from './create.mjs';
|
|
46
49
|
import { runSetupClaude, formatSetupResult } from './setup-claude.mjs';
|
|
47
50
|
import { runSetColors, formatSetColorsResult } from './set-colors.mjs';
|
|
@@ -102,7 +105,8 @@ check-component and check-page also accept:
|
|
|
102
105
|
[...] } drops paths from discovery entirely)
|
|
103
106
|
--no-fix Report every finding and edit nothing, for a
|
|
104
107
|
build or CI. Without it, both checkers first
|
|
105
|
-
apply every
|
|
108
|
+
apply every additive tokens.css migration and
|
|
109
|
+
every finding whose repair is auto (a
|
|
106
110
|
deep import into a component, a raw dimension
|
|
107
111
|
with one nearest design token, a shipped
|
|
108
112
|
component's size prop, a semantic property
|
|
@@ -150,7 +154,8 @@ check-component and check-page also accept:
|
|
|
150
154
|
without writing.
|
|
151
155
|
migrate [--check] [--write] [--tokens <path>]
|
|
152
156
|
Reconcile the project with the installed package:
|
|
153
|
-
applies
|
|
157
|
+
applies every tokens.css migration, breaking
|
|
158
|
+
ones included, moves a
|
|
154
159
|
pre-0.48 data tree onto the current directory
|
|
155
160
|
names, heals what the retired pointer files named,
|
|
156
161
|
and reports source references to the
|
|
@@ -159,6 +164,9 @@ check-component and check-page also accept:
|
|
|
159
164
|
unambiguous route references (never /docs). --check
|
|
160
165
|
prints both plans without writing (exit 1 when
|
|
161
166
|
either is pending; route findings are advisory).
|
|
167
|
+
Also removes the check:design script live-tokens
|
|
168
|
+
added to the build, since vite build now runs the
|
|
169
|
+
design checks through themeFileApi.
|
|
162
170
|
`;
|
|
163
171
|
|
|
164
172
|
// A large body written through console.log is cut at the pipe buffer when the
|
|
@@ -226,6 +234,14 @@ function shiftNote(f) {
|
|
|
226
234
|
return ` shift ${shifts.join(', ')}`;
|
|
227
235
|
}
|
|
228
236
|
|
|
237
|
+
async function migrateTokensCss(rules, opts) {
|
|
238
|
+
const root = process.cwd();
|
|
239
|
+
const config = readChecksConfig(root);
|
|
240
|
+
const apply = !opts.noFix && resolveRuleSeverity('tokens-migration', rules, opts, config) !== 'off';
|
|
241
|
+
const { applied, findings } = await checkTokensCssMigrations({ root, apply });
|
|
242
|
+
return { applied: applySeverity(applied, rules, opts, config), findings };
|
|
243
|
+
}
|
|
244
|
+
|
|
229
245
|
/** Resolves severities and exclusions first, so a silenced rule or an excluded
|
|
230
246
|
* file is never edited. */
|
|
231
247
|
function applyStaticFixes(findings, rules, opts, { exclude } = {}) {
|
|
@@ -236,7 +252,11 @@ function applyStaticFixes(findings, rules, opts, { exclude } = {}) {
|
|
|
236
252
|
function formatFixes({ applied, skipped }) {
|
|
237
253
|
const lines = [];
|
|
238
254
|
for (const f of applied) {
|
|
239
|
-
lines.push(
|
|
255
|
+
lines.push(
|
|
256
|
+
f.details.patch
|
|
257
|
+
? ` fixed ${f.file}:${f.line} ${f.details.patch.from} → ${f.details.patch.to || '(removed)'} [${f.rule}]${shiftNote(f)}`
|
|
258
|
+
: ` fixed ${f.file} ${f.details.migrations.map((m) => m.id).join(', ')} [${f.rule}]`,
|
|
259
|
+
);
|
|
240
260
|
}
|
|
241
261
|
for (const f of skipped) lines.push(` skipped ${f.file}:${f.line} ${f.details.patch.from} [${f.rule}]`);
|
|
242
262
|
if (lines.length === 0) return '';
|
|
@@ -291,24 +311,26 @@ if (command === 'check-component') {
|
|
|
291
311
|
console.log('✓ check-component: no component authored under src/system/components yet.');
|
|
292
312
|
process.exit(0);
|
|
293
313
|
}
|
|
314
|
+
const migration = await migrateTokensCss(COMPONENT_RULES, opts);
|
|
294
315
|
let results = ids.map((id) => [id, checkComponent(id)]);
|
|
295
316
|
const label = ids.length === 1 ? `check-component ${ids[0]}${opts.tests ? ' --tests' : ''}` : `check-component${opts.tests ? ' --tests' : ''}`;
|
|
296
317
|
let fix;
|
|
297
318
|
if (!opts.noFix) {
|
|
298
319
|
fix = applyStaticFixes(results.flatMap(([, r]) => r.findings), COMPONENT_RULES, opts);
|
|
299
320
|
if (fix.applied.length > 0) results = ids.map((id) => [id, checkComponent(id)]);
|
|
321
|
+
fix.applied.unshift(...migration.applied);
|
|
300
322
|
}
|
|
301
|
-
if (!opts.tests && ids.length === 1 && !opts.json && !opts.strict && opts.off.length + opts.warn.length + opts.error.length === 0) {
|
|
323
|
+
if (!opts.tests && migration.findings.length === 0 && ids.length === 1 && !opts.json && !opts.strict && opts.off.length + opts.warn.length + opts.error.length === 0) {
|
|
302
324
|
const [id, result] = results[0];
|
|
303
325
|
console.log([fix ? formatFixes(fix) : '', formatReport(id, result)].filter(Boolean).join('\n'));
|
|
304
326
|
process.exit(result.errors.length === 0 ? 0 : 1);
|
|
305
327
|
}
|
|
306
328
|
if (!opts.tests) {
|
|
307
|
-
reportChecks(label, results.flatMap(([, r]) => r.findings), ids.length, COMPONENT_RULES, opts, { fix });
|
|
329
|
+
reportChecks(label, [...migration.findings, ...results.flatMap(([, r]) => r.findings)], ids.length, COMPONENT_RULES, opts, { fix });
|
|
308
330
|
}
|
|
309
331
|
const { hasHardFailure, runContractTests } = await import('./contractRunner.mjs');
|
|
310
332
|
const testOutcome = await runContractTests(opts.rest[0], { root: process.cwd() });
|
|
311
|
-
const findings = dedupeAliasFindings([...results.flatMap(([, r]) => r.findings), ...testOutcome.findings]);
|
|
333
|
+
const findings = dedupeAliasFindings([...migration.findings, ...results.flatMap(([, r]) => r.findings), ...testOutcome.findings]);
|
|
312
334
|
reportChecks(label, findings, Math.max(ids.length, 1), COMPONENT_RULES, opts, {
|
|
313
335
|
fix,
|
|
314
336
|
coverage: testOutcome.coverage,
|
|
@@ -319,12 +341,15 @@ if (command === 'check-component') {
|
|
|
319
341
|
if (command === 'check-page') {
|
|
320
342
|
const opts = parseCheckFlags(rest);
|
|
321
343
|
const targets = opts.rest.length > 0 ? opts.rest : discoverPages(process.cwd());
|
|
344
|
+
const migration = await migrateTokensCss(PAGE_RULES, opts);
|
|
322
345
|
let { findings, checked } = checkPages(targets, { root: process.cwd() });
|
|
323
346
|
let fix;
|
|
324
347
|
if (!opts.noFix) {
|
|
325
348
|
fix = applyStaticFixes(findings, PAGE_RULES, opts, { exclude: true });
|
|
326
349
|
if (fix.applied.length > 0) ({ findings, checked } = checkPages(targets, { root: process.cwd() }));
|
|
350
|
+
fix.applied.unshift(...migration.applied);
|
|
327
351
|
}
|
|
352
|
+
findings = [...migration.findings, ...findings];
|
|
328
353
|
if (!opts.tests) {
|
|
329
354
|
reportChecks('check-page', findings, checked, PAGE_RULES, opts, { exclude: true, fix });
|
|
330
355
|
}
|
|
@@ -458,6 +483,12 @@ if (command === 'migrate') {
|
|
|
458
483
|
const routeOut = formatRouteResult(routes, { check });
|
|
459
484
|
if (routeOut) console.log('\n' + routeOut);
|
|
460
485
|
|
|
486
|
+
// Build-script pass: removes the check:design step that themeFileApi now
|
|
487
|
+
// runs during vite build. Advisory under --check, like the data heal.
|
|
488
|
+
const buildScript = runMigrateBuildScript({ root: process.cwd(), apply: !check });
|
|
489
|
+
const buildScriptOut = formatBuildScriptResult(buildScript);
|
|
490
|
+
if (buildScriptOut) console.log('\n' + buildScriptOut);
|
|
491
|
+
|
|
461
492
|
// Route findings are advisory; token migrations and the data heal gate the
|
|
462
493
|
// exit code.
|
|
463
494
|
if (result.status === 'no-path') process.exit(1);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// The design checks `vite build` runs through themeFileApi: both static
|
|
2
|
+
// checkers with no repairs, under the project's own severities. The same bar
|
|
3
|
+
// `check-page --no-fix` and `check-component --no-fix` hold.
|
|
4
|
+
|
|
5
|
+
import { COMPONENT_RULES, checkComponent, discoverComponents } from '../check-component.mjs';
|
|
6
|
+
import { PAGE_RULES, checkPages, discoverPages } from '../check-page.mjs';
|
|
7
|
+
import { checkTokensCssMigrations } from '../rules/tokens.mjs';
|
|
8
|
+
import { applySeverity, countBySeverity, formatFindings, readChecksConfig } from './findings.mjs';
|
|
9
|
+
import { loadVocabulary } from './tokenVocabulary.mjs';
|
|
10
|
+
|
|
11
|
+
export async function runBuildChecks({ root = process.cwd(), engine } = {}) {
|
|
12
|
+
const config = readChecksConfig(root);
|
|
13
|
+
const vocabulary = loadVocabulary({ root });
|
|
14
|
+
const migration = await checkTokensCssMigrations({ root, apply: false, engine });
|
|
15
|
+
const pages = checkPages(discoverPages(root), { root, vocabulary });
|
|
16
|
+
const ids = discoverComponents(root);
|
|
17
|
+
const components = ids.map((id) => checkComponent(id, root, { vocabulary }));
|
|
18
|
+
|
|
19
|
+
const pageFindings = applySeverity([...migration.findings, ...pages.findings], PAGE_RULES, {}, config, { exclude: true });
|
|
20
|
+
const componentFindings = applySeverity(components.flatMap((r) => r.findings), COMPONENT_RULES, {}, config);
|
|
21
|
+
const page = countBySeverity(pageFindings);
|
|
22
|
+
const component = countBySeverity(componentFindings);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
errors: page.errors + component.errors,
|
|
26
|
+
warnings: page.warnings + component.warnings,
|
|
27
|
+
report: [
|
|
28
|
+
formatFindings(pageFindings, { label: 'check-page', checked: pages.checked }),
|
|
29
|
+
formatFindings(componentFindings, { label: 'check-component', checked: ids.length }),
|
|
30
|
+
].join('\n'),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// `live-tokens migrate` build-script pass.
|
|
2
|
+
//
|
|
3
|
+
// Through 0.79 the project template, and the check-compliance skill for an
|
|
4
|
+
// existing project, ran both checkers as `npm run check:design` ahead of
|
|
5
|
+
// `vite build`. themeFileApi now runs them during `vite build` itself, so the
|
|
6
|
+
// script only repeats the work. This pass removes it when package.json holds
|
|
7
|
+
// exactly what the template and the skill wrote and the build still gets the
|
|
8
|
+
// checks from the plugin. Anything else is reported and left alone.
|
|
9
|
+
|
|
10
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
|
|
13
|
+
const SCRIPT = 'check:design';
|
|
14
|
+
const SHIPPED_SCRIPT = /^(npx )?live-tokens check-page --no-fix && (npx )?live-tokens check-component --no-fix$/;
|
|
15
|
+
const BUILD_PREFIX = /^npm run check:design\s*&&\s*/;
|
|
16
|
+
const VITE_CONFIGS = ['vite.config.ts', 'vite.config.js', 'vite.config.mjs', 'vite.config.mts'];
|
|
17
|
+
|
|
18
|
+
function viteConfigText(root) {
|
|
19
|
+
const file = VITE_CONFIGS.map((name) => join(root, name)).find((p) => existsSync(p));
|
|
20
|
+
return file ? readFileSync(file, 'utf8') : '';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function runMigrateBuildScript({ root = process.cwd(), apply = true } = {}) {
|
|
24
|
+
const file = join(root, 'package.json');
|
|
25
|
+
if (!existsSync(file)) return { status: 'unchanged' };
|
|
26
|
+
const text = readFileSync(file, 'utf8');
|
|
27
|
+
let pkg;
|
|
28
|
+
try {
|
|
29
|
+
pkg = JSON.parse(text);
|
|
30
|
+
} catch {
|
|
31
|
+
return { status: 'unchanged' };
|
|
32
|
+
}
|
|
33
|
+
const scripts = pkg.scripts ?? {};
|
|
34
|
+
if (!(SCRIPT in scripts)) return { status: 'unchanged' };
|
|
35
|
+
|
|
36
|
+
const vite = viteConfigText(root);
|
|
37
|
+
if (!vite.includes('themeFileApi')) {
|
|
38
|
+
return { status: 'advisory', reason: `keeps "${SCRIPT}": the Vite config does not use themeFileApi, so the build has no other design checks.` };
|
|
39
|
+
}
|
|
40
|
+
if (/checks\s*:\s*false/.test(vite)) {
|
|
41
|
+
return { status: 'advisory', reason: `keeps "${SCRIPT}": themeFileApi is set to checks: false.` };
|
|
42
|
+
}
|
|
43
|
+
if (!SHIPPED_SCRIPT.test(String(scripts[SCRIPT]).trim())) {
|
|
44
|
+
return { status: 'advisory', reason: `keeps "${SCRIPT}": it differs from the script live-tokens added. vite build now runs the design checks, so remove it by hand if it only repeats them.` };
|
|
45
|
+
}
|
|
46
|
+
const build = String(scripts.build ?? '');
|
|
47
|
+
const users = Object.keys(scripts).filter((name) => name !== SCRIPT && name !== 'build' && String(scripts[name]).includes(SCRIPT));
|
|
48
|
+
if (build.includes(SCRIPT) && !BUILD_PREFIX.test(build)) users.push('build');
|
|
49
|
+
if (users.length > 0) {
|
|
50
|
+
return { status: 'advisory', reason: `keeps "${SCRIPT}": ${users.map((u) => `"${u}"`).join(', ')} still run${users.length === 1 ? 's' : ''} it.` };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!apply) return { status: 'would-change' };
|
|
54
|
+
if (BUILD_PREFIX.test(build)) scripts.build = build.replace(BUILD_PREFIX, '');
|
|
55
|
+
delete scripts[SCRIPT];
|
|
56
|
+
const indent = /^([ \t]+)"/m.exec(text)?.[1] ?? ' ';
|
|
57
|
+
writeFileSync(file, JSON.stringify(pkg, null, indent) + (text.endsWith('\n') ? '\n' : ''));
|
|
58
|
+
return { status: 'changed' };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function formatBuildScriptResult(result) {
|
|
62
|
+
if (result.status === 'changed') return `✓ package.json: removed "${SCRIPT}" from the build. vite build runs the design checks through themeFileApi.`;
|
|
63
|
+
if (result.status === 'would-change') return `package.json: would remove "${SCRIPT}" from the build. vite build runs the design checks through themeFileApi.`;
|
|
64
|
+
if (result.status === 'advisory') return `! package.json ${result.reason}`;
|
|
65
|
+
return '';
|
|
66
|
+
}
|
package/bin/migrate.mjs
CHANGED
|
@@ -37,6 +37,11 @@ async function loadEngine() {
|
|
|
37
37
|
return import(ENGINE);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/** Null only before build:plugin, which is this repo's own `npm test` in CI; a published package always ships the engine. */
|
|
41
|
+
export async function loadBuiltEngine() {
|
|
42
|
+
return existsSync(ENGINE) ? import(ENGINE) : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
/** --tokens <path> > live-tokens.config.json tokensCssPath > default scan. */
|
|
41
46
|
export function resolveTokensCssPath(explicit, configPath, root = process.cwd()) {
|
|
42
47
|
if (explicit) return resolve(root, explicit);
|
package/bin/rules/tokens.mjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
1
2
|
import { relative } from 'node:path';
|
|
3
|
+
import { loadBuiltEngine, resolveTokensCssPath } from '../migrate.mjs';
|
|
2
4
|
import { scaleTokens } from '../lib/catalogue.mjs';
|
|
3
5
|
import { COLOR_SCALES, GEOMETRY_SCALES, STATE_TOKENS, readKindRules, tokenScale, unreadTokens } from '../lib/componentSource.mjs';
|
|
4
6
|
import { colorScaleOfProperty, hasColorLiteral, hasDimensionLiteral, stripVarFallbacks } from '../lib/cssValues.mjs';
|
|
5
7
|
import { readComponentConfig } from '../lib/dataDir.mjs';
|
|
6
|
-
import { findJsonKeyLine } from '../lib/findings.mjs';
|
|
8
|
+
import { findJsonKeyLine, isExcluded } from '../lib/findings.mjs';
|
|
7
9
|
import { geometryScaleOfProperty, resolveGeometryLiteral } from '../lib/geometry.mjs';
|
|
8
10
|
import { declarationPatch, declarations, neutralise, pageDeclaredNames } from '../lib/pageSource.mjs';
|
|
9
11
|
import { isContractToken } from '../lib/tokenVocabulary.mjs';
|
|
@@ -35,6 +37,18 @@ const COMPONENT_DEFAULT = 'In a component, make the :global(:root) default read
|
|
|
35
37
|
const INTRINSIC = "Declare a structural keyword, such as start, in the editor's `intrinsics`.";
|
|
36
38
|
|
|
37
39
|
const shared = {
|
|
40
|
+
'tokens-migration': {
|
|
41
|
+
severity: 'error',
|
|
42
|
+
repair: 'auto',
|
|
43
|
+
guidance:
|
|
44
|
+
'A run without --no-fix applies these migrations to tokens.css. Each one adds design tokens the installed package reads and changes no existing token. `details.migrations` names them.',
|
|
45
|
+
},
|
|
46
|
+
'tokens-breaking-migration': {
|
|
47
|
+
severity: 'error',
|
|
48
|
+
repair: 'choice',
|
|
49
|
+
guidance:
|
|
50
|
+
'Each migration in `details.migrations` renames, removes, or rewrites design tokens in tokens.css. Read the plan with `npx live-tokens migrate --check`, apply it with `npx live-tokens migrate`, and move any page or component that reads an old name to the new one. To keep tokens.css as it is, record the `exception`.',
|
|
51
|
+
},
|
|
38
52
|
'color-literal': {
|
|
39
53
|
severity: 'error',
|
|
40
54
|
repair: 'choice',
|
|
@@ -361,3 +375,52 @@ function checkDeclaration(decl, { text, region, vocab, at }, add) {
|
|
|
361
375
|
);
|
|
362
376
|
}
|
|
363
377
|
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Runs before either checker reads the vocabulary, so a tokens.css behind the
|
|
381
|
+
* installed package never surfaces as unknown tokens. `engine` is a test seam.
|
|
382
|
+
*/
|
|
383
|
+
export async function checkTokensCssMigrations({ root = process.cwd(), apply, engine } = {}) {
|
|
384
|
+
const result = { applied: [], findings: [] };
|
|
385
|
+
const loaded = engine ?? (await loadBuiltEngine());
|
|
386
|
+
if (!loaded) return result;
|
|
387
|
+
const { TOKENS_CSS_MIGRATIONS, readLiveTokensConfig, runAdditiveTokensCssMigrations, runTokensCssMigrations } = loaded;
|
|
388
|
+
const tokensPath = resolveTokensCssPath(null, readLiveTokensConfig().tokensCssPath, root);
|
|
389
|
+
if (!tokensPath || !existsSync(tokensPath)) return result;
|
|
390
|
+
const file = relative(root, tokensPath);
|
|
391
|
+
if (isExcluded(file, root)) return result;
|
|
392
|
+
|
|
393
|
+
const migration = (id) => TOKENS_CSS_MIGRATIONS.find((m) => m.id === id);
|
|
394
|
+
const listed = (ids) => ids.map((id) => ({ id, description: migration(id).description }));
|
|
395
|
+
|
|
396
|
+
let css = readFileSync(tokensPath, 'utf8');
|
|
397
|
+
const additive = runAdditiveTokensCssMigrations(css);
|
|
398
|
+
if (additive.changed) {
|
|
399
|
+
const finding = {
|
|
400
|
+
rule: 'tokens-migration',
|
|
401
|
+
file,
|
|
402
|
+
line: 1,
|
|
403
|
+
message: `${file} lacks design tokens the installed package reads: ${additive.applied.join(', ')}`,
|
|
404
|
+
details: { migrations: listed(additive.applied) },
|
|
405
|
+
};
|
|
406
|
+
if (apply) {
|
|
407
|
+
writeFileSync(tokensPath, additive.css);
|
|
408
|
+
css = additive.css;
|
|
409
|
+
result.applied.push(finding);
|
|
410
|
+
} else {
|
|
411
|
+
result.findings.push(finding);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const breaking = runTokensCssMigrations(css).applied.filter((id) => migration(id).kind === 'breaking');
|
|
416
|
+
if (breaking.length > 0) {
|
|
417
|
+
result.findings.push({
|
|
418
|
+
rule: 'tokens-breaking-migration',
|
|
419
|
+
file,
|
|
420
|
+
line: 1,
|
|
421
|
+
message: `${file} has breaking migrations pending: ${breaking.join(', ')}`,
|
|
422
|
+
details: { migrations: listed(breaking) },
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
return result;
|
|
426
|
+
}
|
package/dist-plugin/index.cjs
CHANGED
|
@@ -3925,12 +3925,35 @@ function validateTokensCss(input) {
|
|
|
3925
3925
|
return missing.sort((a, b) => a.token.localeCompare(b.token));
|
|
3926
3926
|
}
|
|
3927
3927
|
|
|
3928
|
-
// vite-plugin/
|
|
3928
|
+
// vite-plugin/buildChecks.ts
|
|
3929
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
3929
3930
|
var import_node_url = require("url");
|
|
3930
3931
|
var import_meta = {};
|
|
3932
|
+
var runPackageChecks = async (root) => {
|
|
3933
|
+
const runner = import_node_path.default.resolve(import_node_path.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url)), "..", "bin", "lib", "buildChecks.mjs");
|
|
3934
|
+
const { runBuildChecks } = await import((0, import_node_url.pathToFileURL)(runner).href);
|
|
3935
|
+
return runBuildChecks({ root });
|
|
3936
|
+
};
|
|
3937
|
+
async function checkBeforeBuild({
|
|
3938
|
+
root,
|
|
3939
|
+
logger,
|
|
3940
|
+
run = runPackageChecks
|
|
3941
|
+
}) {
|
|
3942
|
+
const result = await run(root);
|
|
3943
|
+
if (result.errors > 0) {
|
|
3944
|
+
logger.error(result.report);
|
|
3945
|
+
return `live-tokens: the design checks found ${result.errors} error(s). Run the live-tokens-check-compliance skill, or \`npx live-tokens check-page\` and \`npx live-tokens check-component\`, to repair them.`;
|
|
3946
|
+
}
|
|
3947
|
+
(result.warnings > 0 ? logger.warn : logger.info).call(logger, result.report);
|
|
3948
|
+
return null;
|
|
3949
|
+
}
|
|
3950
|
+
|
|
3951
|
+
// vite-plugin/themeFileApi.ts
|
|
3952
|
+
var import_node_url2 = require("url");
|
|
3953
|
+
var import_meta2 = {};
|
|
3931
3954
|
var PKG_VERSION = (() => {
|
|
3932
3955
|
try {
|
|
3933
|
-
let dir = import_path5.default.dirname((0,
|
|
3956
|
+
let dir = import_path5.default.dirname((0, import_node_url2.fileURLToPath)(import_meta2.url));
|
|
3934
3957
|
for (let i = 0; i < 4; i++) {
|
|
3935
3958
|
const p = import_path5.default.join(dir, "package.json");
|
|
3936
3959
|
if (import_fs5.default.existsSync(p)) {
|
|
@@ -3988,14 +4011,14 @@ function themeFileApi(opts) {
|
|
|
3988
4011
|
const API_BASE = opts.apiBase ?? "/api/live-tokens";
|
|
3989
4012
|
const consumerComponentDirs = opts.componentsSrcDir ? [import_path5.default.resolve(opts.componentsSrcDir)] : [import_path5.default.resolve("src/components"), import_path5.default.resolve("src/system/components")];
|
|
3990
4013
|
const packageComponentsDir = import_path5.default.resolve(
|
|
3991
|
-
import_path5.default.dirname((0,
|
|
4014
|
+
import_path5.default.dirname((0, import_node_url2.fileURLToPath)(import_meta2.url)),
|
|
3992
4015
|
"..",
|
|
3993
4016
|
"src",
|
|
3994
4017
|
"system",
|
|
3995
4018
|
"components"
|
|
3996
4019
|
);
|
|
3997
4020
|
const packageDataDir = testPackageDataDir() ?? import_path5.default.resolve(
|
|
3998
|
-
import_path5.default.dirname((0,
|
|
4021
|
+
import_path5.default.dirname((0, import_node_url2.fileURLToPath)(import_meta2.url)),
|
|
3999
4022
|
"..",
|
|
4000
4023
|
"src",
|
|
4001
4024
|
"live-tokens",
|
|
@@ -5347,6 +5370,8 @@ data: ${JSON.stringify(state)}
|
|
|
5347
5370
|
{ method: "DELETE", pattern: PRODUCTION_ROUTE, handler: methodNotAllowed }
|
|
5348
5371
|
];
|
|
5349
5372
|
const isEditorOwnedJson = (file) => file.endsWith(".json") && import_path5.default.resolve(file).startsWith(dataDirs.dataDir + import_path5.default.sep);
|
|
5373
|
+
let buildLogger = null;
|
|
5374
|
+
let buildChecked = false;
|
|
5350
5375
|
return {
|
|
5351
5376
|
name: "theme-file-api",
|
|
5352
5377
|
config() {
|
|
@@ -5359,6 +5384,15 @@ data: ${JSON.stringify(state)}
|
|
|
5359
5384
|
server: { watch: { ignored: [isEditorOwnedJson] } }
|
|
5360
5385
|
};
|
|
5361
5386
|
},
|
|
5387
|
+
configResolved(config) {
|
|
5388
|
+
buildLogger = config.command === "build" && opts.checks !== false ? config.logger : null;
|
|
5389
|
+
},
|
|
5390
|
+
async buildStart() {
|
|
5391
|
+
if (!buildLogger || buildChecked) return;
|
|
5392
|
+
buildChecked = true;
|
|
5393
|
+
const failure = await checkBeforeBuild({ root: process.cwd(), logger: buildLogger });
|
|
5394
|
+
if (failure) this.error(failure);
|
|
5395
|
+
},
|
|
5362
5396
|
configureServer(server) {
|
|
5363
5397
|
legacyLayout = detectLegacyLayout({
|
|
5364
5398
|
dataDir: dataDirs.dataDir,
|
package/dist-plugin/index.d.cts
CHANGED