@motion-proto/live-tokens 0.75.0 → 0.77.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-create-component/SKILL.md +3 -3
- package/.claude/skills/live-tokens-create-component/references/contract-tests.md +99 -3
- package/CHANGELOG.md +72 -0
- package/README.md +14 -4
- package/bin/check-component.mjs +2 -0
- package/bin/cli.mjs +12 -58
- package/bin/contractRunner.mjs +6 -1
- package/bin/setup-claude.mjs +110 -0
- package/package.json +1 -1
- package/src/editor/skill-atlas/skillSources.generated.ts +2 -2
- package/src/editor/skill-atlas/trees/create-component.ts +8 -3
- package/src/testing-js/{chunk-LXR3MN6N.js → chunk-4JQX6WWL.js} +1 -1
- package/src/testing-js/chunk-4JQX6WWL.js.map +1 -0
- package/src/testing-js/{chunk-AO7EZHYV.js → chunk-7TI7Z6Y6.js} +2 -2
- package/src/testing-js/component-editor.contract.js +19 -3
- package/src/testing-js/component-editor.contract.js.map +1 -1
- package/src/testing-js/component-render.contract.js +2 -2
- package/src/testing-js/index.d.ts +1 -1
- package/src/testing-js/index.js +1 -11
- package/src/testing-js/index.js.map +1 -1
- package/template/README.md +7 -2
- package/template/_gitignore +4 -0
- package/template/live-tokens.testing.ts +6 -0
- package/template/package.json +6 -2
- package/template/src/main.ts +1 -0
- package/template/src/registerComponents.ts +4 -0
- package/template/tests/contracts.ts +3 -0
- package/template/tsconfig.json +1 -1
- package/src/testing-js/chunk-LXR3MN6N.js.map +0 -1
- /package/src/testing-js/{chunk-AO7EZHYV.js.map → chunk-7TI7Z6Y6.js.map} +0 -0
|
@@ -13,7 +13,7 @@ Create a component whose structure and behavior serve the user's purpose. Give e
|
|
|
13
13
|
2. Design the properties: separate the component's parts, variants, and states, then write one row per editable role with its token and the CSS it controls, named the way the shipped components name the same role.
|
|
14
14
|
3. Write the runtime file: the usage comment and the `:global(:root)` block. A structural choice is an intrinsic. Every component joins the sketch layer, and a fixed overlay portals to `<body>`.
|
|
15
15
|
4. Write the editor file: the schema, the preview props, and the markup. Variants that share a value are linked.
|
|
16
|
-
5. Register the component in the module `src/main.ts` and `live-tokens.testing.ts` both name.
|
|
16
|
+
5. Register the component in the module `src/main.ts` and `live-tokens.testing.ts` both name, and write its contract in the module `contractsModule` names.
|
|
17
17
|
6. Run **live-tokens-check-compliance**, then `npx live-tokens check-component <id> --tests --strict --json` until exit 0 with complete applicable coverage, then the Svelte check and the build.
|
|
18
18
|
7. Reply with the files, the id, the props, and each check's result. Then place the component on a page with **live-tokens-create-page**.
|
|
19
19
|
|
|
@@ -204,7 +204,7 @@ registerComponent({
|
|
|
204
204
|
});
|
|
205
205
|
```
|
|
206
206
|
|
|
207
|
-
Import the module from `src/main.ts`, before `bootLiveTokens` or `mount`, and name it as `registrySetup` in `live-tokens.testing.ts`. One module then serves the running app and `check-component --tests`, which imports it to see the registration without mounting the app
|
|
207
|
+
Import the module from `src/main.ts`, before `bootLiveTokens` or `mount`, and name it as `registrySetup` in `live-tokens.testing.ts`. One module then serves the running app and `check-component --tests`, which imports it to see the registration without mounting the app. The component's contract, one `ComponentContract` in the module `contractsModule` names, is what the eight component suites run; without it `--tests` reports `contract-missing`. Read `references/contract-tests.md` for both. A component that declares intrinsics adds `intrinsics` to the entry. `check-component` finds the registration by the id literal inside the call.
|
|
208
208
|
|
|
209
209
|
At boot the plugin reads the `:global(:root)` block and writes `component-configs/<id>/default.json`, one token per property. An edit in the editor writes `_working.json`; Save As writes a named config. The assignments stay token references through that flow.
|
|
210
210
|
|
|
@@ -218,7 +218,7 @@ A fixed overlay portals to `<body>`: read `references/fixed-overlays.md`. A cont
|
|
|
218
218
|
|
|
219
219
|
## Verification
|
|
220
220
|
|
|
221
|
-
1. Run **live-tokens-check-compliance** and address its findings with **live-tokens-fix-findings**. Then run `npx live-tokens check-component <id> --tests --strict --json`. Inside the live-tokens repository, run `node bin/cli.mjs check-component <id> --tests --strict --json`. Each finding carries a rule id and a line; `--off=<rule>` silences a rule for one run. `--tests` runs the registry contract and the component contract suites
|
|
221
|
+
1. Run **live-tokens-check-compliance** and address its findings with **live-tokens-fix-findings**. Then run `npx live-tokens check-component <id> --tests --strict --json`. Inside the live-tokens repository, run `node bin/cli.mjs check-component <id> --tests --strict --json`. Each finding carries a rule id and a line; `--off=<rule>` silences a rule for one run. `--tests` runs the registry contract and, for a component with a contract in the module `contractsModule` names, the component contract suites; its JSON reports coverage by rule. A `tests-not-installed` finding names the missing package; install `@playwright/test`, `vitest`, and `happy-dom` as devDependencies, then `npx playwright install chromium`. Fix every finding and rerun until exit 0 with complete applicable coverage and no disabled checks.
|
|
222
222
|
2. Run the project's Svelte check and its build.
|
|
223
223
|
3. Reply with the files, the component id, the props, and the results of steps 1 and 2, naming any check the environment prevented.
|
|
224
224
|
|
|
@@ -17,9 +17,11 @@ It resolves a shipped component's `sourceFile` against the package and yours
|
|
|
17
17
|
against your project, and reports a component that exists as files and never
|
|
18
18
|
reached a registration.
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
`
|
|
22
|
-
|
|
20
|
+
A project scaffolded by `create` has `@playwright/test`, `vitest`, and
|
|
21
|
+
`happy-dom` installed, with `live-tokens.testing.ts`, `src/registerComponents.ts`,
|
|
22
|
+
and `tests/contracts.ts` in place. Any other project adds the three as
|
|
23
|
+
devDependencies. Both run `npx playwright install chromium` once. A missing
|
|
24
|
+
one is a `tests-not-installed` finding naming the install command.
|
|
23
25
|
|
|
24
26
|
Name the module that registers your components, in `live-tokens.testing.ts` at
|
|
25
27
|
the project root, as a plain quoted string:
|
|
@@ -60,6 +62,100 @@ of their own.
|
|
|
60
62
|
you; it narrows the run to one component and fails when no component is
|
|
61
63
|
registered under that id.
|
|
62
64
|
|
|
65
|
+
## The component contract
|
|
66
|
+
|
|
67
|
+
The eight Playwright suites drive one `ComponentContract` per component. The
|
|
68
|
+
package ships one for each of its own components and none for yours. A run
|
|
69
|
+
with `LIVE_TOKENS_COMPONENT=<id>` and no contract for `<id>` reports one
|
|
70
|
+
`contract-missing` finding, which names the setting that unlocks the suites.
|
|
71
|
+
|
|
72
|
+
Name the module that exports your contracts as `contractsModule`, next to
|
|
73
|
+
`registrySetup`:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
// live-tokens.testing.ts
|
|
77
|
+
export default defineTestingConfig({
|
|
78
|
+
registrySetup: 'src/registerComponents.ts',
|
|
79
|
+
contractsModule: 'tests/contracts.ts',
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The module exports a `ComponentContract[]`, as the default export or a named
|
|
84
|
+
`contracts` export. The type ships from `@motion-proto/live-tokens/testing`,
|
|
85
|
+
and the doc comment on each field is the reference for it. A contract
|
|
86
|
+
declares the component's parts as selectors inside the preview, then one
|
|
87
|
+
expectation per obligation:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
// tests/contracts.ts
|
|
91
|
+
import type { ComponentContract } from '@motion-proto/live-tokens/testing';
|
|
92
|
+
|
|
93
|
+
export const contracts: ComponentContract[] = [
|
|
94
|
+
{
|
|
95
|
+
id: 'statcard',
|
|
96
|
+
origin: 'custom',
|
|
97
|
+
root: 'root',
|
|
98
|
+
parts: { root: '.statcard', value: '.statcard-value' },
|
|
99
|
+
properties: [
|
|
100
|
+
{
|
|
101
|
+
paints: {
|
|
102
|
+
root: {
|
|
103
|
+
backgroundColor: '--statcard-surface',
|
|
104
|
+
borderTopColor: '--statcard-border',
|
|
105
|
+
borderTopWidth: '--statcard-border-width',
|
|
106
|
+
borderRadius: '--statcard-radius',
|
|
107
|
+
paddingTop: '--statcard-padding',
|
|
108
|
+
},
|
|
109
|
+
value: { color: '--statcard-value-text', fontSize: '--statcard-value-font-size' },
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
states: { applicable: false, reason: 'a stat card renders one state' },
|
|
114
|
+
persistence: {
|
|
115
|
+
cases: [
|
|
116
|
+
{ shape: 'token', variable: '--statcard-radius', observe: { part: 'root', css: 'borderRadius' } },
|
|
117
|
+
],
|
|
118
|
+
resetVariable: '--statcard-radius',
|
|
119
|
+
},
|
|
120
|
+
theme: {
|
|
121
|
+
theme: 'brand',
|
|
122
|
+
changed: ['--statcard-radius'],
|
|
123
|
+
unchanged: ['--statcard-padding'],
|
|
124
|
+
aliasedTo: { '--statcard-radius': '--radius-none' },
|
|
125
|
+
observe: { part: 'root', css: 'borderRadius', variable: '--statcard-radius' },
|
|
126
|
+
},
|
|
127
|
+
interaction: { applicable: false, reason: 'a stat card carries no interactive role' },
|
|
128
|
+
sketch: {
|
|
129
|
+
style: 'pencil',
|
|
130
|
+
parts: [{ part: 'root', fill: '--statcard-surface', stroke: '--statcard-border' }],
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Each obligation and what it needs from the project:
|
|
137
|
+
|
|
138
|
+
- `properties`: part key, then CSS property, then the semantic property that
|
|
139
|
+
drives it. One entry per variant tab and state tab the editor renders;
|
|
140
|
+
omit `variant` and `state` when the editor renders one view. The paint maps
|
|
141
|
+
and `uncovered` together name every property in `default.json`. A property
|
|
142
|
+
no computed style shows verbatim, one consumed inside `calc()` or a gradient
|
|
143
|
+
function, goes in `uncovered` with its reason.
|
|
144
|
+
- `states`: one entry per state tab, with the class or attributes the preview
|
|
145
|
+
forces. `applicable: false` when the editor renders no state strip.
|
|
146
|
+
- `persistence`: an edit the run makes through the editor, saves, reloads, and
|
|
147
|
+
reads back. `resetVariable` is a property one of the cases moves.
|
|
148
|
+
- `theme`: a theme in the project's data tree whose block for the component
|
|
149
|
+
reassigns a property. Save one with the editor before the first run.
|
|
150
|
+
`changed` names the properties the theme moves, `unchanged` the ones it
|
|
151
|
+
leaves, and `aliasedTo` the design token each reassigned property resolves
|
|
152
|
+
to under it.
|
|
153
|
+
- `interaction`: the interactive part's role and its cases. `applicable:
|
|
154
|
+
false` for a component with no interactive role.
|
|
155
|
+
- `sketch`: a sketch style id and the fill and stroke each reserved-class part
|
|
156
|
+
resolves to under it. `applicable: false` only for a component
|
|
157
|
+
`references/sketch-mode.md` exempts.
|
|
158
|
+
|
|
63
159
|
## Running vitest yourself
|
|
64
160
|
|
|
65
161
|
`check-component --tests` covers the shipped path. Run vitest directly only
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,77 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.77.0 — A consumer component runs the same suites
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **A consumer component's contract reaches the Playwright suites.**
|
|
8
|
+
`live-tokens.testing.ts` has carried a `contractsModule` setting since
|
|
9
|
+
0.75.0, naming a module that exports the `ComponentContract[]` a project
|
|
10
|
+
adds to the shipped list, and nothing documented it. A consumer following
|
|
11
|
+
live-tokens-create-component reached the Verification step with the eight
|
|
12
|
+
component suites reported as incomplete and the skill giving no way to
|
|
13
|
+
complete them. The skill now names the contract as a deliverable beside the
|
|
14
|
+
registration, and `references/contract-tests.md` shows the setting, a
|
|
15
|
+
complete contract for the skill's own example, and what each obligation
|
|
16
|
+
needs from the project. `src/testing/contracts/contracts.test.ts` covers the
|
|
17
|
+
loader.
|
|
18
|
+
- **`contract-missing` is its own finding.** A run narrowed to a component
|
|
19
|
+
with no contract used to throw from the Playwright config, so the runner
|
|
20
|
+
saw no report and surfaced a `tests-setup` finding holding a tail of
|
|
21
|
+
stderr. The editor suite now emits one failing test tagged
|
|
22
|
+
`contract-missing`, the JSON report carries it, and the runner maps it like
|
|
23
|
+
any other violation: one finding at `live-tokens.testing.ts` that names the
|
|
24
|
+
setting and lists the declared ids, with the `coverage` fix slug. The render
|
|
25
|
+
and alias suites still run for that component, and the failed rule explains
|
|
26
|
+
its seven unrun obligations, so reconciliation adds nothing on top.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- **The `create` template installs the test tooling.** `@playwright/test`,
|
|
31
|
+
`vitest`, and `happy-dom` are devDependencies of a scaffolded project, and
|
|
32
|
+
the scaffold ships `live-tokens.testing.ts` naming `src/registerComponents.ts`
|
|
33
|
+
and `tests/contracts.ts`, both present and empty, with `main.ts` importing
|
|
34
|
+
the registration module before boot. `npm run test:design` therefore works
|
|
35
|
+
once `npx playwright install chromium` has run, and the create-component
|
|
36
|
+
skill lands its Registration and contract in files that already exist. The
|
|
37
|
+
three stay optional peers of the package. The create smoke test asserts the
|
|
38
|
+
tooling resolves in a scaffold from the packed tarball.
|
|
39
|
+
|
|
40
|
+
## 0.76.0 — The skills directory matches the release
|
|
41
|
+
|
|
42
|
+
### Changed (breaking)
|
|
43
|
+
|
|
44
|
+
- **`setup-claude --force` now makes `.claude/skills/` match the release.** It
|
|
45
|
+
replaced each bundled skill by merging over it, which left two kinds of stale
|
|
46
|
+
text behind: a skill a release renamed kept its old directory, where it
|
|
47
|
+
shadowed the replacement, and a reference file a release dropped survived
|
|
48
|
+
inside a skill that was otherwise current. Both are invisible until a model
|
|
49
|
+
reads them. A forced run now replaces each bundled directory outright and
|
|
50
|
+
removes any `live-tokens-` skill the release no longer ships, naming each one
|
|
51
|
+
it removed. A skill under any other name is the project's own and is never
|
|
52
|
+
touched, and an unforced run still deletes nothing. 0.75.0's rename of
|
|
53
|
+
`live-tokens-build-page` to `live-tokens-create-page` is the case that
|
|
54
|
+
prompted this; the changelog then told a reader to delete the directory by
|
|
55
|
+
hand.
|
|
56
|
+
|
|
57
|
+
Before a forced run, copy any bundled skill you edited in place to a name of
|
|
58
|
+
your own. A `live-tokens-` directory this release does not ship is deleted
|
|
59
|
+
without a prompt, and one it does ship is replaced rather than merged.
|
|
60
|
+
|
|
61
|
+
`setup-claude` moves out of `bin/cli.mjs` into `bin/setup-claude.mjs` as
|
|
62
|
+
`runSetupClaude` and `formatSetupResult`, matching `create.mjs`, and gains
|
|
63
|
+
`bin/setup-claude.test.ts`.
|
|
64
|
+
|
|
65
|
+
### Changed
|
|
66
|
+
|
|
67
|
+
- **The `create` template installs the skills from `postinstall` and gitignores
|
|
68
|
+
them.** A scaffolded project no longer commits nine skill directories the
|
|
69
|
+
package owns, so a version bump stops showing twenty changed files that
|
|
70
|
+
nobody wrote. The skills instead refresh on every `npm install`. The
|
|
71
|
+
postinstall ends in `|| exit 0`: `setup-claude` refuses to run on Windows,
|
|
72
|
+
and a skills copy must never fail an install. A project created before this
|
|
73
|
+
release adds the two lines itself; the README carries them.
|
|
74
|
+
|
|
3
75
|
## 0.75.0 — A component passes when the tests pass
|
|
4
76
|
|
|
5
77
|
### Added
|
package/README.md
CHANGED
|
@@ -322,7 +322,7 @@ npx @motion-proto/live-tokens <command>
|
|
|
322
322
|
| Command | What it does |
|
|
323
323
|
|---|---|
|
|
324
324
|
| `create <dir> [--force]` | Scaffold a new Svelte + Vite app wired up with live-tokens. |
|
|
325
|
-
| `setup-claude [--force]` | Install the bundled Claude Code skills into `./.claude/skills
|
|
325
|
+
| `setup-claude [--force]` | Install the bundled Claude Code skills into `./.claude/skills/`; `--force` makes the directory match this release. |
|
|
326
326
|
| `components [id] [--json]` | List every component the project has, shipped and its own, with the props each takes; with an id, its props, variants, tokens, and defaults. |
|
|
327
327
|
| `tokens [--scale <name>] [--json]` | List every design token the project's `tokens.css` declares, by scale, with its value. |
|
|
328
328
|
| `report [--json]` | The project as facts: pending migrations, tokens each component reads, which page renders which component, and both checkers' findings by rule. Always exits 0. |
|
|
@@ -346,10 +346,20 @@ The package bundles nine Claude Code skills. They encode the conventions this RE
|
|
|
346
346
|
npx @motion-proto/live-tokens setup-claude
|
|
347
347
|
```
|
|
348
348
|
|
|
349
|
-
This copies every bundled skill into `./.claude/skills/` in the current directory
|
|
349
|
+
This copies every bundled skill into `./.claude/skills/` in the current directory, leaving any that already exist. macOS and Linux only.
|
|
350
350
|
|
|
351
|
-
|
|
352
|
-
|
|
351
|
+
Re-run it with `--force` after upgrading. That makes the directory match the release: each bundled skill is replaced outright, so a reference file the release dropped goes with it, and a `live-tokens-` skill the release no longer ships is removed. A skill under any other name is the project's own and is never touched. Without `--force` nothing is deleted and an existing skill is left alone, so a renamed skill keeps shadowing its replacement until a forced run clears it.
|
|
352
|
+
|
|
353
|
+
A project created with `create` runs the forced form from `postinstall` and gitignores the copy, so the skills track the installed version with nothing to commit. To do the same in an existing project:
|
|
354
|
+
|
|
355
|
+
```jsonc
|
|
356
|
+
// package.json
|
|
357
|
+
"postinstall": "live-tokens setup-claude --force || exit 0"
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
```
|
|
361
|
+
# .gitignore
|
|
362
|
+
.claude/skills/live-tokens-*/
|
|
353
363
|
```
|
|
354
364
|
|
|
355
365
|
### `live-tokens-pick-component`
|
package/bin/check-component.mjs
CHANGED
|
@@ -55,6 +55,7 @@ export const COMPONENT_RULES = {
|
|
|
55
55
|
'contract-interaction': 'error',
|
|
56
56
|
'contract-listed': 'error',
|
|
57
57
|
'contract-sketch': 'error',
|
|
58
|
+
'contract-missing': 'error',
|
|
58
59
|
'tests-not-installed': 'error',
|
|
59
60
|
'tests-setup': 'error',
|
|
60
61
|
'tests-incomplete': 'error',
|
|
@@ -94,6 +95,7 @@ export const COMPONENT_RULE_FIX = {
|
|
|
94
95
|
'contract-registry': 'registration',
|
|
95
96
|
'contract-listed': 'registration',
|
|
96
97
|
'contract-sketch': 'sketch',
|
|
98
|
+
'contract-missing': 'coverage',
|
|
97
99
|
'tests-not-installed': 'tooling',
|
|
98
100
|
'tests-setup': 'tooling',
|
|
99
101
|
'tests-incomplete': 'coverage',
|
package/bin/cli.mjs
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// save-theme <name> Compose the live state into themes/<slug>.json and open it.
|
|
15
15
|
// migrate [...] Reconcile tokens.css, the data tree, and route references.
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import { writeSync } from 'node:fs';
|
|
18
18
|
import { dirname, join, resolve } from 'node:path';
|
|
19
19
|
import { fileURLToPath } from 'node:url';
|
|
20
20
|
import process from 'node:process';
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
} from './migrate.mjs';
|
|
41
41
|
import { runMigrateRoutes, formatRouteResult } from './migrate-routes.mjs';
|
|
42
42
|
import { runCreate, formatCreateResult } from './create.mjs';
|
|
43
|
+
import { runSetupClaude, formatSetupResult } from './setup-claude.mjs';
|
|
43
44
|
import { runSetColors, formatSetColorsResult } from './set-colors.mjs';
|
|
44
45
|
import { runSetGeometry, formatSetGeometryResult } from './set-geometry.mjs';
|
|
45
46
|
import { runSetType, formatSetTypeResult } from './set-type.mjs';
|
|
@@ -407,61 +408,14 @@ if (process.platform === 'win32') {
|
|
|
407
408
|
fail('setup-claude is macOS/Linux only.');
|
|
408
409
|
}
|
|
409
410
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
);
|
|
421
|
-
|
|
422
|
-
if (skills.length === 0) {
|
|
423
|
-
fail('No bundled skills to install.');
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
const destSkills = join(process.cwd(), '.claude', 'skills');
|
|
427
|
-
mkdirSync(destSkills, { recursive: true });
|
|
428
|
-
|
|
429
|
-
let installed = 0;
|
|
430
|
-
let skipped = 0;
|
|
431
|
-
for (const skill of skills) {
|
|
432
|
-
const src = join(srcSkills, skill);
|
|
433
|
-
const dest = join(destSkills, skill);
|
|
434
|
-
if (existsSync(dest) && !force) {
|
|
435
|
-
console.log(` skip ${skill} (already exists; pass --force to overwrite)`);
|
|
436
|
-
skipped++;
|
|
437
|
-
continue;
|
|
438
|
-
}
|
|
439
|
-
cpSync(src, dest, { recursive: true });
|
|
440
|
-
console.log(` ok ${skill}`);
|
|
441
|
-
installed++;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
console.log(`\n${installed} installed, ${skipped} skipped, in ${destSkills}`);
|
|
445
|
-
|
|
446
|
-
const SAMPLE_PROMPTS = {
|
|
447
|
-
'live-tokens-create-page': 'build a pricing page using live-tokens components',
|
|
448
|
-
'live-tokens-pick-component': "what's the difference between TabBar and SegmentedControl?",
|
|
449
|
-
'live-tokens-create-component': 'author a new Toggle component for my live-tokens project',
|
|
450
|
-
'live-tokens-create-theme': 'make me a bright and cheerful theme',
|
|
451
|
-
'live-tokens-set-colors': 'give me a cooler palette, same fonts',
|
|
452
|
-
'live-tokens-set-type': 'pair some fonts for this theme',
|
|
453
|
-
'live-tokens-set-geometry': 'make the buttons pill shaped',
|
|
454
|
-
'live-tokens-fix-findings': 'make check:design pass',
|
|
455
|
-
'live-tokens-check-compliance': 'check this project against the design system',
|
|
456
|
-
};
|
|
457
|
-
|
|
458
|
-
const installedSamples = skills
|
|
459
|
-
.map((s) => SAMPLE_PROMPTS[s] && [s, SAMPLE_PROMPTS[s]])
|
|
460
|
-
.filter(Boolean);
|
|
461
|
-
|
|
462
|
-
if (installedSamples.length > 0) {
|
|
463
|
-
console.log(`\nIn Claude Code, prompts like these auto-trigger the matching skill:`);
|
|
464
|
-
for (const [skill, prompt] of installedSamples) {
|
|
465
|
-
console.log(` "${prompt}"\n ${skill}`);
|
|
466
|
-
}
|
|
411
|
+
try {
|
|
412
|
+
const result = runSetupClaude({
|
|
413
|
+
pkgRoot,
|
|
414
|
+
cwd: process.cwd(),
|
|
415
|
+
force: rest.includes('--force'),
|
|
416
|
+
});
|
|
417
|
+
console.log(formatSetupResult(result));
|
|
418
|
+
process.exit(0);
|
|
419
|
+
} catch (err) {
|
|
420
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
467
421
|
}
|
package/bin/contractRunner.mjs
CHANGED
|
@@ -552,10 +552,15 @@ export function readPlaywrightTests(report) {
|
|
|
552
552
|
* the shipped `.contract.ts` suite (absent from the tarball, and Playwright
|
|
553
553
|
* reports no assertion location for it once compiled — see the plan's Wave
|
|
554
554
|
* 3b/4 notes). `contract-alias`/`persist`/`theme` name the shipped config;
|
|
555
|
-
* `contract-listed` names the editor; render/preview/sketch name the runtime
|
|
555
|
+
* `contract-listed` names the editor; render/preview/sketch name the runtime;
|
|
556
|
+
* `contract-missing` names the settings file the contract module is declared in.
|
|
556
557
|
*/
|
|
557
558
|
export function artifactForContractRule(root, sourceDataDir, rule, componentId, token) {
|
|
558
559
|
if (!componentId) return { file: 'package.json', line: 1 };
|
|
560
|
+
if (rule === 'contract-missing') {
|
|
561
|
+
const settings = settingsFilePath(root);
|
|
562
|
+
return { file: settings ? relative(root, settings) : 'package.json', line: 1 };
|
|
563
|
+
}
|
|
559
564
|
const paths = resolveComponentPaths(componentId, root);
|
|
560
565
|
if (rule === 'contract-alias' || rule === 'contract-persist' || rule === 'contract-theme') {
|
|
561
566
|
const target = componentConfigPath(sourceDataDir, componentId);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Installs the bundled Claude Code skills into a project's ./.claude/skills/.
|
|
2
|
+
//
|
|
3
|
+
// Claude Code discovers skills only under .claude/skills/, never inside
|
|
4
|
+
// node_modules, so the package's own copy has to be duplicated into each
|
|
5
|
+
// project that wants them. That makes this command the only thing keeping the
|
|
6
|
+
// two trees in step, and `--force` has to mean "make the destination match this
|
|
7
|
+
// release" rather than "write over the files I happen to ship today":
|
|
8
|
+
//
|
|
9
|
+
// - a skill this release renamed leaves its old directory behind, where it
|
|
10
|
+
// shadows the new one with stale instructions;
|
|
11
|
+
// - a reference file this release dropped survives inside a skill that is
|
|
12
|
+
// otherwise current, because cpSync merges into an existing directory.
|
|
13
|
+
//
|
|
14
|
+
// Both are invisible until a model reads the stale text, so --force replaces
|
|
15
|
+
// each directory outright and prunes the ones this release no longer ships.
|
|
16
|
+
|
|
17
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
|
|
20
|
+
// The package owns this prefix in a project's skills directory. A skill without
|
|
21
|
+
// it is the project's own and is never touched, whatever the flags say.
|
|
22
|
+
const SKILL_PREFIX = 'live-tokens-';
|
|
23
|
+
|
|
24
|
+
const SAMPLE_PROMPTS = {
|
|
25
|
+
'live-tokens-create-page': 'build a pricing page using live-tokens components',
|
|
26
|
+
'live-tokens-pick-component': "what's the difference between TabBar and SegmentedControl?",
|
|
27
|
+
'live-tokens-create-component': 'author a new Toggle component for my live-tokens project',
|
|
28
|
+
'live-tokens-create-theme': 'make me a bright and cheerful theme',
|
|
29
|
+
'live-tokens-set-colors': 'give me a cooler palette, same fonts',
|
|
30
|
+
'live-tokens-set-type': 'pair some fonts for this theme',
|
|
31
|
+
'live-tokens-set-geometry': 'make the buttons pill shaped',
|
|
32
|
+
'live-tokens-fix-findings': 'make check:design pass',
|
|
33
|
+
'live-tokens-check-compliance': 'check this project against the design system',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function directoriesIn(dir) {
|
|
37
|
+
if (!existsSync(dir)) return [];
|
|
38
|
+
return readdirSync(dir).filter((name) => statSync(join(dir, name)).isDirectory());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function runSetupClaude({ pkgRoot, cwd, force = false }) {
|
|
42
|
+
const srcSkills = join(pkgRoot, '.claude', 'skills');
|
|
43
|
+
|
|
44
|
+
if (!existsSync(srcSkills)) {
|
|
45
|
+
throw new Error(`No bundled skills found at ${srcSkills}. Is the package installed correctly?`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const shipped = directoriesIn(srcSkills);
|
|
49
|
+
if (shipped.length === 0) {
|
|
50
|
+
throw new Error('No bundled skills to install.');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const destSkills = join(cwd, '.claude', 'skills');
|
|
54
|
+
mkdirSync(destSkills, { recursive: true });
|
|
55
|
+
|
|
56
|
+
const installed = [];
|
|
57
|
+
const skipped = [];
|
|
58
|
+
const removed = [];
|
|
59
|
+
|
|
60
|
+
for (const skill of shipped) {
|
|
61
|
+
const dest = join(destSkills, skill);
|
|
62
|
+
if (existsSync(dest) && !force) {
|
|
63
|
+
skipped.push(skill);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// Replace rather than merge: a file this release dropped must not survive
|
|
67
|
+
// inside a directory that otherwise looks current.
|
|
68
|
+
rmSync(dest, { recursive: true, force: true });
|
|
69
|
+
cpSync(join(srcSkills, skill), dest, { recursive: true });
|
|
70
|
+
installed.push(skill);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (force) {
|
|
74
|
+
for (const name of directoriesIn(destSkills)) {
|
|
75
|
+
if (!name.startsWith(SKILL_PREFIX) || shipped.includes(name)) continue;
|
|
76
|
+
rmSync(join(destSkills, name), { recursive: true, force: true });
|
|
77
|
+
removed.push(name);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { installed, skipped, removed, destSkills, shipped };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function formatSetupResult({ installed, skipped, removed, destSkills, shipped }) {
|
|
85
|
+
const lines = [];
|
|
86
|
+
|
|
87
|
+
for (const skill of shipped) {
|
|
88
|
+
if (installed.includes(skill)) lines.push(` ok ${skill}`);
|
|
89
|
+
else lines.push(` skip ${skill} (already exists; pass --force to overwrite)`);
|
|
90
|
+
}
|
|
91
|
+
for (const skill of removed) {
|
|
92
|
+
lines.push(` gone ${skill} (this release no longer ships it; removed)`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const counts = [`${installed.length} installed`, `${skipped.length} skipped`];
|
|
96
|
+
if (removed.length > 0) counts.push(`${removed.length} removed`);
|
|
97
|
+
lines.push(`\n${counts.join(', ')}, in ${destSkills}`);
|
|
98
|
+
|
|
99
|
+
if (skipped.length > 0 && removed.length === 0) {
|
|
100
|
+
lines.push(`\nRe-run with --force to bring every skill up to this release.`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const samples = shipped.map((s) => SAMPLE_PROMPTS[s] && [s, SAMPLE_PROMPTS[s]]).filter(Boolean);
|
|
104
|
+
if (samples.length > 0) {
|
|
105
|
+
lines.push(`\nIn Claude Code, prompts like these auto-trigger the matching skill:`);
|
|
106
|
+
for (const [skill, prompt] of samples) lines.push(` "${prompt}"\n ${skill}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return lines.join('\n');
|
|
110
|
+
}
|