@motion-proto/live-tokens 0.72.1 → 0.74.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 +37 -2
- package/.claude/skills/live-tokens-build-page/references/layout-sources.md +48 -0
- package/.claude/skills/live-tokens-check-compliance/SKILL.md +2 -2
- package/.claude/skills/live-tokens-create-component/SKILL.md +4 -4
- package/.claude/skills/live-tokens-create-theme/SKILL.md +80 -0
- package/.claude/skills/live-tokens-create-theme/references/design-directions.md +94 -0
- package/.claude/skills/live-tokens-pick-component/SKILL.md +1 -1
- package/.claude/skills/live-tokens-set-colors/SKILL.md +140 -0
- package/.claude/skills/live-tokens-set-colors/references/color-anchors.md +80 -0
- package/.claude/skills/{live-tokens-adjust-geometry → live-tokens-set-geometry}/SKILL.md +12 -7
- package/.claude/skills/live-tokens-set-geometry/references/geometry-anchors.md +61 -0
- package/.claude/skills/{live-tokens-pair-fonts → live-tokens-set-type}/SKILL.md +18 -15
- package/.claude/skills/live-tokens-set-type/references/type-anchors.md +60 -0
- package/CHANGELOG.md +141 -0
- package/README.md +24 -15
- package/bin/check-page.mjs +3 -3
- package/bin/cli.mjs +92 -55
- package/bin/lib/liveState.mjs +110 -0
- package/bin/save-theme.mjs +177 -0
- package/bin/set-colors.mjs +191 -0
- package/bin/{adjust.mjs → set-geometry.mjs} +18 -50
- package/bin/{set-fonts.mjs → set-type.mjs} +21 -55
- package/dist-plugin/{chunk-RIXO2E55.js → chunk-7VRTBGJT.js} +1 -1
- package/dist-plugin/{chunk-YLCOIGQC.js → chunk-V3YF6CGT.js} +56 -2
- package/dist-plugin/index.cjs +58 -3
- package/dist-plugin/index.js +11 -11
- package/dist-plugin/migrateData/index.cjs +56 -1
- package/dist-plugin/migrateData/index.js +2 -2
- package/dist-plugin/{generateColorsAndType → setColors}/index.cjs +1109 -1071
- package/dist-plugin/{generateColorsAndType → setColors}/index.d.cts +33 -24
- package/dist-plugin/{generateColorsAndType → setColors}/index.d.ts +33 -24
- package/dist-plugin/{generateColorsAndType → setColors}/index.js +45 -63
- package/dist-plugin/{adjust → setGeometry}/index.cjs +60 -5
- package/dist-plugin/{adjust → setGeometry}/index.d.cts +1 -1
- package/dist-plugin/{adjust → setGeometry}/index.d.ts +1 -1
- package/dist-plugin/{adjust → setGeometry}/index.js +1 -1
- package/dist-plugin/{fontPairing → setType}/index.cjs +4 -4
- package/dist-plugin/{fontPairing → setType}/index.d.cts +1 -1
- package/dist-plugin/{fontPairing → setType}/index.d.ts +1 -1
- package/dist-plugin/{themeTypes-DSV3Zisf.d.cts → themeTypes-BxRtuN5V.d.cts} +1 -1
- package/dist-plugin/{themeTypes-DSV3Zisf.d.ts → themeTypes-BxRtuN5V.d.ts} +1 -1
- package/package.json +10 -2
- package/src/editor/core/themes/{generateColorsAndType.ts → buildColors.ts} +82 -96
- package/src/editor/core/themes/migrations/2026-09-03-drop-legacy-component-keys.ts +62 -0
- package/src/editor/core/themes/migrations/index.ts +2 -0
- package/src/editor/docs/content/creating-components.md +13 -0
- package/src/editor/docs/content/themes-workflow.md +2 -2
- package/src/editor/docs/content.generated.ts +2 -2
- package/src/editor/overlay/LiveEditorOverlay.svelte +519 -28
- package/src/editor/skill-atlas/SkillAtlas.svelte +836 -0
- package/src/editor/skill-atlas/SkillAtlas.svelte.d.ts +4 -0
- package/src/editor/skill-atlas/SourcePane.svelte +364 -0
- package/src/editor/skill-atlas/TreeNodeCard.svelte +206 -0
- package/src/editor/skill-atlas/skillSources.generated.ts +45 -0
- package/src/editor/skill-atlas/skillSources.ts +1 -0
- package/src/editor/skill-atlas/skillTrees.ts +3844 -0
- package/src/editor/skill-atlas/types.ts +65 -0
- package/src/live-tokens/data/colors-and-type/autumn.json +1 -37
- package/src/live-tokens/data/colors-and-type/default.json +1 -37
- package/src/live-tokens/data/colors-and-type/halloween.json +1 -37
- package/src/live-tokens/data/colors-and-type/midnight-study.json +1 -37
- package/src/live-tokens/data/colors-and-type/ocean.json +1 -37
- package/src/live-tokens/data/colors-and-type/royal-velvet.json +1 -37
- package/src/live-tokens/data/colors-and-type/sketchy.json +1 -37
- package/src/live-tokens/data/colors-and-type/spring-meadow.json +1 -37
- package/src/live-tokens/data/colors-and-type/sunset.json +1 -37
- package/src/live-tokens/data/themes/autumn.json +1 -37
- package/src/live-tokens/data/themes/halloween.json +1 -37
- package/src/live-tokens/data/themes/midnight-study.json +1 -37
- package/src/live-tokens/data/themes/ocean.json +1 -37
- package/src/live-tokens/data/themes/royal-velvet.json +1 -37
- package/src/live-tokens/data/themes/sketchy.json +1 -37
- package/src/live-tokens/data/themes/spring-meadow.json +1 -37
- package/src/live-tokens/data/themes/sunset.json +1 -37
- package/src/live-tokens/data/tokens.generated.css +0 -36
- package/.claude/skills/live-tokens-generate-theme/SKILL.md +0 -156
- package/.claude/skills/live-tokens-generate-theme/references/mood-vocabulary.md +0 -43
- package/.claude/skills/live-tokens-generate-theme/references/named-themes.md +0 -18
- package/.claude/skills/live-tokens-generate-theme/references/style-vocabulary.md +0 -35
- package/bin/generate-theme.mjs +0 -260
- /package/dist-plugin/{fontPairing → setType}/index.js +0 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// `live-tokens set-colors` worker.
|
|
2
|
+
//
|
|
3
|
+
// Reads a base color file (JSON), builds the theme's whole color state via the
|
|
4
|
+
// compiled engine (dist-plugin/setColors — the CLI never imports TS sources),
|
|
5
|
+
// enforces the AA contrast gate, and writes the result into
|
|
6
|
+
// `colors-and-type/_working.json`: the same buffer the editor's own palette
|
|
7
|
+
// edits land in, so a recolor is an unsaved edit the user keeps by saving the
|
|
8
|
+
// open theme or by running `save-theme`. Named colors-and-type files, themes,
|
|
9
|
+
// tokens.css and fonts.css are never touched, and nothing is activated.
|
|
10
|
+
//
|
|
11
|
+
// Non-color content carries forward from the live colors and type, so a recolor
|
|
12
|
+
// replaces the color identity and nothing else. Swatch gradients ride along
|
|
13
|
+
// when user-tuned; stock ones are rebuilt from the new families by the engine.
|
|
14
|
+
|
|
15
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
16
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
readActiveTheme,
|
|
21
|
+
readLiveColorsAndType,
|
|
22
|
+
readSavedColorsAndType,
|
|
23
|
+
sameContent,
|
|
24
|
+
savedColorsAndTypeSource,
|
|
25
|
+
} from './lib/liveState.mjs';
|
|
26
|
+
|
|
27
|
+
const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
28
|
+
const ENGINE = resolve(pkgRoot, 'dist-plugin/setColors/index.js');
|
|
29
|
+
|
|
30
|
+
const SOURCE_LABELS = {
|
|
31
|
+
working: 'your unsaved edits',
|
|
32
|
+
theme: 'the open theme',
|
|
33
|
+
default: 'the package default',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
async function loadEngine() {
|
|
37
|
+
if (!existsSync(ENGINE)) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`setColors engine not found at ${relative(process.cwd(), ENGINE)}. ` +
|
|
40
|
+
`Build the plugin first (npm run build:plugin).`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return import(ENGINE);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readJson(path) {
|
|
47
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** `engine` is a test seam; the CLI always runs the compiled bundle. */
|
|
51
|
+
export async function runSetColors({
|
|
52
|
+
baseColorsPath,
|
|
53
|
+
dryRun = false,
|
|
54
|
+
root = process.cwd(),
|
|
55
|
+
colorsAndTypeDir,
|
|
56
|
+
themesDir,
|
|
57
|
+
engine,
|
|
58
|
+
} = {}) {
|
|
59
|
+
const { buildColors, resolveDataDirs } = engine ?? (await loadEngine());
|
|
60
|
+
|
|
61
|
+
const fullPath = resolve(root, baseColorsPath);
|
|
62
|
+
if (!existsSync(fullPath)) {
|
|
63
|
+
throw new Error(`base color file not found at ${relative(root, fullPath)}`);
|
|
64
|
+
}
|
|
65
|
+
let input;
|
|
66
|
+
try {
|
|
67
|
+
input = readJson(fullPath);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
throw new Error(`base color file is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const resolved = colorsAndTypeDir && themesDir ? null : resolveDataDirs();
|
|
73
|
+
const colorsDir = colorsAndTypeDir ?? resolved.colorsAndTypeDir;
|
|
74
|
+
const themes = themesDir ?? resolved.themesDir;
|
|
75
|
+
if (!existsSync(colorsDir)) {
|
|
76
|
+
throw new Error(`no colors and type at ${relative(root, colorsDir)}. Run the dev server once to create it.`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const active = readActiveTheme(themes);
|
|
80
|
+
const { colorsAndType: live, source } = readLiveColorsAndType(colorsDir, active);
|
|
81
|
+
|
|
82
|
+
const { colors, report } = buildColors(input, {
|
|
83
|
+
cssVariables: live.cssVariables,
|
|
84
|
+
gradients: live.gradients,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const next = {
|
|
88
|
+
...live,
|
|
89
|
+
...colors,
|
|
90
|
+
updatedAt: new Date().toISOString(),
|
|
91
|
+
schemaVersion: live.schemaVersion,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const workingPath = join(colorsDir, '_working.json');
|
|
95
|
+
const saved = readSavedColorsAndType(colorsDir, active);
|
|
96
|
+
|
|
97
|
+
// Returning the buffer to what the layer under it already holds is a discard,
|
|
98
|
+
// not an edit — the same call the dev server's own PUT makes.
|
|
99
|
+
const backToSaved = saved !== null && sameContent(next, saved);
|
|
100
|
+
let wrote = null;
|
|
101
|
+
if (!dryRun) {
|
|
102
|
+
if (backToSaved) {
|
|
103
|
+
if (existsSync(workingPath)) rmSync(workingPath);
|
|
104
|
+
wrote = 'cleared';
|
|
105
|
+
} else {
|
|
106
|
+
writeFileSync(workingPath, JSON.stringify(next, null, 2) + '\n');
|
|
107
|
+
wrote = 'buffer';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
baseColorsPath: fullPath,
|
|
113
|
+
colorsAndTypeDir: colorsDir,
|
|
114
|
+
workingPath,
|
|
115
|
+
openTheme: active?.slug ?? null,
|
|
116
|
+
source,
|
|
117
|
+
// Which layer the discard compared against, so the report can name it. It
|
|
118
|
+
// is the open theme only when one is open; with none, it is the default the
|
|
119
|
+
// package ships, and calling that "the open theme" named a file nobody had
|
|
120
|
+
// opened.
|
|
121
|
+
savedSource: savedColorsAndTypeSource(active),
|
|
122
|
+
// The base color file's `name` used to pick a theme file name. This verb
|
|
123
|
+
// writes no file of its own now, so it names nothing; say so rather than
|
|
124
|
+
// drop it in silence.
|
|
125
|
+
ignoredName: input.name === undefined ? null : String(input.name),
|
|
126
|
+
dryRun,
|
|
127
|
+
wrote,
|
|
128
|
+
report,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function formatSetColorsResult(result) {
|
|
133
|
+
const root = process.cwd();
|
|
134
|
+
const lines = [];
|
|
135
|
+
|
|
136
|
+
const from =
|
|
137
|
+
result.source === 'theme' && result.openTheme
|
|
138
|
+
? `theme "${result.openTheme}"`
|
|
139
|
+
: SOURCE_LABELS[result.source];
|
|
140
|
+
const verb = result.dryRun ? 'Would replace' : 'Replaced';
|
|
141
|
+
lines.push(`${verb} the color identity, carrying everything else forward from ${from}.`);
|
|
142
|
+
if (result.ignoredName !== null) {
|
|
143
|
+
lines.push(
|
|
144
|
+
`Ignored "name": "${result.ignoredName}". The base color file no longer names a theme; ` +
|
|
145
|
+
`name it when you run save-theme.`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
lines.push(`\nContrast report (${result.report.scheme} scheme):`);
|
|
150
|
+
const width = Math.max(...result.report.checks.map((c) => c.textVar.length));
|
|
151
|
+
for (const c of result.report.checks) {
|
|
152
|
+
const mark = c.pass ? '✓' : '✗';
|
|
153
|
+
const fixed = c.corrected ? ' (auto-corrected)' : '';
|
|
154
|
+
lines.push(
|
|
155
|
+
` ${mark} ${c.textVar.padEnd(width)} ${c.ratio.toFixed(2).padStart(6)}:1 vs ${c.against} floor ${c.floor}:1${fixed}`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if (result.report.failures.length > 0) {
|
|
159
|
+
lines.push(`\nUnmet floors — adjust the base colors and re-run:`);
|
|
160
|
+
for (const f of result.report.failures) lines.push(` ! ${f}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
lines.push(
|
|
164
|
+
result.report.gradients === 'carried'
|
|
165
|
+
? '\nGradients: kept your tuned swatch gradients.'
|
|
166
|
+
: '\nGradients: swatch tokens rebuilt from the theme families.',
|
|
167
|
+
);
|
|
168
|
+
lines.push(`Shadows: ${result.report.shadows}; carried geometry kept.`);
|
|
169
|
+
if (result.report.canvasGradient) {
|
|
170
|
+
lines.push(`Canvas sky: ${result.report.canvasGradient}.`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (result.wrote === 'buffer') {
|
|
174
|
+
lines.push(
|
|
175
|
+
`\nReload the app to see it. This is an unsaved edit: save the open theme in the ` +
|
|
176
|
+
`editor's Theme panel to keep it, or run save-theme to write a new one.`,
|
|
177
|
+
);
|
|
178
|
+
} else if (result.wrote === 'cleared') {
|
|
179
|
+
const held =
|
|
180
|
+
result.savedSource === 'theme' ? `theme "${result.openTheme}"` : 'the package default';
|
|
181
|
+
lines.push(
|
|
182
|
+
result.source === 'working'
|
|
183
|
+
? `\nThat is what ${held} already holds, so the unsaved buffer was discarded. ` +
|
|
184
|
+
`Reload the app to see it.`
|
|
185
|
+
: `\nThat is what ${held} already holds, and there was no unsaved buffer, so nothing was written.`,
|
|
186
|
+
);
|
|
187
|
+
} else if (result.dryRun) {
|
|
188
|
+
lines.push(`\nDry run: nothing written under ${relative(root, result.colorsAndTypeDir)}.`);
|
|
189
|
+
}
|
|
190
|
+
return lines.join('\n');
|
|
191
|
+
}
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
// `live-tokens
|
|
1
|
+
// `live-tokens set-geometry` worker.
|
|
2
2
|
//
|
|
3
3
|
// Reads an ops file (JSON), applies it to every component's LIVE config via the
|
|
4
|
-
// compiled engine (dist-plugin/
|
|
4
|
+
// compiled engine (dist-plugin/setGeometry — the CLI never imports TS sources), and
|
|
5
5
|
// writes the result into that component's `_working.json` buffer: the same slot
|
|
6
6
|
// the editor's own edits land in, so an adjustment is an unsaved edit the user
|
|
7
7
|
// saves into a theme when they want to keep it. `default.json`, named preset
|
|
8
8
|
// files, themes, colors and type, and tokens.css are never touched.
|
|
9
9
|
|
|
10
|
-
import { existsSync, readFileSync,
|
|
10
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
11
11
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
12
12
|
import { fileURLToPath } from 'node:url';
|
|
13
13
|
|
|
14
|
+
import { componentNames, readActiveTheme, readLiveComponentConfigs } from './lib/liveState.mjs';
|
|
15
|
+
|
|
14
16
|
const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
15
|
-
const ENGINE = resolve(pkgRoot, 'dist-plugin/
|
|
16
|
-
const packageThemesDir = join(pkgRoot, 'src/live-tokens/data/themes');
|
|
17
|
+
const ENGINE = resolve(pkgRoot, 'dist-plugin/setGeometry/index.js');
|
|
17
18
|
|
|
18
19
|
const SOURCE_LABELS = {
|
|
19
20
|
working: 'your unsaved edits',
|
|
@@ -30,7 +31,7 @@ const SKIP_LABELS = [
|
|
|
30
31
|
async function loadEngine() {
|
|
31
32
|
if (!existsSync(ENGINE)) {
|
|
32
33
|
throw new Error(
|
|
33
|
-
`
|
|
34
|
+
`setGeometry engine not found at ${relative(process.cwd(), ENGINE)}. ` +
|
|
34
35
|
`Build the plugin first (npm run build:plugin).`,
|
|
35
36
|
);
|
|
36
37
|
}
|
|
@@ -41,10 +42,6 @@ function readJson(path) {
|
|
|
41
42
|
return JSON.parse(readFileSync(path, 'utf8'));
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
function readJsonIfExists(path) {
|
|
45
|
-
return existsSync(path) ? readJson(path) : null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
45
|
/** Successive ops can touch the same alias (soften, then pill the buttons);
|
|
49
46
|
* the report shows one entry per alias, first `from` to last `to`. */
|
|
50
47
|
function collapseChanges(changes) {
|
|
@@ -57,49 +54,20 @@ function collapseChanges(changes) {
|
|
|
57
54
|
return [...byVariable.values()].filter((c) => c.from !== c.to);
|
|
58
55
|
}
|
|
59
56
|
|
|
60
|
-
/**
|
|
61
|
-
*
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
const slug = readJsonIfExists(join(themesDir, '_active.json'))?.activeFile ?? 'default';
|
|
65
|
-
const theme =
|
|
66
|
-
readJsonIfExists(join(themesDir, `${slug}.json`)) ??
|
|
67
|
-
readJsonIfExists(join(packageThemesDir, `${slug}.json`));
|
|
68
|
-
return theme ? { slug, theme } : null;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Each component's live config, and whether a buffer or the open document
|
|
72
|
-
* answered. The document's own copy falls back to the shipped default for a
|
|
73
|
-
* component it somehow lacks, which reports as the document either way.
|
|
74
|
-
* Mirrors the dev server's `resolveLiveComponentConfig`, so the CLI adjusts
|
|
75
|
-
* what the page runs. */
|
|
76
|
-
function readLiveConfigs(dir, active) {
|
|
57
|
+
/** A config document names its own component, and a theme-embedded copy can
|
|
58
|
+
* omit it, so stamp it back on before the buffer is written from it. */
|
|
59
|
+
function stampedLiveConfigs(dir, active) {
|
|
60
|
+
const { configs: live, sources } = readLiveComponentConfigs(dir, active);
|
|
77
61
|
const configs = {};
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const comp = entry.name;
|
|
82
|
-
const componentDir = join(dir, comp);
|
|
83
|
-
const working = readJsonIfExists(join(componentDir, '_working.json'));
|
|
84
|
-
// Every theme carries every component by value, but this CLI reads the
|
|
85
|
-
// file straight off disk, ahead of any server-side fill — a theme
|
|
86
|
-
// written before this component existed can still omit it, so a missing
|
|
87
|
-
// entry falls back to the shipped default the same way the theme's own
|
|
88
|
-
// fill would.
|
|
89
|
-
const embedded =
|
|
90
|
-
active?.theme?.componentConfigs?.[comp] ?? readJsonIfExists(join(componentDir, 'default.json'));
|
|
91
|
-
const config = working ?? embedded;
|
|
92
|
-
if (!config) {
|
|
93
|
-
throw new Error(`component "${comp}": default.json is missing`);
|
|
94
|
-
}
|
|
95
|
-
configs[comp] = { ...config, component: comp };
|
|
96
|
-
sources[comp] = working ? 'working' : 'theme';
|
|
62
|
+
for (const comp of componentNames(dir)) {
|
|
63
|
+
if (!live[comp]) throw new Error(`component "${comp}": default.json is missing`);
|
|
64
|
+
configs[comp] = { ...live[comp], component: comp };
|
|
97
65
|
}
|
|
98
66
|
return { configs, sources };
|
|
99
67
|
}
|
|
100
68
|
|
|
101
69
|
/** `engine` is a test seam; the CLI always runs the compiled bundle. */
|
|
102
|
-
export async function
|
|
70
|
+
export async function runSetGeometry({
|
|
103
71
|
opsPath,
|
|
104
72
|
dryRun = false,
|
|
105
73
|
root = process.cwd(),
|
|
@@ -132,7 +100,7 @@ export async function runAdjust({
|
|
|
132
100
|
}
|
|
133
101
|
|
|
134
102
|
const active = readActiveTheme(themesDir ?? resolved?.themesDir);
|
|
135
|
-
const { configs, sources } =
|
|
103
|
+
const { configs, sources } = stampedLiveConfigs(dir, active);
|
|
136
104
|
const now = new Date().toISOString();
|
|
137
105
|
const { configs: next, report } = adjustAliases(configs, ops, now);
|
|
138
106
|
|
|
@@ -175,7 +143,7 @@ export async function runAdjust({
|
|
|
175
143
|
};
|
|
176
144
|
}
|
|
177
145
|
|
|
178
|
-
export function
|
|
146
|
+
export function formatSetGeometryResult(result) {
|
|
179
147
|
const root = process.cwd();
|
|
180
148
|
const lines = [];
|
|
181
149
|
const { components: changedCount, aliases, skips } = result.totals;
|
|
@@ -192,7 +160,7 @@ export function formatAdjustResult(result) {
|
|
|
192
160
|
}
|
|
193
161
|
if (result.ignoredName !== null) {
|
|
194
162
|
lines.push(
|
|
195
|
-
`Ignored "name": "${result.ignoredName}".
|
|
163
|
+
`Ignored "name": "${result.ignoredName}". set-geometry edits the open buffer, ` +
|
|
196
164
|
`so it writes no file of its own.`,
|
|
197
165
|
);
|
|
198
166
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// `live-tokens set-
|
|
1
|
+
// `live-tokens set-type` worker.
|
|
2
2
|
//
|
|
3
|
-
// Reads a pairing
|
|
4
|
-
// binds it to a font stack via the compiled engine (dist-plugin/
|
|
3
|
+
// Reads a pairing file (JSON), verifies each family against Google Fonts,
|
|
4
|
+
// binds it to a font stack via the compiled engine (dist-plugin/setType —
|
|
5
5
|
// the CLI never imports TS sources), and writes the result into
|
|
6
6
|
// `colors-and-type/_working.json`: the same buffer the editor's own font edits
|
|
7
7
|
// land in, so a retype is an unsaved edit the user saves into a theme when they
|
|
@@ -12,11 +12,11 @@ import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
|
12
12
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
13
13
|
import { fileURLToPath } from 'node:url';
|
|
14
14
|
|
|
15
|
+
import { readActiveTheme, readLiveColorsAndType, readSavedColorsAndType, sameContent } from './lib/liveState.mjs';
|
|
15
16
|
import { resolveTokensCssPath } from './migrate.mjs';
|
|
16
17
|
|
|
17
18
|
const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
18
|
-
const ENGINE = resolve(pkgRoot, 'dist-plugin/
|
|
19
|
-
const packageDataDir = join(pkgRoot, 'src/live-tokens/data');
|
|
19
|
+
const ENGINE = resolve(pkgRoot, 'dist-plugin/setType/index.js');
|
|
20
20
|
|
|
21
21
|
// Google serves a different stylesheet per user agent; ask as a browser would
|
|
22
22
|
// so the weight census matches what the page will actually load.
|
|
@@ -32,7 +32,7 @@ const SOURCE_LABELS = {
|
|
|
32
32
|
async function loadEngine() {
|
|
33
33
|
if (!existsSync(ENGINE)) {
|
|
34
34
|
throw new Error(
|
|
35
|
-
`
|
|
35
|
+
`setType engine not found at ${relative(process.cwd(), ENGINE)}. ` +
|
|
36
36
|
`Build the plugin first (npm run build:plugin).`,
|
|
37
37
|
);
|
|
38
38
|
}
|
|
@@ -43,53 +43,22 @@ function readJson(path) {
|
|
|
43
43
|
return JSON.parse(readFileSync(path, 'utf8'));
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
function readJsonIfExists(path) {
|
|
47
|
-
return existsSync(path) ? readJson(path) : null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
46
|
function browserFetch(url) {
|
|
51
47
|
return fetch(url, { headers: { 'user-agent': BROWSER_UA } });
|
|
52
48
|
}
|
|
53
49
|
|
|
54
|
-
/** The open theme, read the way every other door reads it: the local file
|
|
55
|
-
* first, then the copy the installed package ships. */
|
|
56
|
-
function readActiveTheme(themesDir) {
|
|
57
|
-
const slug = readJsonIfExists(join(themesDir, '_active.json'))?.activeFile ?? 'default';
|
|
58
|
-
const theme =
|
|
59
|
-
readJsonIfExists(join(themesDir, `${slug}.json`)) ??
|
|
60
|
-
readJsonIfExists(join(packageDataDir, 'themes', `${slug}.json`));
|
|
61
|
-
return theme ? { slug, theme } : null;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Mirrors the dev server's live colors-and-type resolution: the unsaved
|
|
65
|
-
* buffer, then the open theme's own copy, then the package default. */
|
|
66
|
-
function readLiveColorsAndType(colorsAndTypeDir, active) {
|
|
67
|
-
const working = readJsonIfExists(join(colorsAndTypeDir, '_working.json'));
|
|
68
|
-
if (working) return { colorsAndType: working, source: 'working' };
|
|
69
|
-
if (active?.theme?.colorsAndType) return { colorsAndType: active.theme.colorsAndType, source: 'theme' };
|
|
70
|
-
const shipped =
|
|
71
|
-
readJsonIfExists(join(colorsAndTypeDir, 'default.json')) ??
|
|
72
|
-
readJsonIfExists(join(packageDataDir, 'colors-and-type', 'default.json'));
|
|
73
|
-
if (!shipped) throw new Error(`no colors and type to read at ${colorsAndTypeDir}`);
|
|
74
|
-
return { colorsAndType: shipped, source: 'default' };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
50
|
function normalizeFace(slot, value) {
|
|
78
51
|
if (typeof value === 'string') return { name: value };
|
|
79
52
|
if (value && typeof value === 'object' && typeof value.name === 'string') {
|
|
80
53
|
return { name: value.name, ...(value.url ? { url: value.url } : {}) };
|
|
81
54
|
}
|
|
82
|
-
throw new Error(`
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function sameJson(a, b) {
|
|
86
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
55
|
+
throw new Error(`pairing slot "${slot}" must be a family name or { "name": "...", "url": "..." }`);
|
|
87
56
|
}
|
|
88
57
|
|
|
89
58
|
/** `engine` and `fetcher` are test seams; the CLI always runs the compiled
|
|
90
59
|
* bundle against the live API. */
|
|
91
|
-
export async function
|
|
92
|
-
|
|
60
|
+
export async function runSetType({
|
|
61
|
+
pairingPath,
|
|
93
62
|
dryRun = false,
|
|
94
63
|
verify = true,
|
|
95
64
|
root = process.cwd(),
|
|
@@ -110,21 +79,21 @@ export async function runSetFonts({
|
|
|
110
79
|
resolveDataDirs,
|
|
111
80
|
} = engine ?? (await loadEngine());
|
|
112
81
|
|
|
113
|
-
const
|
|
114
|
-
if (!existsSync(
|
|
115
|
-
let
|
|
82
|
+
const fullPath = resolve(root, pairingPath);
|
|
83
|
+
if (!existsSync(fullPath)) throw new Error(`pairing file not found at ${relative(root, fullPath)}`);
|
|
84
|
+
let input;
|
|
116
85
|
try {
|
|
117
|
-
|
|
86
|
+
input = readJson(fullPath);
|
|
118
87
|
} catch (err) {
|
|
119
|
-
throw new Error(`
|
|
88
|
+
throw new Error(`pairing file is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
120
89
|
}
|
|
121
90
|
|
|
122
|
-
const requested = SLOT_ORDER.filter((slot) =>
|
|
91
|
+
const requested = SLOT_ORDER.filter((slot) => input[slot] !== undefined).map((slot) => ({
|
|
123
92
|
slot,
|
|
124
|
-
face: normalizeFace(slot,
|
|
93
|
+
face: normalizeFace(slot, input[slot]),
|
|
125
94
|
}));
|
|
126
95
|
if (requested.length === 0) {
|
|
127
|
-
throw new Error(`
|
|
96
|
+
throw new Error(`pairing names no slot. Use one or more of: ${SLOT_ORDER.join(', ')}.`);
|
|
128
97
|
}
|
|
129
98
|
|
|
130
99
|
const resolved = colorsAndTypeDir && themesDir ? null : resolveDataDirs();
|
|
@@ -176,14 +145,11 @@ export async function runSetFonts({
|
|
|
176
145
|
}
|
|
177
146
|
|
|
178
147
|
const workingPath = join(colorsDir, '_working.json');
|
|
179
|
-
const savedColorsAndType =
|
|
180
|
-
active?.theme?.colorsAndType ??
|
|
181
|
-
readJsonIfExists(join(colorsDir, 'default.json')) ??
|
|
182
|
-
readJsonIfExists(join(packageDataDir, 'colors-and-type', 'default.json'));
|
|
148
|
+
const savedColorsAndType = readSavedColorsAndType(colorsDir, active);
|
|
183
149
|
|
|
184
150
|
// Returning the buffer to what the open theme already holds is a discard,
|
|
185
151
|
// not an edit — the same call the dev server's own PUT makes.
|
|
186
|
-
const backToSaved = savedColorsAndType !== null &&
|
|
152
|
+
const backToSaved = savedColorsAndType !== null && sameContent(next, savedColorsAndType);
|
|
187
153
|
let wrote = null;
|
|
188
154
|
if (!dryRun && report.changed) {
|
|
189
155
|
if (backToSaved) {
|
|
@@ -196,7 +162,7 @@ export async function runSetFonts({
|
|
|
196
162
|
}
|
|
197
163
|
|
|
198
164
|
return {
|
|
199
|
-
|
|
165
|
+
pairingPath: fullPath,
|
|
200
166
|
colorsAndTypeDir: colorsDir,
|
|
201
167
|
workingPath,
|
|
202
168
|
openTheme: active?.slug ?? null,
|
|
@@ -212,7 +178,7 @@ export async function runSetFonts({
|
|
|
212
178
|
};
|
|
213
179
|
}
|
|
214
180
|
|
|
215
|
-
export function
|
|
181
|
+
export function formatSetTypeResult(result) {
|
|
216
182
|
const root = process.cwd();
|
|
217
183
|
const lines = [];
|
|
218
184
|
|
|
@@ -826,6 +826,60 @@ var componentMigration_2026_09_02_sectiondividerDropTitleOutline = {
|
|
|
826
826
|
}
|
|
827
827
|
};
|
|
828
828
|
|
|
829
|
+
// src/editor/core/themes/migrations/2026-09-03-drop-legacy-component-keys.ts
|
|
830
|
+
var DROPPED3 = /* @__PURE__ */ new Set([
|
|
831
|
+
"--badge-trait-surface",
|
|
832
|
+
"--badge-trait-text",
|
|
833
|
+
"--badge-trait-text-font-family",
|
|
834
|
+
"--badge-trait-text-font-size",
|
|
835
|
+
"--badge-trait-text-font-weight",
|
|
836
|
+
"--badge-trait-text-line-height",
|
|
837
|
+
"--badge-trait-border",
|
|
838
|
+
"--badge-trait-border-width",
|
|
839
|
+
"--badge-trait-shadow",
|
|
840
|
+
"--sectiondivider-title",
|
|
841
|
+
"--sectiondivider-title-font-family",
|
|
842
|
+
"--sectiondivider-title-font-size",
|
|
843
|
+
"--sectiondivider-title-font-weight",
|
|
844
|
+
"--sectiondivider-title-line-height",
|
|
845
|
+
"--sectiondivider-title-border-width",
|
|
846
|
+
"--sectiondivider-title-stroke-color",
|
|
847
|
+
"--sectiondivider-description",
|
|
848
|
+
"--sectiondivider-description-font-family",
|
|
849
|
+
"--sectiondivider-description-font-size",
|
|
850
|
+
"--sectiondivider-description-font-weight",
|
|
851
|
+
"--sectiondivider-description-line-height",
|
|
852
|
+
"--dialog-primary-default-surface",
|
|
853
|
+
"--dialog-primary-default-text",
|
|
854
|
+
"--dialog-primary-default-border",
|
|
855
|
+
"--dialog-primary-default-border-width",
|
|
856
|
+
"--dialog-primary-hover-surface",
|
|
857
|
+
"--dialog-primary-hover-text",
|
|
858
|
+
"--dialog-primary-hover-border",
|
|
859
|
+
"--dialog-primary-hover-border-width",
|
|
860
|
+
"--dialog-secondary-default-text",
|
|
861
|
+
"--dialog-secondary-default-border",
|
|
862
|
+
"--dialog-secondary-default-border-width",
|
|
863
|
+
"--dialog-secondary-hover-surface",
|
|
864
|
+
"--dialog-secondary-hover-text",
|
|
865
|
+
"--dialog-secondary-hover-border",
|
|
866
|
+
"--dialog-secondary-hover-border-width"
|
|
867
|
+
]);
|
|
868
|
+
var colorsAndTypeMigration_2026_09_03_dropLegacyComponentKeys = {
|
|
869
|
+
id: "2026-09-03-drop-legacy-component-keys",
|
|
870
|
+
fromVersion: 7,
|
|
871
|
+
toVersion: 8,
|
|
872
|
+
appliesTo: "colors-and-type",
|
|
873
|
+
apply(rawVars) {
|
|
874
|
+
const out = {};
|
|
875
|
+
for (const [key, value] of Object.entries(rawVars)) {
|
|
876
|
+
if (DROPPED3.has(key)) continue;
|
|
877
|
+
out[key] = value;
|
|
878
|
+
}
|
|
879
|
+
return out;
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
|
|
829
883
|
// src/editor/core/themes/migrations/index.ts
|
|
830
884
|
var MIGRATIONS = [
|
|
831
885
|
colorsAndTypeMigration_2026_04_24_legacyKeysAndBgToCanvas,
|
|
@@ -860,7 +914,8 @@ var MIGRATIONS = [
|
|
|
860
914
|
componentMigration_2026_09_01_tintRename,
|
|
861
915
|
componentMigration_2026_09_01_tabbarActiveTint,
|
|
862
916
|
componentMigration_2026_09_01_gateSuffixEnabled,
|
|
863
|
-
componentMigration_2026_09_02_sectiondividerDropTitleOutline
|
|
917
|
+
componentMigration_2026_09_02_sectiondividerDropTitleOutline,
|
|
918
|
+
colorsAndTypeMigration_2026_09_03_dropLegacyComponentKeys
|
|
864
919
|
];
|
|
865
920
|
function countFor(kind) {
|
|
866
921
|
return MIGRATIONS.filter((m) => m.appliesTo === kind).length;
|
|
@@ -877,7 +932,6 @@ function runMigrations(kind, fileVersion, rawVars, meta = {}) {
|
|
|
877
932
|
}
|
|
878
933
|
|
|
879
934
|
export {
|
|
880
|
-
CURRENT_COLORS_AND_TYPE_SCHEMA_VERSION,
|
|
881
935
|
CURRENT_COMPONENT_SCHEMA_VERSION,
|
|
882
936
|
runMigrations
|
|
883
937
|
};
|
package/dist-plugin/index.cjs
CHANGED
|
@@ -1610,6 +1610,60 @@ var componentMigration_2026_09_02_sectiondividerDropTitleOutline = {
|
|
|
1610
1610
|
}
|
|
1611
1611
|
};
|
|
1612
1612
|
|
|
1613
|
+
// src/editor/core/themes/migrations/2026-09-03-drop-legacy-component-keys.ts
|
|
1614
|
+
var DROPPED3 = /* @__PURE__ */ new Set([
|
|
1615
|
+
"--badge-trait-surface",
|
|
1616
|
+
"--badge-trait-text",
|
|
1617
|
+
"--badge-trait-text-font-family",
|
|
1618
|
+
"--badge-trait-text-font-size",
|
|
1619
|
+
"--badge-trait-text-font-weight",
|
|
1620
|
+
"--badge-trait-text-line-height",
|
|
1621
|
+
"--badge-trait-border",
|
|
1622
|
+
"--badge-trait-border-width",
|
|
1623
|
+
"--badge-trait-shadow",
|
|
1624
|
+
"--sectiondivider-title",
|
|
1625
|
+
"--sectiondivider-title-font-family",
|
|
1626
|
+
"--sectiondivider-title-font-size",
|
|
1627
|
+
"--sectiondivider-title-font-weight",
|
|
1628
|
+
"--sectiondivider-title-line-height",
|
|
1629
|
+
"--sectiondivider-title-border-width",
|
|
1630
|
+
"--sectiondivider-title-stroke-color",
|
|
1631
|
+
"--sectiondivider-description",
|
|
1632
|
+
"--sectiondivider-description-font-family",
|
|
1633
|
+
"--sectiondivider-description-font-size",
|
|
1634
|
+
"--sectiondivider-description-font-weight",
|
|
1635
|
+
"--sectiondivider-description-line-height",
|
|
1636
|
+
"--dialog-primary-default-surface",
|
|
1637
|
+
"--dialog-primary-default-text",
|
|
1638
|
+
"--dialog-primary-default-border",
|
|
1639
|
+
"--dialog-primary-default-border-width",
|
|
1640
|
+
"--dialog-primary-hover-surface",
|
|
1641
|
+
"--dialog-primary-hover-text",
|
|
1642
|
+
"--dialog-primary-hover-border",
|
|
1643
|
+
"--dialog-primary-hover-border-width",
|
|
1644
|
+
"--dialog-secondary-default-text",
|
|
1645
|
+
"--dialog-secondary-default-border",
|
|
1646
|
+
"--dialog-secondary-default-border-width",
|
|
1647
|
+
"--dialog-secondary-hover-surface",
|
|
1648
|
+
"--dialog-secondary-hover-text",
|
|
1649
|
+
"--dialog-secondary-hover-border",
|
|
1650
|
+
"--dialog-secondary-hover-border-width"
|
|
1651
|
+
]);
|
|
1652
|
+
var colorsAndTypeMigration_2026_09_03_dropLegacyComponentKeys = {
|
|
1653
|
+
id: "2026-09-03-drop-legacy-component-keys",
|
|
1654
|
+
fromVersion: 7,
|
|
1655
|
+
toVersion: 8,
|
|
1656
|
+
appliesTo: "colors-and-type",
|
|
1657
|
+
apply(rawVars) {
|
|
1658
|
+
const out = {};
|
|
1659
|
+
for (const [key, value] of Object.entries(rawVars)) {
|
|
1660
|
+
if (DROPPED3.has(key)) continue;
|
|
1661
|
+
out[key] = value;
|
|
1662
|
+
}
|
|
1663
|
+
return out;
|
|
1664
|
+
}
|
|
1665
|
+
};
|
|
1666
|
+
|
|
1613
1667
|
// src/editor/core/themes/migrations/index.ts
|
|
1614
1668
|
var MIGRATIONS = [
|
|
1615
1669
|
colorsAndTypeMigration_2026_04_24_legacyKeysAndBgToCanvas,
|
|
@@ -1644,7 +1698,8 @@ var MIGRATIONS = [
|
|
|
1644
1698
|
componentMigration_2026_09_01_tintRename,
|
|
1645
1699
|
componentMigration_2026_09_01_tabbarActiveTint,
|
|
1646
1700
|
componentMigration_2026_09_01_gateSuffixEnabled,
|
|
1647
|
-
componentMigration_2026_09_02_sectiondividerDropTitleOutline
|
|
1701
|
+
componentMigration_2026_09_02_sectiondividerDropTitleOutline,
|
|
1702
|
+
colorsAndTypeMigration_2026_09_03_dropLegacyComponentKeys
|
|
1648
1703
|
];
|
|
1649
1704
|
function countFor(kind) {
|
|
1650
1705
|
return MIGRATIONS.filter((m) => m.appliesTo === kind).length;
|
|
@@ -3816,7 +3871,7 @@ ${lines.join("\n")}
|
|
|
3816
3871
|
return "corrupt";
|
|
3817
3872
|
}
|
|
3818
3873
|
function notAThemeError(fileName) {
|
|
3819
|
-
return `"${fileName}" is a colors-and-type file, not a theme. Before 0.48 those lived in themes/; move it to ${import_path4.default.basename(COLORS_AND_TYPE_DIR)}/ or delete it. To build a theme, use the theme
|
|
3874
|
+
return `"${fileName}" is a colors-and-type file, not a theme. Before 0.48 those lived in themes/; move it to ${import_path4.default.basename(COLORS_AND_TYPE_DIR)}/ or delete it. To build a theme, use the theme skill "create-theme".`;
|
|
3820
3875
|
}
|
|
3821
3876
|
function respondUnreadableTheme(res, fileName, missingError) {
|
|
3822
3877
|
switch (unreadableThemeReason(fileName)) {
|
|
@@ -4408,7 +4463,7 @@ ${lines.join("\n")}
|
|
|
4408
4463
|
const { theme, filled } = read;
|
|
4409
4464
|
if (!theme.colorsAndType) {
|
|
4410
4465
|
jsonResponse(res, 422, {
|
|
4411
|
-
error: 'This theme carries no colors and type. Build one with the theme
|
|
4466
|
+
error: 'This theme carries no colors and type. Build one with the theme skill "create-theme".'
|
|
4412
4467
|
});
|
|
4413
4468
|
return;
|
|
4414
4469
|
}
|