@motion-proto/live-tokens 0.33.1 → 0.34.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/CHANGELOG.md +23 -0
- package/README.md +3 -1
- 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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.34.0 — Token-as-API contract guardrail; opt-in autoMigrate
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Token names are now a versioned API contract, with a guardrail.** Each
|
|
8
|
+
`tokens.css` migration declares `kind: 'additive' | 'breaking'`. A new
|
|
9
|
+
`check:token-contract` (wired into `prepublishOnly`, plus
|
|
10
|
+
`tokensCssMigrations/contract.test.ts`) verifies behaviorally that an additive
|
|
11
|
+
migration never removes or renames a token (catches a breaking change shipped
|
|
12
|
+
as backward-compatible), and gates breaking migrations on a major bump from
|
|
13
|
+
1.0.0 (pre-1.0 it warns). See TOKENS.md and RELEASING.md.
|
|
14
|
+
- **`themeFileApi({ autoMigrate: true })`.** Opt-in: the dev server applies
|
|
15
|
+
pending **additive** token migrations to your `tokens.css` at startup and
|
|
16
|
+
writes the file (shown in git), so it stays current with the package without a
|
|
17
|
+
manual step. Breaking migrations are never auto-applied. Off by default, which
|
|
18
|
+
preserves the invariant that the plugin never writes `tokensCssPath` unless you
|
|
19
|
+
enable it.
|
|
20
|
+
|
|
21
|
+
### Docs
|
|
22
|
+
|
|
23
|
+
- **`TOKENS.md`** gained a plain-language section on how token changes are
|
|
24
|
+
versioned (additive vs breaking, what an upgrade can and cannot change).
|
|
25
|
+
|
|
3
26
|
## 0.33.1 — Ship the changelog in the package
|
|
4
27
|
|
|
5
28
|
### Fixed
|
package/README.md
CHANGED
|
@@ -351,7 +351,7 @@ It enforces the file layout, `:global(:root)` block, token-suffix vocabulary, th
|
|
|
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/).
|
|
@@ -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);
|
|
@@ -33,12 +33,16 @@ __export(tokensCssMigrations_exports, {
|
|
|
33
33
|
TOKENS_CSS_MIGRATIONS: () => TOKENS_CSS_MIGRATIONS,
|
|
34
34
|
collectDefinedTokens: () => collectDefinedTokens,
|
|
35
35
|
collectReferencedTokens: () => collectReferencedTokens,
|
|
36
|
+
enforceBreakingRequiresMajor: () => enforceBreakingRequiresMajor,
|
|
36
37
|
ensureScale: () => ensureScale,
|
|
38
|
+
findContractViolations: () => findContractViolations,
|
|
37
39
|
readLiveTokensConfig: () => readLiveTokensConfig,
|
|
38
40
|
removeToken: () => removeToken,
|
|
39
41
|
removeTokensMatching: () => removeTokensMatching,
|
|
40
42
|
renameToken: () => renameToken,
|
|
43
|
+
runAdditiveTokensCssMigrations: () => runAdditiveTokensCssMigrations,
|
|
41
44
|
runTokensCssMigrations: () => runTokensCssMigrations,
|
|
45
|
+
semverBumpType: () => semverBumpType,
|
|
42
46
|
validateTokensCss: () => validateTokensCss
|
|
43
47
|
});
|
|
44
48
|
module.exports = __toCommonJS(tokensCssMigrations_exports);
|
|
@@ -151,6 +155,7 @@ function escapeRe(s) {
|
|
|
151
155
|
// vite-plugin/tokensCssMigrations/migrations/2026-05-29-typography-scale-additions.ts
|
|
152
156
|
var tokensCssMigration_2026_05_29_typographyScaleAdditions = {
|
|
153
157
|
id: "2026-05-29-typography-scale-additions",
|
|
158
|
+
kind: "additive",
|
|
154
159
|
description: "Add --line-height-{xs..xl}, --letter-spacing-* and --ease-out-quart scales",
|
|
155
160
|
apply(css) {
|
|
156
161
|
let out = css;
|
|
@@ -189,6 +194,7 @@ var tokensCssMigration_2026_05_29_typographyScaleAdditions = {
|
|
|
189
194
|
var KEEP_SEGMENTS = /* @__PURE__ */ new Set(["lg", "md", "sm"]);
|
|
190
195
|
var tokensCssMigration_2026_05_29_sectiondividerLegacyAxisCleanup = {
|
|
191
196
|
id: "2026-05-29-sectiondivider-legacy-axis-cleanup",
|
|
197
|
+
kind: "breaking",
|
|
192
198
|
description: "Remove legacy --sectiondivider-* tokens not on the lg/md/sm axis",
|
|
193
199
|
apply(css) {
|
|
194
200
|
return removeTokensMatching(css, (name) => {
|
|
@@ -202,6 +208,7 @@ var tokensCssMigration_2026_05_29_sectiondividerLegacyAxisCleanup = {
|
|
|
202
208
|
// vite-plugin/tokensCssMigrations/migrations/2026-06-03-transform-scale-additions.ts
|
|
203
209
|
var tokensCssMigration_2026_06_03_transformScaleAdditions = {
|
|
204
210
|
id: "2026-06-03-transform-scale-additions",
|
|
211
|
+
kind: "additive",
|
|
205
212
|
description: "Add the --scale-{sm..2xl} transform-multiplier scale",
|
|
206
213
|
apply(css) {
|
|
207
214
|
return ensureScale(css, {
|
|
@@ -221,6 +228,7 @@ var tokensCssMigration_2026_06_03_transformScaleAdditions = {
|
|
|
221
228
|
// vite-plugin/tokensCssMigrations/migrations/2026-06-04-remove-dead-size-icon-scale.ts
|
|
222
229
|
var tokensCssMigration_2026_06_04_removeDeadSizeIconScale = {
|
|
223
230
|
id: "2026-06-04-remove-dead-size-icon-scale",
|
|
231
|
+
kind: "breaking",
|
|
224
232
|
description: "Remove the unused --size-icon-* scale (live scale is --icon-size-*)",
|
|
225
233
|
apply(css) {
|
|
226
234
|
return removeTokensMatching(css, (name) => name.startsWith("--size-icon-"));
|
|
@@ -230,6 +238,7 @@ var tokensCssMigration_2026_06_04_removeDeadSizeIconScale = {
|
|
|
230
238
|
// vite-plugin/tokensCssMigrations/migrations/2026-06-04-easing-color-and-typescale-additions.ts
|
|
231
239
|
var tokensCssMigration_2026_06_04_easingColorAndTypescaleAdditions = {
|
|
232
240
|
id: "2026-06-04-easing-color-and-typescale-additions",
|
|
241
|
+
kind: "additive",
|
|
233
242
|
description: "Add the full --ease-* scale, --color-white/black, and --font-size-7xl",
|
|
234
243
|
apply(css) {
|
|
235
244
|
let out = css;
|
|
@@ -345,9 +354,16 @@ var TOKENS_CSS_MIGRATIONS = [
|
|
|
345
354
|
tokensCssMigration_2026_06_04_easingColorAndTypescaleAdditions
|
|
346
355
|
];
|
|
347
356
|
function runTokensCssMigrations(css) {
|
|
357
|
+
return foldMigrations(css, () => true);
|
|
358
|
+
}
|
|
359
|
+
function runAdditiveTokensCssMigrations(css) {
|
|
360
|
+
return foldMigrations(css, (m) => m.kind === "additive");
|
|
361
|
+
}
|
|
362
|
+
function foldMigrations(css, include) {
|
|
348
363
|
let out = css;
|
|
349
364
|
const applied = [];
|
|
350
365
|
for (const m of TOKENS_CSS_MIGRATIONS) {
|
|
366
|
+
if (!include(m)) continue;
|
|
351
367
|
const next = m.apply(out);
|
|
352
368
|
if (next !== out) {
|
|
353
369
|
applied.push(m.id);
|
|
@@ -356,6 +372,45 @@ function runTokensCssMigrations(css) {
|
|
|
356
372
|
}
|
|
357
373
|
return { css: out, applied, changed: out !== css };
|
|
358
374
|
}
|
|
375
|
+
function findContractViolations(canonicalCss) {
|
|
376
|
+
const violations = [];
|
|
377
|
+
for (const m of TOKENS_CSS_MIGRATIONS) {
|
|
378
|
+
if (m.kind !== "additive") continue;
|
|
379
|
+
const before = collectDefinedTokens(canonicalCss);
|
|
380
|
+
const after = collectDefinedTokens(m.apply(canonicalCss));
|
|
381
|
+
const removed = [...before].filter((t) => !after.has(t)).sort();
|
|
382
|
+
if (removed.length) violations.push({ id: m.id, removed });
|
|
383
|
+
}
|
|
384
|
+
return violations;
|
|
385
|
+
}
|
|
386
|
+
function semverBumpType(prev, next) {
|
|
387
|
+
const p = parseSemver(prev);
|
|
388
|
+
const n = parseSemver(next);
|
|
389
|
+
if (n.major > p.major) return "major";
|
|
390
|
+
if (n.major === p.major && n.minor > p.minor) return "minor";
|
|
391
|
+
if (n.major === p.major && n.minor === p.minor && n.patch > p.patch) return "patch";
|
|
392
|
+
return "none";
|
|
393
|
+
}
|
|
394
|
+
function parseSemver(v) {
|
|
395
|
+
const [core] = v.replace(/^v/, "").split(/[-+]/);
|
|
396
|
+
const [major = 0, minor = 0, patch = 0] = core.split(".").map((n) => Number(n) || 0);
|
|
397
|
+
return { major, minor, patch };
|
|
398
|
+
}
|
|
399
|
+
function enforceBreakingRequiresMajor(args) {
|
|
400
|
+
const breakingIds = args.newMigrations.filter((m) => m.kind === "breaking").map((m) => m.id);
|
|
401
|
+
const bump = semverBumpType(args.prevVersion, args.nextVersion);
|
|
402
|
+
if (breakingIds.length === 0 || bump === "major") {
|
|
403
|
+
return { level: "ok", breakingIds, bump, message: "" };
|
|
404
|
+
}
|
|
405
|
+
const pre1 = parseSemver(args.nextVersion).major < 1;
|
|
406
|
+
const ids = breakingIds.join(", ");
|
|
407
|
+
return {
|
|
408
|
+
level: pre1 ? "warn" : "error",
|
|
409
|
+
breakingIds,
|
|
410
|
+
bump,
|
|
411
|
+
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.`)
|
|
412
|
+
};
|
|
413
|
+
}
|
|
359
414
|
function validateTokensCss(input) {
|
|
360
415
|
const defined = /* @__PURE__ */ new Set([
|
|
361
416
|
...collectDefinedTokens(input.tokensCss),
|
|
@@ -385,11 +440,15 @@ function validateTokensCss(input) {
|
|
|
385
440
|
TOKENS_CSS_MIGRATIONS,
|
|
386
441
|
collectDefinedTokens,
|
|
387
442
|
collectReferencedTokens,
|
|
443
|
+
enforceBreakingRequiresMajor,
|
|
388
444
|
ensureScale,
|
|
445
|
+
findContractViolations,
|
|
389
446
|
readLiveTokensConfig,
|
|
390
447
|
removeToken,
|
|
391
448
|
removeTokensMatching,
|
|
392
449
|
renameToken,
|
|
450
|
+
runAdditiveTokensCssMigrations,
|
|
393
451
|
runTokensCssMigrations,
|
|
452
|
+
semverBumpType,
|
|
394
453
|
validateTokensCss
|
|
395
454
|
});
|
|
@@ -16,6 +16,16 @@
|
|
|
16
16
|
interface TokensCssMigration {
|
|
17
17
|
/** Unique id; convention: `YYYY-MM-DD-<short-name>`. */
|
|
18
18
|
id: string;
|
|
19
|
+
/**
|
|
20
|
+
* Whether this migration is backward-compatible.
|
|
21
|
+
*
|
|
22
|
+
* Token names are public API (see TOKENS.md). `'additive'` only ever inserts
|
|
23
|
+
* new names, so it is safe to auto-apply to a consumer's vendored `tokens.css`
|
|
24
|
+
* (the dev plugin's `autoMigrate`). `'breaking'` renames or removes a name a
|
|
25
|
+
* consumer may reference, so it ships only with a major version (post-1.0) and
|
|
26
|
+
* is never auto-applied — it rides an explicit `live-tokens migrate`.
|
|
27
|
+
*/
|
|
28
|
+
kind: 'additive' | 'breaking';
|
|
19
29
|
/** One-line summary shown by the CLI when the migration applies. */
|
|
20
30
|
description: string;
|
|
21
31
|
/** Pure, idempotent transform on the `tokens.css` source. */
|
|
@@ -113,6 +123,50 @@ interface RunResult {
|
|
|
113
123
|
}
|
|
114
124
|
/** Fold every registered migration over `css`. Pure and idempotent. */
|
|
115
125
|
declare function runTokensCssMigrations(css: string): RunResult;
|
|
126
|
+
/**
|
|
127
|
+
* Fold only the `additive` migrations. Additive changes only insert new token
|
|
128
|
+
* names, so applying them to a consumer's vendored `tokens.css` is always
|
|
129
|
+
* backward-compatible — this is what the dev plugin's `autoMigrate` runs. The
|
|
130
|
+
* `breaking` migrations (rename/remove) are deliberately skipped; they ride an
|
|
131
|
+
* explicit `live-tokens migrate` during a major upgrade.
|
|
132
|
+
*/
|
|
133
|
+
declare function runAdditiveTokensCssMigrations(css: string): RunResult;
|
|
134
|
+
interface ContractViolation {
|
|
135
|
+
id: string;
|
|
136
|
+
/** Token names the migration removed or renamed away despite declaring `additive`. */
|
|
137
|
+
removed: string[];
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Guardrail for the token-as-API contract: an `additive` migration must never
|
|
141
|
+
* remove or rename a token. We verify behaviorally rather than trusting the
|
|
142
|
+
* label — apply each additive migration to the package's own canonical
|
|
143
|
+
* `tokens.css` (which defines the full current vocabulary) and flag any token
|
|
144
|
+
* that disappears. A rename surfaces as the removal of its old name, so it is
|
|
145
|
+
* caught too. This catches the dangerous direction: a breaking change shipped as
|
|
146
|
+
* a backward-compatible one. (Over-labeling a no-op as `breaking` is harmless
|
|
147
|
+
* and not checked.)
|
|
148
|
+
*/
|
|
149
|
+
declare function findContractViolations(canonicalCss: string): ContractViolation[];
|
|
150
|
+
type SemverBump = 'major' | 'minor' | 'patch' | 'none';
|
|
151
|
+
/** Classify the bump from `prev` to `next` (leading `v` and pre-release tags ignored). */
|
|
152
|
+
declare function semverBumpType(prev: string, next: string): SemverBump;
|
|
153
|
+
interface BreakingGateResult {
|
|
154
|
+
level: 'ok' | 'warn' | 'error';
|
|
155
|
+
breakingIds: string[];
|
|
156
|
+
bump: SemverBump;
|
|
157
|
+
message: string;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The version side of the token contract: a `breaking` migration introduced in a
|
|
161
|
+
* release requires a major version bump. Pre-1.0 (next major is 0) this is only
|
|
162
|
+
* a warning, since semver permits breaking changes in 0.x minors; from 1.0.0 on
|
|
163
|
+
* it is an error. Pure so the release check and its tests share one rule.
|
|
164
|
+
*/
|
|
165
|
+
declare function enforceBreakingRequiresMajor(args: {
|
|
166
|
+
newMigrations: TokensCssMigration[];
|
|
167
|
+
prevVersion: string;
|
|
168
|
+
nextVersion: string;
|
|
169
|
+
}): BreakingGateResult;
|
|
116
170
|
interface ComponentSource {
|
|
117
171
|
name: string;
|
|
118
172
|
source: string;
|
|
@@ -141,4 +195,4 @@ interface ValidateInput {
|
|
|
141
195
|
*/
|
|
142
196
|
declare function validateTokensCss(input: ValidateInput): MissingToken[];
|
|
143
197
|
|
|
144
|
-
export { type ComponentSource, type MissingToken, type RunResult, TOKENS_CSS_MIGRATIONS, type TokensCssMigration, type ValidateInput, collectDefinedTokens, collectReferencedTokens, ensureScale, readLiveTokensConfig, removeToken, removeTokensMatching, renameToken, runTokensCssMigrations, validateTokensCss };
|
|
198
|
+
export { type BreakingGateResult, type ComponentSource, type ContractViolation, type MissingToken, type RunResult, type SemverBump, TOKENS_CSS_MIGRATIONS, type TokensCssMigration, type ValidateInput, collectDefinedTokens, collectReferencedTokens, enforceBreakingRequiresMajor, ensureScale, findContractViolations, readLiveTokensConfig, removeToken, removeTokensMatching, renameToken, runAdditiveTokensCssMigrations, runTokensCssMigrations, semverBumpType, validateTokensCss };
|
|
@@ -16,6 +16,16 @@
|
|
|
16
16
|
interface TokensCssMigration {
|
|
17
17
|
/** Unique id; convention: `YYYY-MM-DD-<short-name>`. */
|
|
18
18
|
id: string;
|
|
19
|
+
/**
|
|
20
|
+
* Whether this migration is backward-compatible.
|
|
21
|
+
*
|
|
22
|
+
* Token names are public API (see TOKENS.md). `'additive'` only ever inserts
|
|
23
|
+
* new names, so it is safe to auto-apply to a consumer's vendored `tokens.css`
|
|
24
|
+
* (the dev plugin's `autoMigrate`). `'breaking'` renames or removes a name a
|
|
25
|
+
* consumer may reference, so it ships only with a major version (post-1.0) and
|
|
26
|
+
* is never auto-applied — it rides an explicit `live-tokens migrate`.
|
|
27
|
+
*/
|
|
28
|
+
kind: 'additive' | 'breaking';
|
|
19
29
|
/** One-line summary shown by the CLI when the migration applies. */
|
|
20
30
|
description: string;
|
|
21
31
|
/** Pure, idempotent transform on the `tokens.css` source. */
|
|
@@ -113,6 +123,50 @@ interface RunResult {
|
|
|
113
123
|
}
|
|
114
124
|
/** Fold every registered migration over `css`. Pure and idempotent. */
|
|
115
125
|
declare function runTokensCssMigrations(css: string): RunResult;
|
|
126
|
+
/**
|
|
127
|
+
* Fold only the `additive` migrations. Additive changes only insert new token
|
|
128
|
+
* names, so applying them to a consumer's vendored `tokens.css` is always
|
|
129
|
+
* backward-compatible — this is what the dev plugin's `autoMigrate` runs. The
|
|
130
|
+
* `breaking` migrations (rename/remove) are deliberately skipped; they ride an
|
|
131
|
+
* explicit `live-tokens migrate` during a major upgrade.
|
|
132
|
+
*/
|
|
133
|
+
declare function runAdditiveTokensCssMigrations(css: string): RunResult;
|
|
134
|
+
interface ContractViolation {
|
|
135
|
+
id: string;
|
|
136
|
+
/** Token names the migration removed or renamed away despite declaring `additive`. */
|
|
137
|
+
removed: string[];
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Guardrail for the token-as-API contract: an `additive` migration must never
|
|
141
|
+
* remove or rename a token. We verify behaviorally rather than trusting the
|
|
142
|
+
* label — apply each additive migration to the package's own canonical
|
|
143
|
+
* `tokens.css` (which defines the full current vocabulary) and flag any token
|
|
144
|
+
* that disappears. A rename surfaces as the removal of its old name, so it is
|
|
145
|
+
* caught too. This catches the dangerous direction: a breaking change shipped as
|
|
146
|
+
* a backward-compatible one. (Over-labeling a no-op as `breaking` is harmless
|
|
147
|
+
* and not checked.)
|
|
148
|
+
*/
|
|
149
|
+
declare function findContractViolations(canonicalCss: string): ContractViolation[];
|
|
150
|
+
type SemverBump = 'major' | 'minor' | 'patch' | 'none';
|
|
151
|
+
/** Classify the bump from `prev` to `next` (leading `v` and pre-release tags ignored). */
|
|
152
|
+
declare function semverBumpType(prev: string, next: string): SemverBump;
|
|
153
|
+
interface BreakingGateResult {
|
|
154
|
+
level: 'ok' | 'warn' | 'error';
|
|
155
|
+
breakingIds: string[];
|
|
156
|
+
bump: SemverBump;
|
|
157
|
+
message: string;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The version side of the token contract: a `breaking` migration introduced in a
|
|
161
|
+
* release requires a major version bump. Pre-1.0 (next major is 0) this is only
|
|
162
|
+
* a warning, since semver permits breaking changes in 0.x minors; from 1.0.0 on
|
|
163
|
+
* it is an error. Pure so the release check and its tests share one rule.
|
|
164
|
+
*/
|
|
165
|
+
declare function enforceBreakingRequiresMajor(args: {
|
|
166
|
+
newMigrations: TokensCssMigration[];
|
|
167
|
+
prevVersion: string;
|
|
168
|
+
nextVersion: string;
|
|
169
|
+
}): BreakingGateResult;
|
|
116
170
|
interface ComponentSource {
|
|
117
171
|
name: string;
|
|
118
172
|
source: string;
|
|
@@ -141,4 +195,4 @@ interface ValidateInput {
|
|
|
141
195
|
*/
|
|
142
196
|
declare function validateTokensCss(input: ValidateInput): MissingToken[];
|
|
143
197
|
|
|
144
|
-
export { type ComponentSource, type MissingToken, type RunResult, TOKENS_CSS_MIGRATIONS, type TokensCssMigration, type ValidateInput, collectDefinedTokens, collectReferencedTokens, ensureScale, readLiveTokensConfig, removeToken, removeTokensMatching, renameToken, runTokensCssMigrations, validateTokensCss };
|
|
198
|
+
export { type BreakingGateResult, type ComponentSource, type ContractViolation, type MissingToken, type RunResult, type SemverBump, TOKENS_CSS_MIGRATIONS, type TokensCssMigration, type ValidateInput, collectDefinedTokens, collectReferencedTokens, enforceBreakingRequiresMajor, ensureScale, findContractViolations, readLiveTokensConfig, removeToken, removeTokensMatching, renameToken, runAdditiveTokensCssMigrations, runTokensCssMigrations, semverBumpType, validateTokensCss };
|
|
@@ -2,23 +2,31 @@ import {
|
|
|
2
2
|
TOKENS_CSS_MIGRATIONS,
|
|
3
3
|
collectDefinedTokens,
|
|
4
4
|
collectReferencedTokens,
|
|
5
|
+
enforceBreakingRequiresMajor,
|
|
5
6
|
ensureScale,
|
|
7
|
+
findContractViolations,
|
|
6
8
|
readLiveTokensConfig,
|
|
7
9
|
removeToken,
|
|
8
10
|
removeTokensMatching,
|
|
9
11
|
renameToken,
|
|
12
|
+
runAdditiveTokensCssMigrations,
|
|
10
13
|
runTokensCssMigrations,
|
|
14
|
+
semverBumpType,
|
|
11
15
|
validateTokensCss
|
|
12
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-D77VD4Z6.js";
|
|
13
17
|
export {
|
|
14
18
|
TOKENS_CSS_MIGRATIONS,
|
|
15
19
|
collectDefinedTokens,
|
|
16
20
|
collectReferencedTokens,
|
|
21
|
+
enforceBreakingRequiresMajor,
|
|
17
22
|
ensureScale,
|
|
23
|
+
findContractViolations,
|
|
18
24
|
readLiveTokensConfig,
|
|
19
25
|
removeToken,
|
|
20
26
|
removeTokensMatching,
|
|
21
27
|
renameToken,
|
|
28
|
+
runAdditiveTokensCssMigrations,
|
|
22
29
|
runTokensCssMigrations,
|
|
30
|
+
semverBumpType,
|
|
23
31
|
validateTokensCss
|
|
24
32
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motion-proto/live-tokens",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Design token editor with live CSS variable editing. Svelte 5 + Vite 8.",
|
|
6
6
|
"keywords": [
|
|
@@ -101,12 +101,13 @@
|
|
|
101
101
|
"check:component-defaults": "node scripts/sync-component-defaults.mjs --check",
|
|
102
102
|
"check:production-is-default": "node scripts/check-production-is-default.mjs",
|
|
103
103
|
"check:docs-content": "node scripts/sync-docs.mjs --check",
|
|
104
|
+
"check:token-contract": "node scripts/check-token-contract.mjs",
|
|
104
105
|
"sync:component-defaults": "node scripts/sync-component-defaults.mjs --write",
|
|
105
106
|
"sync:docs": "node scripts/sync-docs.mjs --write",
|
|
106
107
|
"collapse:manifest": "node scripts/collapse-manifest-to-default.mjs",
|
|
107
108
|
"check:smoke-install": "bash scripts/smoke-install.sh",
|
|
108
109
|
"check:smoke-create": "bash scripts/smoke-create.sh",
|
|
109
|
-
"prepublishOnly": "npm run check:no-style-imports && npm run check:slot-prose && npm run check:overlay-portal && npm run check:editor-font-isolation && npm run check:component-defaults && npm run check:production-is-default && npm run check:docs-content && npm run build:lib && npm run check:smoke-install && npm run check:smoke-create"
|
|
110
|
+
"prepublishOnly": "npm run check:no-style-imports && npm run check:slot-prose && npm run check:overlay-portal && npm run check:editor-font-isolation && npm run check:component-defaults && npm run check:production-is-default && npm run check:docs-content && npm run build:lib && npm run check:token-contract && npm run check:smoke-install && npm run check:smoke-create"
|
|
110
111
|
},
|
|
111
112
|
"peerDependencies": {
|
|
112
113
|
"@sveltejs/vite-plugin-svelte": "^7.0",
|