@motion-proto/live-tokens 0.33.1 → 0.35.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-build-page/SKILL.md +3 -3
- package/.claude/skills/live-tokens-create-component/SKILL.md +3 -3
- package/CHANGELOG.md +59 -0
- package/README.md +8 -6
- package/bin/cli.mjs +19 -5
- package/bin/migrate-routes.mjs +179 -0
- package/dist-plugin/{chunk-MJO4T3CM.js → chunk-D77VD4Z6.js} +55 -0
- package/dist-plugin/index.cjs +27 -0
- package/dist-plugin/index.d.cts +1 -0
- package/dist-plugin/index.d.ts +1 -0
- package/dist-plugin/index.js +17 -1
- package/dist-plugin/tokensCssMigrations/index.cjs +59 -0
- package/dist-plugin/tokensCssMigrations/index.d.cts +55 -1
- package/dist-plugin/tokensCssMigrations/index.d.ts +55 -1
- package/dist-plugin/tokensCssMigrations/index.js +9 -1
- package/package.json +3 -2
- package/src/editor/core/cssVarSync.ts +3 -3
- package/src/editor/core/routing/ownedRoutes.ts +11 -0
- package/src/editor/core/routing/router.ts +2 -1
- package/src/editor/docs/Docs.svelte +3 -2
- package/src/editor/docs/content/creating-components.md +2 -2
- package/src/editor/docs/content/getting-started.md +2 -2
- package/src/editor/docs/content.generated.ts +2 -2
- package/src/editor/overlay/LiveEditorOverlay.svelte +8 -5
- package/src/editor/overlay/LiveTokensRouter.svelte +12 -9
- package/src/editor/pages/Editor.svelte +2 -1
- package/src/editor/ui/EditorViewSwitcher.svelte +7 -4
- package/template/README.md +5 -4
- package/template/src/App.svelte +4 -3
- package/template/src/pages/Home.svelte +2 -2
|
@@ -8,7 +8,7 @@ description: Apply the @motion-proto/live-tokens project conventions when buildi
|
|
|
8
8
|
Two rules above all else:
|
|
9
9
|
|
|
10
10
|
1. **Use a shipped component if one fits.** Import from `@motion-proto/live-tokens/components/<Name>.svelte`. See [[live-tokens-pick-component]] for the catalogue and the confusing-pair decisions. Author custom markup only when nothing fits, and then consider [[live-tokens-create-component]] so the new piece is editable too.
|
|
11
|
-
2. **Use theme tokens for every value.** Every color, spacing, radius, font-size, and font-family in page CSS is a `var(--token-*)`. No hex literals. No pixel literals. A change in `/editor` should repaint your page.
|
|
11
|
+
2. **Use theme tokens for every value.** Every color, spacing, radius, font-size, and font-family in page CSS is a `var(--token-*)`. No hex literals. No pixel literals. A change in `/live-tokens/editor` should repaint your page.
|
|
12
12
|
|
|
13
13
|
## Layout
|
|
14
14
|
|
|
@@ -28,10 +28,10 @@ To place children at specific page-column positions, span the parent grid (`grid
|
|
|
28
28
|
|
|
29
29
|
- Hex or pixel literals in page CSS.
|
|
30
30
|
- Hardcoded column counts (`repeat(10, 1fr)`). Use `repeat(var(--columns-count), 1fr)`.
|
|
31
|
-
- Utility classes overriding shipped components. Extend via the `/components` editor instead.
|
|
31
|
+
- Utility classes overriding shipped components. Extend via the `/live-tokens/components` editor instead.
|
|
32
32
|
- Deep imports from `node_modules/@motion-proto/live-tokens/src/...`. Use public entry points only.
|
|
33
33
|
- Mounting `Editor` or `ComponentEditorPage` outside their dedicated routes.
|
|
34
34
|
|
|
35
35
|
## Verify
|
|
36
36
|
|
|
37
|
-
In dev: change a colour in `/editor` and confirm your page repaints (proves token usage). The overlay's "Page Source" button on the new route opens the page in VS Code (proves the route's `source`). `ColumnsOverlay` (Cmd+G) shows content sitting inside `--columns-max-width`.
|
|
37
|
+
In dev: change a colour in `/live-tokens/editor` and confirm your page repaints (proves token usage). The overlay's "Page Source" button on the new route opens the page in VS Code (proves the route's `source`). `ColumnsOverlay` (Cmd+G) shows content sitting inside `--columns-max-width`.
|
|
@@ -5,7 +5,7 @@ description: Author a brand-new editable component for a @motion-proto/live-toke
|
|
|
5
5
|
|
|
6
6
|
# Authoring a component for a live-tokens project
|
|
7
7
|
|
|
8
|
-
This skill teaches you how to add a new editable component to a project that consumes `@motion-proto/live-tokens`. The end state: a runtime Svelte file, an editor Svelte file, one `registerComponent()` call, and a `/components` page entry under the **CUSTOM** group with full token editing, linked-block sharing, and persistence.
|
|
8
|
+
This skill teaches you how to add a new editable component to a project that consumes `@motion-proto/live-tokens`. The end state: a runtime Svelte file, an editor Svelte file, one `registerComponent()` call, and a `/live-tokens/components` page entry under the **CUSTOM** group with full token editing, linked-block sharing, and persistence.
|
|
9
9
|
|
|
10
10
|
## Worked examples ship inside the package
|
|
11
11
|
|
|
@@ -44,7 +44,7 @@ For pattern reference, read any shipped component's source directly from the con
|
|
|
44
44
|
```
|
|
45
45
|
The schema side-effect happens inside `registerComponent` (which `bootLiveTokens` calls for you), so you don't call `registerComponentSchema` separately. **Do not place a standalone `registerComponent(...)` *before* `bootLiveTokens`** — that registers before the editor's init hooks run, which is the wrong window and can leave editor changes disconnected from the live page. Only call `registerComponent` directly if your app mounts manually (no `bootLiveTokens`), in which case call it before `mount(App, ...)`.
|
|
46
46
|
4. **Tell the picker** — open `.claude/skills/live-tokens-pick-component/SKILL.md` and add your new component to the **Catalogue** line under the family it belongs to (Action / Input / Selection / Containers / Messaging / Display). If it's confusable with an existing component (a second selection control, a competing container), add a row to that family's decision table explaining the use-case it owns. Without this step, the component exists but [[live-tokens-pick-component]] can't recommend it when a user asks "which component should I use?" — the same rule applies whether the component is first-party (update the picker shipped in this package) or consumer-authored (update the local copy at `.claude/skills/live-tokens-pick-component/SKILL.md` that `setup-claude` placed in your project).
|
|
47
|
-
5. **Verify** — open `/components` and run the verification checklist at the bottom of this file.
|
|
47
|
+
5. **Verify** — open `/live-tokens/components` and run the verification checklist at the bottom of this file.
|
|
48
48
|
|
|
49
49
|
## Token discipline
|
|
50
50
|
|
|
@@ -559,7 +559,7 @@ A new first-party component is auto-covered the moment it lands in `builtInRegis
|
|
|
559
559
|
|
|
560
560
|
**If your component declares `intrinsics`, the intrinsics contract test covers it too.** `src/editor/component-editor/intrinsicsContract.test.ts` iterates every entry with an `intrinsics` array and asserts, per (intrinsic, variant), that the runtime `:global(:root)` declares a default, the default is one of the spec's `values`, and the editor's `default` equals the runtime default. This is what would have caught a getter defaulting to `center` while `:global(:root)` says `start`. Same auto-coverage rule: declare `intrinsics` on the registry entry and the test picks it up.
|
|
561
561
|
|
|
562
|
-
Finally navigate to `/components` and confirm the runtime behaviours no static check can see:
|
|
562
|
+
Finally navigate to `/live-tokens/components` and confirm the runtime behaviours no static check can see:
|
|
563
563
|
|
|
564
564
|
- [ ] The new component appears in the nav rail under the **CUSTOM** group (system entries above, custom below the labeled divider).
|
|
565
565
|
- [ ] Token rows render. Color pickers, radius selectors, font selectors all work.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,64 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.35.0 — Dev routes moved to a reserved `/live-tokens/*` namespace
|
|
4
|
+
|
|
5
|
+
### Changed (breaking)
|
|
6
|
+
|
|
7
|
+
- **The package's dev-only routes moved under a reserved `/live-tokens/*`
|
|
8
|
+
namespace:** `/editor` → `/live-tokens/editor`, `/components` →
|
|
9
|
+
`/live-tokens/components`, `/docs` → `/live-tokens/docs`. These routes are
|
|
10
|
+
`import.meta.env.DEV`-only and never appear in production, so the longer paths
|
|
11
|
+
cost nothing where users actually see URLs. Reserving a namespace means a
|
|
12
|
+
consumer's own `/docs` or `/components` page no longer collides with a package
|
|
13
|
+
route, and any owned route added in a future release stays inside the namespace
|
|
14
|
+
(no surprise collisions on a version bump). Relocate or disable any of them via
|
|
15
|
+
the `editorRoutes` prop exactly as before.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **A consumer page at `/docs` or `/components` no longer crashes the app.** The
|
|
20
|
+
package auto-injected nav entries at those paths; a consumer page at the same
|
|
21
|
+
path produced a duplicate key in the overlay's keyed nav list and threw an
|
|
22
|
+
uncaught error, and silently shadowed the consumer's page at dispatch. With the
|
|
23
|
+
reserved namespace the collision cannot occur, so `editorRoutes.docs = false`
|
|
24
|
+
is no longer needed to dodge it.
|
|
25
|
+
- **`editorRoutes.components` relocation now also moves the overlay's
|
|
26
|
+
components-view pairing**, which previously compared a hardcoded `/components`.
|
|
27
|
+
|
|
28
|
+
### Migrating
|
|
29
|
+
|
|
30
|
+
- **`npx live-tokens migrate` now flags hardcoded route references.** If your
|
|
31
|
+
source navigates to the old paths (e.g. `navigate('/editor')` from the old
|
|
32
|
+
scaffold, or `<a href="/components">`), `migrate` reports each one with its
|
|
33
|
+
file, line, and suggested `/live-tokens/*` replacement. Add `--write` to
|
|
34
|
+
rewrite the unambiguous ones automatically. `/docs` is never auto-rewritten
|
|
35
|
+
(you likely own that route), and any path you declare in `pages` or relocate
|
|
36
|
+
via `editorRoutes` is left for manual review. The editor stays reachable via
|
|
37
|
+
the dev overlay regardless, so this only affects hardcoded shortcut links.
|
|
38
|
+
|
|
39
|
+
## 0.34.0 — Token-as-API contract guardrail; opt-in autoMigrate
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- **Token names are now a versioned API contract, with a guardrail.** Each
|
|
44
|
+
`tokens.css` migration declares `kind: 'additive' | 'breaking'`. A new
|
|
45
|
+
`check:token-contract` (wired into `prepublishOnly`, plus
|
|
46
|
+
`tokensCssMigrations/contract.test.ts`) verifies behaviorally that an additive
|
|
47
|
+
migration never removes or renames a token (catches a breaking change shipped
|
|
48
|
+
as backward-compatible), and gates breaking migrations on a major bump from
|
|
49
|
+
1.0.0 (pre-1.0 it warns). See TOKENS.md and RELEASING.md.
|
|
50
|
+
- **`themeFileApi({ autoMigrate: true })`.** Opt-in: the dev server applies
|
|
51
|
+
pending **additive** token migrations to your `tokens.css` at startup and
|
|
52
|
+
writes the file (shown in git), so it stays current with the package without a
|
|
53
|
+
manual step. Breaking migrations are never auto-applied. Off by default, which
|
|
54
|
+
preserves the invariant that the plugin never writes `tokensCssPath` unless you
|
|
55
|
+
enable it.
|
|
56
|
+
|
|
57
|
+
### Docs
|
|
58
|
+
|
|
59
|
+
- **`TOKENS.md`** gained a plain-language section on how token changes are
|
|
60
|
+
versioned (additive vs breaking, what an upgrade can and cannot change).
|
|
61
|
+
|
|
3
62
|
## 0.33.1 — Ship the changelog in the package
|
|
4
63
|
|
|
5
64
|
### Fixed
|
package/README.md
CHANGED
|
@@ -8,8 +8,8 @@ A foundational design system for quickly styling and building Svelte + Vite micr
|
|
|
8
8
|
|
|
9
9
|
- **Real-time token editing.** Pick a color, drag a hue slider, retype a font size — the page repaints on every input event via CSS-variable writes. No reload, no save-and-refresh, no build step. Works across colors, typography, spacing, radii, shadows, motion, palettes, and gradients.
|
|
10
10
|
- **Real-time component editing.** Each of ~24 shipped Svelte components (Button, Input, Card, Dialog, Badge, Callout, Table, Tooltip, Toggle, TabBar, SegmentedControl, RadioButton, MenuSelect, ProgressBar, CornerBadge, SectionDivider, CollapsibleSection, Notification, Image, ImageLightbox, CodeSnippet, SideNavigation, and more) declares its own design-token aliases in a `:global(:root)` block. Rewire any alias from a per-component picker and see that component update everywhere it's used — live, on your real pages, not in a Storybook sandbox.
|
|
11
|
-
- **Theme editor** (`/editor` route, dev-only) — the home of real-time token editing. Save themes to disk as JSON, promote one to "production" to bake it into a static `tokens.css` for the build.
|
|
12
|
-
- **Per-component editor** (`/components` route, dev-only) — the home of real-time component-alias editing. Pick token aliases per component without writing CSS.
|
|
11
|
+
- **Theme editor** (`/live-tokens/editor` route, dev-only) — the home of real-time token editing. Save themes to disk as JSON, promote one to "production" to bake it into a static `tokens.css` for the build.
|
|
12
|
+
- **Per-component editor** (`/live-tokens/components` route, dev-only) — the home of real-time component-alias editing. Pick token aliases per component without writing CSS.
|
|
13
13
|
- **Live editor overlay** — pins to the top-right of every dev page. Opens the editor in a side panel or floating window so you edit *on the page you're styling*, not in a separate tab. Includes a "Page Source" button that opens the current page's `.svelte` file in VS Code.
|
|
14
14
|
- **Manifests** — a manifest captures a whole site configuration as one portable artifact: the theme in one slot, every component in its own slot, each holding either the shipped default or a custom file. Export it as a bundle and import it into another project to restore the full styling in one step.
|
|
15
15
|
- **Vite plugin** — hosts the `/api/live-tokens/{themes,component-configs,manifests}/*` routes that persist your edits to disk as you make them. The single namespace keeps live-tokens' routes from colliding with anything your app serves under `/api`.
|
|
@@ -122,7 +122,7 @@ bootLiveTokens(App, '#app', {
|
|
|
122
122
|
```
|
|
123
123
|
|
|
124
124
|
`<LiveTokensRouter>` owns the dev overlay (`<LiveEditorOverlay>` +
|
|
125
|
-
`<ColumnsOverlay>`), the editor routes (`/editor`, `/components`, `/docs`), the
|
|
125
|
+
`<ColumnsOverlay>`), the editor routes (`/live-tokens/editor`, `/live-tokens/components`, `/live-tokens/docs`), the
|
|
126
126
|
in-app link-click interception, and the nav-rail/page-source plumbing the
|
|
127
127
|
overlay needs. Each entry in `pages` is one of your routes; entries with a
|
|
128
128
|
`label` appear in the overlay's nav rail. Pass pages as `lazy: () => import('./Page.svelte')`
|
|
@@ -307,7 +307,7 @@ bootLiveTokens(App, '#app', {
|
|
|
307
307
|
|
|
308
308
|
(`bootLiveTokens` calls `registerComponent` internally for each entry, gated on `import.meta.env.DEV` so the registration tree-shakes out of production builds. Call `registerComponent` directly if you need finer control over timing.)
|
|
309
309
|
|
|
310
|
-
The component appears in the `/components` page under a **CUSTOM** group in the nav rail. Token rows, linked-block sharing, per-component config persistence, and reset-to-default work identically to the built-in set. All imports must come from `@motion-proto/live-tokens` or `@motion-proto/live-tokens/component-editor`; never deep-import from `src/`.
|
|
310
|
+
The component appears in the `/live-tokens/components` page under a **CUSTOM** group in the nav rail. Token rows, linked-block sharing, per-component config persistence, and reset-to-default work identically to the built-in set. All imports must come from `@motion-proto/live-tokens` or `@motion-proto/live-tokens/component-editor`; never deep-import from `src/`.
|
|
311
311
|
|
|
312
312
|
## Claude Code skills
|
|
313
313
|
|
|
@@ -345,13 +345,13 @@ It enforces the file layout, `:global(:root)` block, token-suffix vocabulary, th
|
|
|
345
345
|
|
|
346
346
|
## How the editor ships changes to prod
|
|
347
347
|
|
|
348
|
-
1. Edit in `/editor` or `/components`. Saves write to `<dataDir>/themes/{name}.json` and `<dataDir>/component-configs/{comp}/{name}.json`.
|
|
348
|
+
1. Edit in `/live-tokens/editor` or `/live-tokens/components`. Saves write to `<dataDir>/themes/{name}.json` and `<dataDir>/component-configs/{comp}/{name}.json`.
|
|
349
349
|
2. Promote a theme to "production." Its variables are written into `tokens.generated.css` next to your authored `tokens.css`.
|
|
350
350
|
3. `npm run build` bundles both as plain CSS. No editor code, no JSON lookups, no dev surfaces ship to prod.
|
|
351
351
|
|
|
352
352
|
## File ownership — what the plugin writes
|
|
353
353
|
|
|
354
|
-
Knowing which files the plugin touches matters when upgrading the package or working in a repo you don't want overwritten.
|
|
354
|
+
Knowing which files the plugin touches matters when upgrading the package or working in a repo you don't want overwritten. For a plain-language version of how your saved look stays safe across upgrades while `tokens.css` holds the building blocks, see [TOKENS.md](./TOKENS.md).
|
|
355
355
|
|
|
356
356
|
**On `npm install` or `npm update`: nothing outside `node_modules/`.** No install hooks. Upgrading versions never touches your `src/live-tokens/data/`, or any file in `src/` outside it.
|
|
357
357
|
|
|
@@ -378,6 +378,8 @@ It never writes to your project root, your `src/` outside the data folder, or an
|
|
|
378
378
|
|
|
379
379
|
The developer-authored `tokens.css` itself is **never written** by the plugin — it holds defaults you're free to hand-edit. The editor's overrides land in the sidecar `tokens.generated.css`, which the package imports immediately after `tokens.css`.
|
|
380
380
|
|
|
381
|
+
The one exception is the opt-in `themeFileApi({ autoMigrate: true })` option. When enabled, the dev server applies pending **additive** token migrations (new token names only) to your `tokens.css` at startup and writes the file, so it stays current with the package as you upgrade. The change shows up in git for review. Breaking migrations (rename/remove) are never auto-applied; run `npx live-tokens migrate` for those during a deliberate upgrade. Off by default, so the "never written" rule holds unless you turn it on. See [TOKENS.md](./TOKENS.md).
|
|
382
|
+
|
|
381
383
|
## License
|
|
382
384
|
|
|
383
385
|
MIT. Originally extracted from [RuneGoblin](https://www.runegoblin.com/).
|
package/bin/cli.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
11
11
|
import process from 'node:process';
|
|
12
12
|
import { checkComponent, formatReport } from './check-component.mjs';
|
|
13
13
|
import { runMigrate, formatMigrateResult } from './migrate.mjs';
|
|
14
|
+
import { runMigrateRoutes, formatRouteResult } from './migrate-routes.mjs';
|
|
14
15
|
import { runCreate, formatCreateResult } from './create.mjs';
|
|
15
16
|
|
|
16
17
|
const USAGE = `Usage: npx @motion-proto/live-tokens <command> [options]
|
|
@@ -21,10 +22,15 @@ Commands:
|
|
|
21
22
|
setup-claude [--force] Install bundled Claude Code skills into ./.claude/skills/
|
|
22
23
|
check-component <id> Validate <id>'s runtime, editor, and registration
|
|
23
24
|
against the live-tokens-create-component contract
|
|
24
|
-
migrate [--check] [--tokens <path>]
|
|
25
|
-
Reconcile your
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
migrate [--check] [--write] [--tokens <path>]
|
|
26
|
+
Reconcile your project with the installed package:
|
|
27
|
+
applies additive tokens.css migrations (unless
|
|
28
|
+
--check), and reports source references to the
|
|
29
|
+
editor/components/docs routes that moved to
|
|
30
|
+
/live-tokens/* in 0.35.0. --write also rewrites the
|
|
31
|
+
unambiguous route references (never /docs). --check
|
|
32
|
+
reports without writing (exit 1 if token migrations
|
|
33
|
+
are pending; route findings are advisory).
|
|
28
34
|
`;
|
|
29
35
|
|
|
30
36
|
function fail(message, code = 1) {
|
|
@@ -67,13 +73,21 @@ if (command === 'check-component') {
|
|
|
67
73
|
|
|
68
74
|
if (command === 'migrate') {
|
|
69
75
|
const check = rest.includes('--check');
|
|
76
|
+
const write = rest.includes('--write');
|
|
70
77
|
const tokensIdx = rest.indexOf('--tokens');
|
|
71
78
|
const tokensArg = tokensIdx !== -1 ? rest[tokensIdx + 1] : undefined;
|
|
72
79
|
if (tokensIdx !== -1 && !tokensArg) fail(`--tokens requires a path`);
|
|
73
80
|
try {
|
|
74
81
|
const result = await runMigrate({ tokensArg, check });
|
|
75
82
|
console.log(formatMigrateResult(result, { check }));
|
|
76
|
-
|
|
83
|
+
|
|
84
|
+
// Route-reference pass: advisory by default, rewrites the unambiguous hits
|
|
85
|
+
// only with --write (and never under --check).
|
|
86
|
+
const routes = runMigrateRoutes({ root: process.cwd(), apply: write && !check });
|
|
87
|
+
const routeOut = formatRouteResult(routes, { check });
|
|
88
|
+
if (routeOut) console.log('\n' + routeOut);
|
|
89
|
+
|
|
90
|
+
// Route findings are advisory; only token migrations gate the exit code.
|
|
77
91
|
if (result.status === 'no-path') process.exit(1);
|
|
78
92
|
if (check && result.status === 'would-change') process.exit(1);
|
|
79
93
|
process.exit(0);
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// `live-tokens migrate` route-reference pass (0.35.0 namespace move).
|
|
2
|
+
//
|
|
3
|
+
// The package's dev-only routes moved to a reserved `/live-tokens/*` namespace,
|
|
4
|
+
// so consumer source that hardcoded `/editor`, `/components`, or `/docs` (e.g.
|
|
5
|
+
// `navigate('/editor')` from the old scaffold) now 404s. This scans the
|
|
6
|
+
// consumer's source and reports those references; with `apply`, it rewrites the
|
|
7
|
+
// unambiguous ones.
|
|
8
|
+
//
|
|
9
|
+
// Safety: `/docs` is never auto-rewritten (consumers commonly own a docs page),
|
|
10
|
+
// and any path the project declares as its own (a `pages` key) or manages via
|
|
11
|
+
// `editorRoutes` is left as an advisory. A reference is rewritten only when the
|
|
12
|
+
// package definitely owns that path.
|
|
13
|
+
|
|
14
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
15
|
+
import { extname, join, relative } from 'node:path';
|
|
16
|
+
|
|
17
|
+
const MOVED = [
|
|
18
|
+
{ old: '/editor', next: '/live-tokens/editor' },
|
|
19
|
+
{ old: '/components', next: '/live-tokens/components' },
|
|
20
|
+
{ old: '/docs', next: '/live-tokens/docs' },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
// Never auto-rewritten: a consumer's own docs page is the common case, and we
|
|
24
|
+
// can't tell their `/docs` from the package's without guessing.
|
|
25
|
+
const NEVER_AUTOWRITE = new Set(['/docs']);
|
|
26
|
+
|
|
27
|
+
const SCAN_EXTS = new Set(['.svelte', '.ts', '.js', '.mjs', '.tsx', '.jsx']);
|
|
28
|
+
const SKIP_DIRS = new Set(['node_modules', 'dist', 'dist-plugin', '.git', '.svelte-kit', 'build']);
|
|
29
|
+
|
|
30
|
+
function walk(dir, out = []) {
|
|
31
|
+
let entries;
|
|
32
|
+
try {
|
|
33
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
34
|
+
} catch {
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
for (const e of entries) {
|
|
38
|
+
if (e.isDirectory()) {
|
|
39
|
+
if (!SKIP_DIRS.has(e.name)) walk(join(dir, e.name), out);
|
|
40
|
+
} else if (SCAN_EXTS.has(extname(e.name))) {
|
|
41
|
+
out.push(join(dir, e.name));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// `navigate('/p')` / `navigate("/p")` / `navigate(`/p`)` and the href forms
|
|
48
|
+
// `href="/p"` / `href='/p'` / `href={'/p'}`. The closing backreference quote
|
|
49
|
+
// pins the path to the exact string, so `/live-tokens/editor` never matches
|
|
50
|
+
// `/editor` (the char before `/editor` there is `s`, not a quote).
|
|
51
|
+
function refRegex(oldPath) {
|
|
52
|
+
return new RegExp(`(navigate\\(\\s*|href\\s*=\\s*\\{?\\s*)(['"\`])${oldPath}\\2`, 'g');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function kindOf(prefix) {
|
|
56
|
+
return prefix.trimStart().startsWith('href') ? 'href' : 'navigate';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A path is the consumer's, not the package's, if they declare a page at it
|
|
61
|
+
* (`'/x':` in a pages map) or name it in an `editorRoutes` override. Conservative
|
|
62
|
+
* by design: when unsure we mark it owned, which only ever downgrades a rewrite
|
|
63
|
+
* to an advisory.
|
|
64
|
+
*/
|
|
65
|
+
function detectConsumerOwned(files) {
|
|
66
|
+
const owned = new Set();
|
|
67
|
+
for (const file of files) {
|
|
68
|
+
const c = readFileSync(file, 'utf8');
|
|
69
|
+
const hasEditorRoutes = /editorRoutes/.test(c);
|
|
70
|
+
for (const { old } of MOVED) {
|
|
71
|
+
const key = old.slice(1);
|
|
72
|
+
if (new RegExp(`['"]\\/${key}['"]\\s*:`).test(c)) owned.add(old);
|
|
73
|
+
if (hasEditorRoutes && new RegExp(`\\b${key}\\s*:`).test(c)) owned.add(old);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return owned;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function scanContent(content) {
|
|
80
|
+
const lines = content.split('\n');
|
|
81
|
+
const found = [];
|
|
82
|
+
for (const { old, next } of MOVED) {
|
|
83
|
+
const re = refRegex(old);
|
|
84
|
+
lines.forEach((text, i) => {
|
|
85
|
+
re.lastIndex = 0;
|
|
86
|
+
let m;
|
|
87
|
+
while ((m = re.exec(text))) {
|
|
88
|
+
found.push({ line: i + 1, kind: kindOf(m[1]), old, next });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return found;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function applyRewrites(content, paths) {
|
|
96
|
+
let out = content;
|
|
97
|
+
for (const { old, next } of paths) {
|
|
98
|
+
out = out.replace(refRegex(old), (_full, prefix, quote) => `${prefix}${quote}${next}${quote}`);
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Scan (and with `apply`, rewrite) source under `root`. Returns
|
|
105
|
+
* `{ scannedFiles, rewritten, pendingWrite, advisory, owned }` — `rewritten` is
|
|
106
|
+
* populated only when `apply` is true; otherwise auto-writable hits land in
|
|
107
|
+
* `pendingWrite`. `advisory` always holds the hits we won't touch automatically.
|
|
108
|
+
*/
|
|
109
|
+
export function runMigrateRoutes({ root = process.cwd(), apply = false } = {}) {
|
|
110
|
+
const srcDir = join(root, 'src');
|
|
111
|
+
const base = existsSync(srcDir) ? srcDir : root;
|
|
112
|
+
const files = walk(base);
|
|
113
|
+
const owned = detectConsumerOwned(files);
|
|
114
|
+
|
|
115
|
+
const rewritten = [];
|
|
116
|
+
const pendingWrite = [];
|
|
117
|
+
const advisory = [];
|
|
118
|
+
|
|
119
|
+
for (const file of files) {
|
|
120
|
+
const content = readFileSync(file, 'utf8');
|
|
121
|
+
const rel = relative(root, file);
|
|
122
|
+
const hits = scanContent(content).map((h) => ({
|
|
123
|
+
...h,
|
|
124
|
+
file: rel,
|
|
125
|
+
autoWrite: !NEVER_AUTOWRITE.has(h.old) && !owned.has(h.old),
|
|
126
|
+
reason: NEVER_AUTOWRITE.has(h.old) ? 'docs-never' : owned.has(h.old) ? 'consumer-owned' : null,
|
|
127
|
+
}));
|
|
128
|
+
if (hits.length === 0) continue;
|
|
129
|
+
|
|
130
|
+
const auto = hits.filter((h) => h.autoWrite);
|
|
131
|
+
advisory.push(...hits.filter((h) => !h.autoWrite));
|
|
132
|
+
|
|
133
|
+
if (apply && auto.length) {
|
|
134
|
+
const paths = MOVED.filter((m) => auto.some((h) => h.old === m.old));
|
|
135
|
+
const next = applyRewrites(content, paths);
|
|
136
|
+
if (next !== content) {
|
|
137
|
+
writeFileSync(file, next);
|
|
138
|
+
rewritten.push(...auto);
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
pendingWrite.push(...auto);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { scannedFiles: files.length, rewritten, pendingWrite, advisory, owned: [...owned] };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function formatRouteResult(result, { check = false } = {}) {
|
|
149
|
+
const { rewritten, pendingWrite, advisory } = result;
|
|
150
|
+
if (!rewritten.length && !pendingWrite.length && !advisory.length) return '';
|
|
151
|
+
|
|
152
|
+
const ref = (h) => ` ${h.file}:${h.line} ${h.kind} '${h.old}' → '${h.next}'`;
|
|
153
|
+
const lines = ['Route references — /editor, /components, /docs moved to /live-tokens/* in 0.35.0:'];
|
|
154
|
+
|
|
155
|
+
if (rewritten.length) {
|
|
156
|
+
lines.push(` ✓ Rewrote ${rewritten.length} reference(s):`);
|
|
157
|
+
rewritten.forEach((h) => lines.push(ref(h)));
|
|
158
|
+
}
|
|
159
|
+
if (pendingWrite.length) {
|
|
160
|
+
lines.push(
|
|
161
|
+
check
|
|
162
|
+
? ` Would rewrite ${pendingWrite.length} reference(s) with --write:`
|
|
163
|
+
: ` ${pendingWrite.length} reference(s) can be rewritten — re-run with --write to apply:`,
|
|
164
|
+
);
|
|
165
|
+
pendingWrite.forEach((h) => lines.push(ref(h)));
|
|
166
|
+
}
|
|
167
|
+
if (advisory.length) {
|
|
168
|
+
lines.push(` ⚠ ${advisory.length} reference(s) need manual review:`);
|
|
169
|
+
advisory.forEach((h) => {
|
|
170
|
+
const why =
|
|
171
|
+
h.reason === 'docs-never'
|
|
172
|
+
? `/docs is never auto-rewritten — update to '${h.next}' only if it points at the package guide`
|
|
173
|
+
: `you declare or relocate '${h.old}' yourself — leave it if it's your route`;
|
|
174
|
+
lines.push(` ${h.file}:${h.line} ${h.kind} '${h.old}' (${why})`);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
if (rewritten.length) lines.push('\n Review the diff in git before committing.');
|
|
178
|
+
return lines.join('\n');
|
|
179
|
+
}
|
|
@@ -106,6 +106,7 @@ function escapeRe(s) {
|
|
|
106
106
|
// vite-plugin/tokensCssMigrations/migrations/2026-05-29-typography-scale-additions.ts
|
|
107
107
|
var tokensCssMigration_2026_05_29_typographyScaleAdditions = {
|
|
108
108
|
id: "2026-05-29-typography-scale-additions",
|
|
109
|
+
kind: "additive",
|
|
109
110
|
description: "Add --line-height-{xs..xl}, --letter-spacing-* and --ease-out-quart scales",
|
|
110
111
|
apply(css) {
|
|
111
112
|
let out = css;
|
|
@@ -144,6 +145,7 @@ var tokensCssMigration_2026_05_29_typographyScaleAdditions = {
|
|
|
144
145
|
var KEEP_SEGMENTS = /* @__PURE__ */ new Set(["lg", "md", "sm"]);
|
|
145
146
|
var tokensCssMigration_2026_05_29_sectiondividerLegacyAxisCleanup = {
|
|
146
147
|
id: "2026-05-29-sectiondivider-legacy-axis-cleanup",
|
|
148
|
+
kind: "breaking",
|
|
147
149
|
description: "Remove legacy --sectiondivider-* tokens not on the lg/md/sm axis",
|
|
148
150
|
apply(css) {
|
|
149
151
|
return removeTokensMatching(css, (name) => {
|
|
@@ -157,6 +159,7 @@ var tokensCssMigration_2026_05_29_sectiondividerLegacyAxisCleanup = {
|
|
|
157
159
|
// vite-plugin/tokensCssMigrations/migrations/2026-06-03-transform-scale-additions.ts
|
|
158
160
|
var tokensCssMigration_2026_06_03_transformScaleAdditions = {
|
|
159
161
|
id: "2026-06-03-transform-scale-additions",
|
|
162
|
+
kind: "additive",
|
|
160
163
|
description: "Add the --scale-{sm..2xl} transform-multiplier scale",
|
|
161
164
|
apply(css) {
|
|
162
165
|
return ensureScale(css, {
|
|
@@ -176,6 +179,7 @@ var tokensCssMigration_2026_06_03_transformScaleAdditions = {
|
|
|
176
179
|
// vite-plugin/tokensCssMigrations/migrations/2026-06-04-remove-dead-size-icon-scale.ts
|
|
177
180
|
var tokensCssMigration_2026_06_04_removeDeadSizeIconScale = {
|
|
178
181
|
id: "2026-06-04-remove-dead-size-icon-scale",
|
|
182
|
+
kind: "breaking",
|
|
179
183
|
description: "Remove the unused --size-icon-* scale (live scale is --icon-size-*)",
|
|
180
184
|
apply(css) {
|
|
181
185
|
return removeTokensMatching(css, (name) => name.startsWith("--size-icon-"));
|
|
@@ -185,6 +189,7 @@ var tokensCssMigration_2026_06_04_removeDeadSizeIconScale = {
|
|
|
185
189
|
// vite-plugin/tokensCssMigrations/migrations/2026-06-04-easing-color-and-typescale-additions.ts
|
|
186
190
|
var tokensCssMigration_2026_06_04_easingColorAndTypescaleAdditions = {
|
|
187
191
|
id: "2026-06-04-easing-color-and-typescale-additions",
|
|
192
|
+
kind: "additive",
|
|
188
193
|
description: "Add the full --ease-* scale, --color-white/black, and --font-size-7xl",
|
|
189
194
|
apply(css) {
|
|
190
195
|
let out = css;
|
|
@@ -313,9 +318,16 @@ var TOKENS_CSS_MIGRATIONS = [
|
|
|
313
318
|
tokensCssMigration_2026_06_04_easingColorAndTypescaleAdditions
|
|
314
319
|
];
|
|
315
320
|
function runTokensCssMigrations(css) {
|
|
321
|
+
return foldMigrations(css, () => true);
|
|
322
|
+
}
|
|
323
|
+
function runAdditiveTokensCssMigrations(css) {
|
|
324
|
+
return foldMigrations(css, (m) => m.kind === "additive");
|
|
325
|
+
}
|
|
326
|
+
function foldMigrations(css, include) {
|
|
316
327
|
let out = css;
|
|
317
328
|
const applied = [];
|
|
318
329
|
for (const m of TOKENS_CSS_MIGRATIONS) {
|
|
330
|
+
if (!include(m)) continue;
|
|
319
331
|
const next = m.apply(out);
|
|
320
332
|
if (next !== out) {
|
|
321
333
|
applied.push(m.id);
|
|
@@ -324,6 +336,45 @@ function runTokensCssMigrations(css) {
|
|
|
324
336
|
}
|
|
325
337
|
return { css: out, applied, changed: out !== css };
|
|
326
338
|
}
|
|
339
|
+
function findContractViolations(canonicalCss) {
|
|
340
|
+
const violations = [];
|
|
341
|
+
for (const m of TOKENS_CSS_MIGRATIONS) {
|
|
342
|
+
if (m.kind !== "additive") continue;
|
|
343
|
+
const before = collectDefinedTokens(canonicalCss);
|
|
344
|
+
const after = collectDefinedTokens(m.apply(canonicalCss));
|
|
345
|
+
const removed = [...before].filter((t) => !after.has(t)).sort();
|
|
346
|
+
if (removed.length) violations.push({ id: m.id, removed });
|
|
347
|
+
}
|
|
348
|
+
return violations;
|
|
349
|
+
}
|
|
350
|
+
function semverBumpType(prev, next) {
|
|
351
|
+
const p = parseSemver(prev);
|
|
352
|
+
const n = parseSemver(next);
|
|
353
|
+
if (n.major > p.major) return "major";
|
|
354
|
+
if (n.major === p.major && n.minor > p.minor) return "minor";
|
|
355
|
+
if (n.major === p.major && n.minor === p.minor && n.patch > p.patch) return "patch";
|
|
356
|
+
return "none";
|
|
357
|
+
}
|
|
358
|
+
function parseSemver(v) {
|
|
359
|
+
const [core] = v.replace(/^v/, "").split(/[-+]/);
|
|
360
|
+
const [major = 0, minor = 0, patch = 0] = core.split(".").map((n) => Number(n) || 0);
|
|
361
|
+
return { major, minor, patch };
|
|
362
|
+
}
|
|
363
|
+
function enforceBreakingRequiresMajor(args) {
|
|
364
|
+
const breakingIds = args.newMigrations.filter((m) => m.kind === "breaking").map((m) => m.id);
|
|
365
|
+
const bump = semverBumpType(args.prevVersion, args.nextVersion);
|
|
366
|
+
if (breakingIds.length === 0 || bump === "major") {
|
|
367
|
+
return { level: "ok", breakingIds, bump, message: "" };
|
|
368
|
+
}
|
|
369
|
+
const pre1 = parseSemver(args.nextVersion).major < 1;
|
|
370
|
+
const ids = breakingIds.join(", ");
|
|
371
|
+
return {
|
|
372
|
+
level: pre1 ? "warn" : "error",
|
|
373
|
+
breakingIds,
|
|
374
|
+
bump,
|
|
375
|
+
message: `Breaking token migration(s) [${ids}] are shipping in a ${bump} bump (${args.prevVersion} -> ${args.nextVersion}). Token names are public API; ` + (pre1 ? `pre-1.0 this is allowed, but the CHANGELOG must flag it under "Changed (breaking)".` : `from 1.0.0 a breaking token change requires a major bump.`)
|
|
376
|
+
};
|
|
377
|
+
}
|
|
327
378
|
function validateTokensCss(input) {
|
|
328
379
|
const defined = /* @__PURE__ */ new Set([
|
|
329
380
|
...collectDefinedTokens(input.tokensCss),
|
|
@@ -361,5 +412,9 @@ export {
|
|
|
361
412
|
removeTokensMatching,
|
|
362
413
|
TOKENS_CSS_MIGRATIONS,
|
|
363
414
|
runTokensCssMigrations,
|
|
415
|
+
runAdditiveTokensCssMigrations,
|
|
416
|
+
findContractViolations,
|
|
417
|
+
semverBumpType,
|
|
418
|
+
enforceBreakingRequiresMajor,
|
|
364
419
|
validateTokensCss
|
|
365
420
|
};
|
package/dist-plugin/index.cjs
CHANGED
|
@@ -730,6 +730,7 @@ function findTopLevelRoot(lines) {
|
|
|
730
730
|
// vite-plugin/tokensCssMigrations/migrations/2026-05-29-typography-scale-additions.ts
|
|
731
731
|
var tokensCssMigration_2026_05_29_typographyScaleAdditions = {
|
|
732
732
|
id: "2026-05-29-typography-scale-additions",
|
|
733
|
+
kind: "additive",
|
|
733
734
|
description: "Add --line-height-{xs..xl}, --letter-spacing-* and --ease-out-quart scales",
|
|
734
735
|
apply(css) {
|
|
735
736
|
let out = css;
|
|
@@ -768,6 +769,7 @@ var tokensCssMigration_2026_05_29_typographyScaleAdditions = {
|
|
|
768
769
|
var KEEP_SEGMENTS = /* @__PURE__ */ new Set(["lg", "md", "sm"]);
|
|
769
770
|
var tokensCssMigration_2026_05_29_sectiondividerLegacyAxisCleanup = {
|
|
770
771
|
id: "2026-05-29-sectiondivider-legacy-axis-cleanup",
|
|
772
|
+
kind: "breaking",
|
|
771
773
|
description: "Remove legacy --sectiondivider-* tokens not on the lg/md/sm axis",
|
|
772
774
|
apply(css) {
|
|
773
775
|
return removeTokensMatching(css, (name) => {
|
|
@@ -781,6 +783,7 @@ var tokensCssMigration_2026_05_29_sectiondividerLegacyAxisCleanup = {
|
|
|
781
783
|
// vite-plugin/tokensCssMigrations/migrations/2026-06-03-transform-scale-additions.ts
|
|
782
784
|
var tokensCssMigration_2026_06_03_transformScaleAdditions = {
|
|
783
785
|
id: "2026-06-03-transform-scale-additions",
|
|
786
|
+
kind: "additive",
|
|
784
787
|
description: "Add the --scale-{sm..2xl} transform-multiplier scale",
|
|
785
788
|
apply(css) {
|
|
786
789
|
return ensureScale(css, {
|
|
@@ -800,6 +803,7 @@ var tokensCssMigration_2026_06_03_transformScaleAdditions = {
|
|
|
800
803
|
// vite-plugin/tokensCssMigrations/migrations/2026-06-04-remove-dead-size-icon-scale.ts
|
|
801
804
|
var tokensCssMigration_2026_06_04_removeDeadSizeIconScale = {
|
|
802
805
|
id: "2026-06-04-remove-dead-size-icon-scale",
|
|
806
|
+
kind: "breaking",
|
|
803
807
|
description: "Remove the unused --size-icon-* scale (live scale is --icon-size-*)",
|
|
804
808
|
apply(css) {
|
|
805
809
|
return removeTokensMatching(css, (name) => name.startsWith("--size-icon-"));
|
|
@@ -809,6 +813,7 @@ var tokensCssMigration_2026_06_04_removeDeadSizeIconScale = {
|
|
|
809
813
|
// vite-plugin/tokensCssMigrations/migrations/2026-06-04-easing-color-and-typescale-additions.ts
|
|
810
814
|
var tokensCssMigration_2026_06_04_easingColorAndTypescaleAdditions = {
|
|
811
815
|
id: "2026-06-04-easing-color-and-typescale-additions",
|
|
816
|
+
kind: "additive",
|
|
812
817
|
description: "Add the full --ease-* scale, --color-white/black, and --font-size-7xl",
|
|
813
818
|
apply(css) {
|
|
814
819
|
let out = css;
|
|
@@ -891,9 +896,16 @@ var TOKENS_CSS_MIGRATIONS = [
|
|
|
891
896
|
tokensCssMigration_2026_06_04_easingColorAndTypescaleAdditions
|
|
892
897
|
];
|
|
893
898
|
function runTokensCssMigrations(css) {
|
|
899
|
+
return foldMigrations(css, () => true);
|
|
900
|
+
}
|
|
901
|
+
function runAdditiveTokensCssMigrations(css) {
|
|
902
|
+
return foldMigrations(css, (m) => m.kind === "additive");
|
|
903
|
+
}
|
|
904
|
+
function foldMigrations(css, include) {
|
|
894
905
|
let out = css;
|
|
895
906
|
const applied = [];
|
|
896
907
|
for (const m of TOKENS_CSS_MIGRATIONS) {
|
|
908
|
+
if (!include(m)) continue;
|
|
897
909
|
const next = m.apply(out);
|
|
898
910
|
if (next !== out) {
|
|
899
911
|
applied.push(m.id);
|
|
@@ -1233,6 +1245,20 @@ ${lines.join("\n")}
|
|
|
1233
1245
|
These render as blank/empty editor slots. Run \`npx live-tokens migrate\` to reconcile.`
|
|
1234
1246
|
);
|
|
1235
1247
|
}
|
|
1248
|
+
function autoMigrateAdditive(log) {
|
|
1249
|
+
let before = "";
|
|
1250
|
+
try {
|
|
1251
|
+
before = import_fs3.default.readFileSync(CSS_PATH, "utf-8");
|
|
1252
|
+
} catch {
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
const { css, applied, changed } = runAdditiveTokensCssMigrations(before);
|
|
1256
|
+
if (!changed) return;
|
|
1257
|
+
import_fs3.default.writeFileSync(CSS_PATH, css);
|
|
1258
|
+
log(
|
|
1259
|
+
`[live-tokens] autoMigrate applied ${applied.length} additive migration(s) to ${import_path3.default.relative(process.cwd(), CSS_PATH)}: ${applied.join(", ")}. Review the diff in git.`
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1236
1262
|
function generateDefaultConfig(comp, sourcePath) {
|
|
1237
1263
|
if (!import_fs3.default.existsSync(sourcePath)) return;
|
|
1238
1264
|
const r = componentResource(comp);
|
|
@@ -2030,6 +2056,7 @@ ${lines.join("\n")}
|
|
|
2030
2056
|
ensureComponentConfigsDir();
|
|
2031
2057
|
ensureManifestsDir();
|
|
2032
2058
|
regenerateTokensCss();
|
|
2059
|
+
if (opts.autoMigrate) autoMigrateAdditive((msg) => server.config.logger.info(msg));
|
|
2033
2060
|
warnOnTokenDrift((msg) => server.config.logger.warn(msg));
|
|
2034
2061
|
server.middlewares.use(async (req, res, next) => {
|
|
2035
2062
|
const handled = await dispatch(req, res, routes);
|
package/dist-plugin/index.d.cts
CHANGED
package/dist-plugin/index.d.ts
CHANGED
package/dist-plugin/index.js
CHANGED
|
@@ -2,9 +2,10 @@ import {
|
|
|
2
2
|
TOKENS_CSS_MIGRATIONS,
|
|
3
3
|
extractGlobalRootBody,
|
|
4
4
|
resolveDataDirs,
|
|
5
|
+
runAdditiveTokensCssMigrations,
|
|
5
6
|
runTokensCssMigrations,
|
|
6
7
|
validateTokensCss
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-D77VD4Z6.js";
|
|
8
9
|
|
|
9
10
|
// vite-plugin/themeFileApi.ts
|
|
10
11
|
import fs2 from "fs";
|
|
@@ -866,6 +867,20 @@ ${lines.join("\n")}
|
|
|
866
867
|
These render as blank/empty editor slots. Run \`npx live-tokens migrate\` to reconcile.`
|
|
867
868
|
);
|
|
868
869
|
}
|
|
870
|
+
function autoMigrateAdditive(log) {
|
|
871
|
+
let before = "";
|
|
872
|
+
try {
|
|
873
|
+
before = fs2.readFileSync(CSS_PATH, "utf-8");
|
|
874
|
+
} catch {
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
const { css, applied, changed } = runAdditiveTokensCssMigrations(before);
|
|
878
|
+
if (!changed) return;
|
|
879
|
+
fs2.writeFileSync(CSS_PATH, css);
|
|
880
|
+
log(
|
|
881
|
+
`[live-tokens] autoMigrate applied ${applied.length} additive migration(s) to ${path2.relative(process.cwd(), CSS_PATH)}: ${applied.join(", ")}. Review the diff in git.`
|
|
882
|
+
);
|
|
883
|
+
}
|
|
869
884
|
function generateDefaultConfig(comp, sourcePath) {
|
|
870
885
|
if (!fs2.existsSync(sourcePath)) return;
|
|
871
886
|
const r = componentResource(comp);
|
|
@@ -1663,6 +1678,7 @@ ${lines.join("\n")}
|
|
|
1663
1678
|
ensureComponentConfigsDir();
|
|
1664
1679
|
ensureManifestsDir();
|
|
1665
1680
|
regenerateTokensCss();
|
|
1681
|
+
if (opts.autoMigrate) autoMigrateAdditive((msg) => server.config.logger.info(msg));
|
|
1666
1682
|
warnOnTokenDrift((msg) => server.config.logger.warn(msg));
|
|
1667
1683
|
server.middlewares.use(async (req, res, next) => {
|
|
1668
1684
|
const handled = await dispatch(req, res, routes);
|