cronus-ui 0.6.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/LICENSE +21 -0
- package/README.md +90 -0
- package/dist/commands/add-page.d.ts +108 -0
- package/dist/commands/add-page.js +642 -0
- package/dist/commands/add.d.ts +9 -0
- package/dist/commands/add.js +114 -0
- package/dist/commands/ai.d.ts +14 -0
- package/dist/commands/ai.js +69 -0
- package/dist/commands/compose.d.ts +82 -0
- package/dist/commands/compose.js +403 -0
- package/dist/commands/diff.d.ts +8 -0
- package/dist/commands/diff.js +55 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.js +53 -0
- package/dist/commands/list.d.ts +7 -0
- package/dist/commands/list.js +28 -0
- package/dist/commands/theme.d.ts +23 -0
- package/dist/commands/theme.js +735 -0
- package/dist/commands/upgrade.d.ts +51 -0
- package/dist/commands/upgrade.js +840 -0
- package/dist/compose/data-slots.d.ts +71 -0
- package/dist/compose/data-slots.js +104 -0
- package/dist/compose/manifest.d.ts +90 -0
- package/dist/compose/manifest.js +224 -0
- package/dist/compose/plan.d.ts +164 -0
- package/dist/compose/plan.js +506 -0
- package/dist/compose/preview.d.ts +10 -0
- package/dist/compose/preview.js +48 -0
- package/dist/compose/reload.d.ts +56 -0
- package/dist/compose/reload.js +138 -0
- package/dist/compose/render.d.ts +123 -0
- package/dist/compose/render.js +404 -0
- package/dist/compose/templates.d.ts +22 -0
- package/dist/compose/templates.js +76 -0
- package/dist/compose.d.ts +10 -0
- package/dist/compose.js +8 -0
- package/dist/config.d.ts +94 -0
- package/dist/config.js +38 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +184 -0
- package/dist/registry.d.ts +59 -0
- package/dist/registry.js +96 -0
- package/dist/utils.d.ts +72 -0
- package/dist/utils.js +186 -0
- package/package.json +68 -0
- package/templates/apps/chat.json +44 -0
- package/templates/apps/finance.json +44 -0
- package/templates/apps/landing-agency.json +31 -0
- package/templates/apps/landing-agents.json +32 -0
- package/templates/apps/landing-broadcast.json +29 -0
- package/templates/apps/landing-care.json +28 -0
- package/templates/apps/landing-coverage.json +23 -0
- package/templates/apps/landing-docs.json +29 -0
- package/templates/apps/landing-glass.json +28 -0
- package/templates/apps/landing-ops.json +29 -0
- package/templates/apps/landing-premium.json +31 -0
- package/templates/apps/landing-secure.json +31 -0
- package/templates/apps/landing-shop.json +27 -0
- package/templates/apps/landing-studio.json +30 -0
- package/templates/apps/landing.json +23 -0
- package/templates/apps/mail.json +44 -0
- package/templates/apps/saas.json +64 -0
- package/templates/apps/store.json +75 -0
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { isAbsolute, join } from "node:path";
|
|
4
|
+
import { CONFIG_FILE, hasConfig, readConfig, writeConfig } from "../config.js";
|
|
5
|
+
import { log, writeFileEnsured } from "../utils.js";
|
|
6
|
+
/** The theme presets shipped by @cronus-ui/theme. */
|
|
7
|
+
export const THEME_PRESETS = ["aurora", "neutral", "midnight", "sunset", "emerald"];
|
|
8
|
+
/** The two color modes a preset can be pinned to. */
|
|
9
|
+
export const THEME_MODES = ["dark", "light"];
|
|
10
|
+
/** Root-layout locations we probe, in priority order (relative to cwd). */
|
|
11
|
+
const LAYOUT_CANDIDATES = [
|
|
12
|
+
"app/layout.tsx",
|
|
13
|
+
"src/app/layout.tsx",
|
|
14
|
+
"app/layout.jsx",
|
|
15
|
+
"src/app/layout.jsx",
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* A layout only counts if it actually mounts the theme runtime — otherwise
|
|
19
|
+
* rewriting attributes there would be pointless (and risk touching an unrelated
|
|
20
|
+
* `layout.tsx`). Either the anti-flash script tag or the provider qualifies.
|
|
21
|
+
*/
|
|
22
|
+
const THEME_MARKERS = ["CronusThemeScript", "CronusUIProvider"];
|
|
23
|
+
/**
|
|
24
|
+
* Rewrite every `<attr>="…"` occurrence to `value`, preserving the original
|
|
25
|
+
* quote style and only counting occurrences whose value actually changed (so a
|
|
26
|
+
* re-run on an already-set attribute reports zero changes — idempotent).
|
|
27
|
+
*/
|
|
28
|
+
function rewriteAttr(source, attr, value) {
|
|
29
|
+
let changed = 0;
|
|
30
|
+
const re = new RegExp(`${attr}=(["'])(.*?)\\1`, "g");
|
|
31
|
+
const content = source.replace(re, (match, quote, previous) => {
|
|
32
|
+
if (previous === value)
|
|
33
|
+
return match;
|
|
34
|
+
changed += 1;
|
|
35
|
+
return `${attr}=${quote}${value}${quote}`;
|
|
36
|
+
});
|
|
37
|
+
return { content, changed };
|
|
38
|
+
}
|
|
39
|
+
/** First existing candidate layout that mounts the Cronus theme runtime. */
|
|
40
|
+
async function findLayout(cwd) {
|
|
41
|
+
for (const rel of LAYOUT_CANDIDATES) {
|
|
42
|
+
const path = join(cwd, rel);
|
|
43
|
+
if (!existsSync(path))
|
|
44
|
+
continue;
|
|
45
|
+
const content = await readFile(path, "utf8");
|
|
46
|
+
if (THEME_MARKERS.some((marker) => content.includes(marker))) {
|
|
47
|
+
return { path, rel, content };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
export async function themeSet(options) {
|
|
53
|
+
const { cwd, name } = options;
|
|
54
|
+
if (!THEME_PRESETS.includes(name)) {
|
|
55
|
+
log.err(`Unknown theme "${name}". Choose one of: ${THEME_PRESETS.join(", ")}.`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (options.mode !== undefined && !THEME_MODES.includes(options.mode)) {
|
|
59
|
+
log.err(`Invalid mode "${options.mode}". Choose "${THEME_MODES.join('" or "')}".`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const mode = options.mode;
|
|
63
|
+
log.title(`Setting theme to ${name}${mode ? ` (${mode})` : ""}`);
|
|
64
|
+
const layout = await findLayout(cwd);
|
|
65
|
+
if (layout) {
|
|
66
|
+
const theme = rewriteAttr(layout.content, "defaultThemeName", name);
|
|
67
|
+
let next = theme.content;
|
|
68
|
+
let changed = theme.changed;
|
|
69
|
+
if (mode) {
|
|
70
|
+
const colorMode = rewriteAttr(next, "defaultModeName", mode);
|
|
71
|
+
next = colorMode.content;
|
|
72
|
+
changed += colorMode.changed;
|
|
73
|
+
}
|
|
74
|
+
if (next !== layout.content) {
|
|
75
|
+
await writeFile(layout.path, next, "utf8");
|
|
76
|
+
}
|
|
77
|
+
log.ok(`Updated ${layout.rel} (${changed} attribute(s) changed)`);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
log.warn("No app layout mounting <CronusThemeScript>/<CronusUIProvider> found — updating config only.");
|
|
81
|
+
}
|
|
82
|
+
if (hasConfig(cwd)) {
|
|
83
|
+
const config = await readConfig(cwd);
|
|
84
|
+
// Preserve the current mode when --mode is omitted; fall back to "dark" only
|
|
85
|
+
// when the config has never recorded a theme mode before.
|
|
86
|
+
const nextMode = mode ?? config.theme?.mode ?? "dark";
|
|
87
|
+
config.theme = { name, mode: nextMode };
|
|
88
|
+
await writeConfig(cwd, config);
|
|
89
|
+
log.ok(`Wrote theme to ${CONFIG_FILE}`);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
log.step(`No ${CONFIG_FILE} — skipped config update.`);
|
|
93
|
+
}
|
|
94
|
+
log.title("Done");
|
|
95
|
+
}
|
|
96
|
+
/** Every dark ramp shares the same white-alpha border trio. */
|
|
97
|
+
const DARK_BORDERS = {
|
|
98
|
+
border: "oklch(1 0 0 / 0.1)",
|
|
99
|
+
borderStrong: "oklch(1 0 0 / 0.16)",
|
|
100
|
+
borderSoft: "oklch(1 0 0 / 0.06)",
|
|
101
|
+
};
|
|
102
|
+
/** The Studio's 5 neutral base ramps (ids + values ported verbatim). */
|
|
103
|
+
const STUDIO_BASE_COLORS = {
|
|
104
|
+
neutral: {
|
|
105
|
+
dark: {
|
|
106
|
+
surfaceBase: "oklch(0.11 0 0)",
|
|
107
|
+
surfaceInset: "oklch(0.13 0 0)",
|
|
108
|
+
surfaceRaised: "oklch(0.16 0 0)",
|
|
109
|
+
surfaceOverlay: "oklch(0.2 0 0)",
|
|
110
|
+
surfaceElevated: "oklch(0.23 0 0)",
|
|
111
|
+
surfaceFloating: "oklch(0.27 0 0)",
|
|
112
|
+
fg: "oklch(0.93 0 0)",
|
|
113
|
+
fgSecondary: "oklch(0.7 0 0)",
|
|
114
|
+
fgTertiary: "oklch(0.62 0 0)",
|
|
115
|
+
fgMuted: "oklch(0.44 0 0)",
|
|
116
|
+
...DARK_BORDERS,
|
|
117
|
+
},
|
|
118
|
+
light: {
|
|
119
|
+
surfaceBase: "oklch(0.985 0 0)",
|
|
120
|
+
surfaceInset: "oklch(0.97 0 0)",
|
|
121
|
+
surfaceRaised: "oklch(1 0 0)",
|
|
122
|
+
surfaceOverlay: "oklch(0.96 0 0)",
|
|
123
|
+
surfaceElevated: "oklch(1 0 0)",
|
|
124
|
+
surfaceFloating: "oklch(1 0 0)",
|
|
125
|
+
fg: "oklch(0.145 0 0)",
|
|
126
|
+
fgSecondary: "oklch(0.43 0 0)",
|
|
127
|
+
fgTertiary: "oklch(0.556 0 0)",
|
|
128
|
+
fgMuted: "oklch(0.705 0 0)",
|
|
129
|
+
border: "oklch(0.915 0 0)",
|
|
130
|
+
borderStrong: "oklch(0.86 0 0)",
|
|
131
|
+
borderSoft: "oklch(0.94 0 0)",
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
slate: {
|
|
135
|
+
dark: {
|
|
136
|
+
surfaceBase: "oklch(0.11 0.012 252)",
|
|
137
|
+
surfaceInset: "oklch(0.13 0.013 252)",
|
|
138
|
+
surfaceRaised: "oklch(0.16 0.014 252)",
|
|
139
|
+
surfaceOverlay: "oklch(0.2 0.016 252)",
|
|
140
|
+
surfaceElevated: "oklch(0.23 0.017 252)",
|
|
141
|
+
surfaceFloating: "oklch(0.27 0.018 252)",
|
|
142
|
+
fg: "oklch(0.93 0.006 252)",
|
|
143
|
+
fgSecondary: "oklch(0.7 0.013 252)",
|
|
144
|
+
fgTertiary: "oklch(0.62 0.016 252)",
|
|
145
|
+
fgMuted: "oklch(0.44 0.016 252)",
|
|
146
|
+
...DARK_BORDERS,
|
|
147
|
+
},
|
|
148
|
+
light: {
|
|
149
|
+
surfaceBase: "oklch(0.985 0.004 252)",
|
|
150
|
+
surfaceInset: "oklch(0.97 0.006 252)",
|
|
151
|
+
surfaceRaised: "oklch(1 0 0)",
|
|
152
|
+
surfaceOverlay: "oklch(0.96 0.008 252)",
|
|
153
|
+
surfaceElevated: "oklch(1 0 0)",
|
|
154
|
+
surfaceFloating: "oklch(1 0 0)",
|
|
155
|
+
fg: "oklch(0.145 0.014 252)",
|
|
156
|
+
fgSecondary: "oklch(0.43 0.018 252)",
|
|
157
|
+
fgTertiary: "oklch(0.556 0.016 252)",
|
|
158
|
+
fgMuted: "oklch(0.705 0.014 252)",
|
|
159
|
+
border: "oklch(0.915 0.008 252)",
|
|
160
|
+
borderStrong: "oklch(0.86 0.012 252)",
|
|
161
|
+
borderSoft: "oklch(0.94 0.006 252)",
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
zinc: {
|
|
165
|
+
dark: {
|
|
166
|
+
surfaceBase: "oklch(0.11 0.005 286)",
|
|
167
|
+
surfaceInset: "oklch(0.13 0.005 286)",
|
|
168
|
+
surfaceRaised: "oklch(0.16 0.006 286)",
|
|
169
|
+
surfaceOverlay: "oklch(0.2 0.006 286)",
|
|
170
|
+
surfaceElevated: "oklch(0.23 0.006 286)",
|
|
171
|
+
surfaceFloating: "oklch(0.27 0.006 286)",
|
|
172
|
+
fg: "oklch(0.93 0.003 286)",
|
|
173
|
+
fgSecondary: "oklch(0.705 0.015 286)",
|
|
174
|
+
fgTertiary: "oklch(0.62 0.014 286)",
|
|
175
|
+
fgMuted: "oklch(0.442 0.013 286)",
|
|
176
|
+
...DARK_BORDERS,
|
|
177
|
+
},
|
|
178
|
+
light: {
|
|
179
|
+
surfaceBase: "oklch(0.985 0.001 286)",
|
|
180
|
+
surfaceInset: "oklch(0.97 0.002 286)",
|
|
181
|
+
surfaceRaised: "oklch(1 0 0)",
|
|
182
|
+
surfaceOverlay: "oklch(0.967 0.003 286)",
|
|
183
|
+
surfaceElevated: "oklch(1 0 0)",
|
|
184
|
+
surfaceFloating: "oklch(1 0 0)",
|
|
185
|
+
fg: "oklch(0.145 0.004 286)",
|
|
186
|
+
fgSecondary: "oklch(0.43 0.008 286)",
|
|
187
|
+
fgTertiary: "oklch(0.556 0.008 286)",
|
|
188
|
+
fgMuted: "oklch(0.705 0.006 286)",
|
|
189
|
+
border: "oklch(0.915 0.003 286)",
|
|
190
|
+
borderStrong: "oklch(0.86 0.005 286)",
|
|
191
|
+
borderSoft: "oklch(0.94 0.002 286)",
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
stone: {
|
|
195
|
+
dark: {
|
|
196
|
+
surfaceBase: "oklch(0.11 0.006 70)",
|
|
197
|
+
surfaceInset: "oklch(0.13 0.007 70)",
|
|
198
|
+
surfaceRaised: "oklch(0.16 0.008 70)",
|
|
199
|
+
surfaceOverlay: "oklch(0.2 0.009 70)",
|
|
200
|
+
surfaceElevated: "oklch(0.23 0.01 70)",
|
|
201
|
+
surfaceFloating: "oklch(0.27 0.011 70)",
|
|
202
|
+
fg: "oklch(0.93 0.004 70)",
|
|
203
|
+
fgSecondary: "oklch(0.7 0.009 70)",
|
|
204
|
+
fgTertiary: "oklch(0.62 0.011 70)",
|
|
205
|
+
fgMuted: "oklch(0.44 0.011 70)",
|
|
206
|
+
...DARK_BORDERS,
|
|
207
|
+
},
|
|
208
|
+
light: {
|
|
209
|
+
surfaceBase: "oklch(0.985 0.003 70)",
|
|
210
|
+
surfaceInset: "oklch(0.97 0.004 70)",
|
|
211
|
+
surfaceRaised: "oklch(1 0 0)",
|
|
212
|
+
surfaceOverlay: "oklch(0.96 0.006 70)",
|
|
213
|
+
surfaceElevated: "oklch(1 0 0)",
|
|
214
|
+
surfaceFloating: "oklch(1 0 0)",
|
|
215
|
+
fg: "oklch(0.145 0.008 70)",
|
|
216
|
+
fgSecondary: "oklch(0.43 0.012 70)",
|
|
217
|
+
fgTertiary: "oklch(0.556 0.011 70)",
|
|
218
|
+
fgMuted: "oklch(0.705 0.009 70)",
|
|
219
|
+
border: "oklch(0.915 0.005 70)",
|
|
220
|
+
borderStrong: "oklch(0.86 0.008 70)",
|
|
221
|
+
borderSoft: "oklch(0.94 0.004 70)",
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
gray: {
|
|
225
|
+
dark: {
|
|
226
|
+
surfaceBase: "oklch(0.11 0.006 252)",
|
|
227
|
+
surfaceInset: "oklch(0.13 0.006 252)",
|
|
228
|
+
surfaceRaised: "oklch(0.16 0.007 252)",
|
|
229
|
+
surfaceOverlay: "oklch(0.2 0.008 252)",
|
|
230
|
+
surfaceElevated: "oklch(0.23 0.008 252)",
|
|
231
|
+
surfaceFloating: "oklch(0.27 0.009 252)",
|
|
232
|
+
fg: "oklch(0.93 0.003 252)",
|
|
233
|
+
fgSecondary: "oklch(0.7 0.008 252)",
|
|
234
|
+
fgTertiary: "oklch(0.62 0.009 252)",
|
|
235
|
+
fgMuted: "oklch(0.44 0.009 252)",
|
|
236
|
+
...DARK_BORDERS,
|
|
237
|
+
},
|
|
238
|
+
light: {
|
|
239
|
+
surfaceBase: "oklch(0.985 0.002 252)",
|
|
240
|
+
surfaceInset: "oklch(0.97 0.003 252)",
|
|
241
|
+
surfaceRaised: "oklch(1 0 0)",
|
|
242
|
+
surfaceOverlay: "oklch(0.96 0.004 252)",
|
|
243
|
+
surfaceElevated: "oklch(1 0 0)",
|
|
244
|
+
surfaceFloating: "oklch(1 0 0)",
|
|
245
|
+
fg: "oklch(0.145 0.006 252)",
|
|
246
|
+
fgSecondary: "oklch(0.43 0.009 252)",
|
|
247
|
+
fgTertiary: "oklch(0.556 0.009 252)",
|
|
248
|
+
fgMuted: "oklch(0.705 0.007 252)",
|
|
249
|
+
border: "oklch(0.915 0.004 252)",
|
|
250
|
+
borderStrong: "oklch(0.86 0.006 252)",
|
|
251
|
+
borderSoft: "oklch(0.94 0.003 252)",
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
/** The Studio's 8 brand colors. */
|
|
256
|
+
const STUDIO_BRANDS = {
|
|
257
|
+
sky: {
|
|
258
|
+
primary: "oklch(0.685 0.169 237.3)",
|
|
259
|
+
accent: "oklch(0.715 0.143 215.2)",
|
|
260
|
+
primaryLight: "oklch(0.54 0.165 245)",
|
|
261
|
+
accentLight: "oklch(0.55 0.13 218)",
|
|
262
|
+
primaryForeground: "oklch(0.145 0 0)",
|
|
263
|
+
},
|
|
264
|
+
violet: {
|
|
265
|
+
primary: "oklch(0.66 0.2 292)",
|
|
266
|
+
accent: "oklch(0.7 0.16 305)",
|
|
267
|
+
primaryLight: "oklch(0.52 0.21 292)",
|
|
268
|
+
accentLight: "oklch(0.55 0.18 305)",
|
|
269
|
+
primaryForeground: "oklch(0.985 0 0)",
|
|
270
|
+
},
|
|
271
|
+
emerald: {
|
|
272
|
+
primary: "oklch(0.715 0.155 162.5)",
|
|
273
|
+
accent: "oklch(0.74 0.14 174)",
|
|
274
|
+
primaryLight: "oklch(0.54 0.13 163)",
|
|
275
|
+
accentLight: "oklch(0.56 0.115 175)",
|
|
276
|
+
primaryForeground: "oklch(0.985 0 0)",
|
|
277
|
+
},
|
|
278
|
+
amber: {
|
|
279
|
+
primary: "oklch(0.769 0.166 70.08)",
|
|
280
|
+
accent: "oklch(0.81 0.145 84)",
|
|
281
|
+
primaryLight: "oklch(0.58 0.13 62)",
|
|
282
|
+
accentLight: "oklch(0.62 0.12 78)",
|
|
283
|
+
primaryForeground: "oklch(0.205 0.04 70)",
|
|
284
|
+
},
|
|
285
|
+
rose: {
|
|
286
|
+
primary: "oklch(0.645 0.222 16.44)",
|
|
287
|
+
accent: "oklch(0.7 0.18 358)",
|
|
288
|
+
primaryLight: "oklch(0.53 0.2 18)",
|
|
289
|
+
accentLight: "oklch(0.55 0.17 0)",
|
|
290
|
+
primaryForeground: "oklch(0.985 0 0)",
|
|
291
|
+
},
|
|
292
|
+
blue: {
|
|
293
|
+
primary: "oklch(0.62 0.19 259)",
|
|
294
|
+
accent: "oklch(0.68 0.16 248)",
|
|
295
|
+
primaryLight: "oklch(0.5 0.2 261)",
|
|
296
|
+
accentLight: "oklch(0.54 0.17 250)",
|
|
297
|
+
primaryForeground: "oklch(0.985 0 0)",
|
|
298
|
+
},
|
|
299
|
+
orange: {
|
|
300
|
+
primary: "oklch(0.7 0.19 41)",
|
|
301
|
+
accent: "oklch(0.75 0.16 55)",
|
|
302
|
+
primaryLight: "oklch(0.56 0.18 38)",
|
|
303
|
+
accentLight: "oklch(0.58 0.155 52)",
|
|
304
|
+
primaryForeground: "oklch(0.985 0 0)",
|
|
305
|
+
},
|
|
306
|
+
teal: {
|
|
307
|
+
primary: "oklch(0.72 0.12 190)",
|
|
308
|
+
accent: "oklch(0.76 0.11 200)",
|
|
309
|
+
primaryLight: "oklch(0.55 0.1 190)",
|
|
310
|
+
accentLight: "oklch(0.57 0.095 200)",
|
|
311
|
+
primaryForeground: "oklch(0.985 0 0)",
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
/** The Studio's 4 chart palettes — exactly 5 series colors each. */
|
|
315
|
+
const STUDIO_CHARTS = {
|
|
316
|
+
brand: [
|
|
317
|
+
"oklch(0.685 0.169 237.3)",
|
|
318
|
+
"oklch(0.715 0.143 215.2)",
|
|
319
|
+
"oklch(0.66 0.2 292)",
|
|
320
|
+
"oklch(0.72 0.12 190)",
|
|
321
|
+
"oklch(0.62 0.19 259)",
|
|
322
|
+
],
|
|
323
|
+
neutral: [
|
|
324
|
+
"oklch(0.3 0 0)",
|
|
325
|
+
"oklch(0.44 0 0)",
|
|
326
|
+
"oklch(0.58 0 0)",
|
|
327
|
+
"oklch(0.72 0 0)",
|
|
328
|
+
"oklch(0.85 0 0)",
|
|
329
|
+
],
|
|
330
|
+
vivid: [
|
|
331
|
+
"oklch(0.645 0.222 16.44)",
|
|
332
|
+
"oklch(0.7 0.19 41)",
|
|
333
|
+
"oklch(0.769 0.166 70.08)",
|
|
334
|
+
"oklch(0.715 0.155 162.5)",
|
|
335
|
+
"oklch(0.66 0.2 292)",
|
|
336
|
+
],
|
|
337
|
+
warm: [
|
|
338
|
+
"oklch(0.82 0.13 88)",
|
|
339
|
+
"oklch(0.769 0.166 70.08)",
|
|
340
|
+
"oklch(0.71 0.18 50)",
|
|
341
|
+
"oklch(0.67 0.2 32)",
|
|
342
|
+
"oklch(0.64 0.21 12)",
|
|
343
|
+
],
|
|
344
|
+
};
|
|
345
|
+
/** The Studio's 6 font choices — id → CSS font-family stack. */
|
|
346
|
+
const STUDIO_FONTS = {
|
|
347
|
+
geist: 'Geist, "SF Pro Text", "SF Pro Display", system-ui, sans-serif',
|
|
348
|
+
inter: 'Inter, "SF Pro Text", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
349
|
+
system: '-apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Segoe UI", system-ui, sans-serif',
|
|
350
|
+
grotesk: '"Space Grotesk", "SF Pro Display", Inter, system-ui, sans-serif',
|
|
351
|
+
serif: 'Charter, "Iowan Old Style", "New York", Georgia, ui-serif, serif',
|
|
352
|
+
mono: '"SF Mono", "JetBrains Mono", ui-monospace, Menlo, Consolas, monospace',
|
|
353
|
+
};
|
|
354
|
+
/** The Studio's DEFAULT_CONFIG (the "Nova" look) — fields omitted from a permalink mean these. */
|
|
355
|
+
const STUDIO_DEFAULTS = {
|
|
356
|
+
mode: "dark",
|
|
357
|
+
baseColor: "neutral",
|
|
358
|
+
brand: "sky",
|
|
359
|
+
chart: "brand",
|
|
360
|
+
headingFont: "grotesk",
|
|
361
|
+
bodyFont: "inter",
|
|
362
|
+
radius: 14,
|
|
363
|
+
};
|
|
364
|
+
/**
|
|
365
|
+
* Ported subset of the @cronus-ui/tokens `cssVarMap` — exactly the tokens a
|
|
366
|
+
* Studio theme overrides. Key order here IS the emitted CSS order.
|
|
367
|
+
*/
|
|
368
|
+
const STUDIO_CSS_VARS = {
|
|
369
|
+
primary: "--cronus-primary",
|
|
370
|
+
primaryForeground: "--cronus-primary-foreground",
|
|
371
|
+
accent: "--cronus-accent",
|
|
372
|
+
accentForeground: "--cronus-accent-foreground",
|
|
373
|
+
surfaceBase: "--cronus-surface-base",
|
|
374
|
+
surfaceInset: "--cronus-surface-inset",
|
|
375
|
+
surfaceRaised: "--cronus-surface-raised",
|
|
376
|
+
surfaceOverlay: "--cronus-surface-overlay",
|
|
377
|
+
surfaceElevated: "--cronus-surface-elevated",
|
|
378
|
+
surfaceFloating: "--cronus-surface-floating",
|
|
379
|
+
fg: "--cronus-fg",
|
|
380
|
+
fgSecondary: "--cronus-fg-secondary",
|
|
381
|
+
fgTertiary: "--cronus-fg-tertiary",
|
|
382
|
+
fgMuted: "--cronus-fg-muted",
|
|
383
|
+
border: "--cronus-border",
|
|
384
|
+
borderStrong: "--cronus-border-strong",
|
|
385
|
+
borderSoft: "--cronus-border-soft",
|
|
386
|
+
ring: "--cronus-ring",
|
|
387
|
+
info: "--cronus-info",
|
|
388
|
+
radius: "--cronus-radius",
|
|
389
|
+
fontDisplay: "--cronus-font-display",
|
|
390
|
+
fontSans: "--cronus-font-sans",
|
|
391
|
+
fontMono: "--cronus-font-mono",
|
|
392
|
+
chart1: "--cronus-chart-1",
|
|
393
|
+
chart2: "--cronus-chart-2",
|
|
394
|
+
chart3: "--cronus-chart-3",
|
|
395
|
+
chart4: "--cronus-chart-4",
|
|
396
|
+
chart5: "--cronus-chart-5",
|
|
397
|
+
shadowGlow: "--cronus-shadow-glow",
|
|
398
|
+
};
|
|
399
|
+
/** The base preset `theme add` pins under the overrides — mirrors the Studio's provider export. */
|
|
400
|
+
const STUDIO_BASE_THEME = "neutral";
|
|
401
|
+
/**
|
|
402
|
+
* Selector for the generated block. The tokens stylesheet sets theme values on
|
|
403
|
+
* `[data-cronus-theme]` and mode values on `[data-cronus-theme][data-cronus-mode]`
|
|
404
|
+
* (specificity 0,2,0), so a bare `:root` block would lose to the mode block.
|
|
405
|
+
* `:root[data-cronus-theme]` ties that specificity and wins by source order —
|
|
406
|
+
* the consumer's globals rules always follow their `@import`s.
|
|
407
|
+
*/
|
|
408
|
+
const OVERRIDES_SELECTOR = ":root[data-cronus-theme]";
|
|
409
|
+
export const OVERRIDES_BEGIN = "/* cronus-ui theme overrides — BEGIN (generated by `cronus-ui theme add`; re-runs replace this block) */";
|
|
410
|
+
export const OVERRIDES_END = "/* cronus-ui theme overrides — END */";
|
|
411
|
+
/** Globals-CSS locations probed (relative to cwd) when `--css` is not given. */
|
|
412
|
+
const CSS_CANDIDATES = [
|
|
413
|
+
"app/globals.css",
|
|
414
|
+
"src/app/globals.css",
|
|
415
|
+
"styles/globals.css",
|
|
416
|
+
"app/global.css",
|
|
417
|
+
"src/styles/globals.css",
|
|
418
|
+
];
|
|
419
|
+
/** Safety-net lookup: the config is validated upstream, but never index blindly. */
|
|
420
|
+
function studioEntry(record, id, label) {
|
|
421
|
+
const entry = record[id];
|
|
422
|
+
if (!entry)
|
|
423
|
+
throw new Error(`Unknown ${label}: "${id}".`);
|
|
424
|
+
return entry;
|
|
425
|
+
}
|
|
426
|
+
/** `id` if it names a known entry (own key only — no prototype hits), else undefined. */
|
|
427
|
+
function pickKnownId(id, known) {
|
|
428
|
+
return typeof id === "string" && Object.hasOwn(known, id) ? id : undefined;
|
|
429
|
+
}
|
|
430
|
+
function requireKnownId(id, known, label) {
|
|
431
|
+
const picked = pickKnownId(id, known);
|
|
432
|
+
if (!picked) {
|
|
433
|
+
const got = typeof id === "string" ? `"${id}"` : "missing";
|
|
434
|
+
throw new Error(`Unknown ${label}: ${got}. Known: ${Object.keys(known).join(", ")}.`);
|
|
435
|
+
}
|
|
436
|
+
return picked;
|
|
437
|
+
}
|
|
438
|
+
function requireRadius(value) {
|
|
439
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 28) {
|
|
440
|
+
throw new Error("Theme radius must be a number between 0 and 28.");
|
|
441
|
+
}
|
|
442
|
+
return value;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* The freeform primary/accent colors land verbatim in the consumer's CSS, so
|
|
446
|
+
* gate them to a plausible CSS color: short, no `;`/`{`/`}`/`*` (rules out
|
|
447
|
+
* declaration breakouts and comment tricks) and no `url(`.
|
|
448
|
+
*/
|
|
449
|
+
function isSafeCssColor(value) {
|
|
450
|
+
return value.length <= 64 && /^[a-z0-9#%(),./\s-]+$/i.test(value) && !/url\s*\(/i.test(value);
|
|
451
|
+
}
|
|
452
|
+
function requireOptionalCssColor(value, field) {
|
|
453
|
+
if (value === undefined || value === null)
|
|
454
|
+
return undefined;
|
|
455
|
+
if (typeof value !== "string")
|
|
456
|
+
throw new Error(`Theme ${field} must be a string CSS color.`);
|
|
457
|
+
const trimmed = value.trim();
|
|
458
|
+
if (!trimmed)
|
|
459
|
+
return undefined;
|
|
460
|
+
if (!isSafeCssColor(trimmed)) {
|
|
461
|
+
throw new Error(`Theme ${field} does not look like a plain CSS color: ${JSON.stringify(value)}`);
|
|
462
|
+
}
|
|
463
|
+
return trimmed;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Decode a Studio permalink token (the `?c=` value): base64url → JSON with the
|
|
467
|
+
* Studio's short keys (m/b/r/c/h/y/i/d). Mirrors the Studio's own decode —
|
|
468
|
+
* unknown or malformed fields fall back to the defaults — but errors when
|
|
469
|
+
* nothing applies, since "apply the defaults" is never what the user meant.
|
|
470
|
+
*/
|
|
471
|
+
function configFromPermalinkToken(rawToken) {
|
|
472
|
+
const token = rawToken.trim().replace(/^c=/, "");
|
|
473
|
+
let payload;
|
|
474
|
+
try {
|
|
475
|
+
if (!token || !/^[A-Za-z0-9_-]+$/.test(token))
|
|
476
|
+
throw new Error("not base64url");
|
|
477
|
+
const parsed = JSON.parse(Buffer.from(token, "base64url").toString("utf8"));
|
|
478
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
479
|
+
throw new Error("not an object");
|
|
480
|
+
}
|
|
481
|
+
payload = parsed;
|
|
482
|
+
}
|
|
483
|
+
catch {
|
|
484
|
+
throw new Error("Could not decode the payload — expected the base64url `c=` token from a Create Studio permalink (…/create?c=…), a theme JSON file, or that file's path.");
|
|
485
|
+
}
|
|
486
|
+
const config = { style: "Custom", ...STUDIO_DEFAULTS };
|
|
487
|
+
let applied = false;
|
|
488
|
+
if (payload.m === "light" || payload.m === "dark") {
|
|
489
|
+
config.mode = payload.m;
|
|
490
|
+
applied = true;
|
|
491
|
+
}
|
|
492
|
+
const baseColor = pickKnownId(payload.b, STUDIO_BASE_COLORS);
|
|
493
|
+
if (baseColor) {
|
|
494
|
+
config.baseColor = baseColor;
|
|
495
|
+
applied = true;
|
|
496
|
+
}
|
|
497
|
+
const brand = pickKnownId(payload.r, STUDIO_BRANDS);
|
|
498
|
+
if (brand) {
|
|
499
|
+
config.brand = brand;
|
|
500
|
+
applied = true;
|
|
501
|
+
}
|
|
502
|
+
const chart = pickKnownId(payload.c, STUDIO_CHARTS);
|
|
503
|
+
if (chart) {
|
|
504
|
+
config.chart = chart;
|
|
505
|
+
applied = true;
|
|
506
|
+
}
|
|
507
|
+
const headingFont = pickKnownId(payload.h, STUDIO_FONTS);
|
|
508
|
+
if (headingFont) {
|
|
509
|
+
config.headingFont = headingFont;
|
|
510
|
+
applied = true;
|
|
511
|
+
}
|
|
512
|
+
const bodyFont = pickKnownId(payload.y, STUDIO_FONTS);
|
|
513
|
+
if (bodyFont) {
|
|
514
|
+
config.bodyFont = bodyFont;
|
|
515
|
+
applied = true;
|
|
516
|
+
}
|
|
517
|
+
const radius = payload.d;
|
|
518
|
+
if (typeof radius === "number" && Number.isFinite(radius) && radius >= 0 && radius <= 28) {
|
|
519
|
+
config.radius = radius;
|
|
520
|
+
applied = true;
|
|
521
|
+
}
|
|
522
|
+
// `i` (icon library) is preview-only in the Studio — nothing to write here.
|
|
523
|
+
if (!applied) {
|
|
524
|
+
throw new Error("The payload decodes but carries no theme settings — customize the theme in the Create Studio so the URL shows `?c=…`, then copy the link again.");
|
|
525
|
+
}
|
|
526
|
+
return config;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Parse an exported theme JSON. Accepts both shapes the Studio produces: the
|
|
530
|
+
* "Export JSON" config (fields at the top level) and a saved preset
|
|
531
|
+
* (`{ name, config: { … } }`). Unlike the permalink path this validates
|
|
532
|
+
* strictly — a hand-edited file should fail loudly, not half-apply.
|
|
533
|
+
*/
|
|
534
|
+
function configFromJsonFile(raw, rel) {
|
|
535
|
+
let parsed;
|
|
536
|
+
try {
|
|
537
|
+
parsed = JSON.parse(raw);
|
|
538
|
+
}
|
|
539
|
+
catch {
|
|
540
|
+
throw new Error(`${rel} is not valid JSON.`);
|
|
541
|
+
}
|
|
542
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
543
|
+
throw new Error(`${rel} must contain a JSON object (a Create Studio theme export).`);
|
|
544
|
+
}
|
|
545
|
+
const record = parsed;
|
|
546
|
+
const nested = record.config;
|
|
547
|
+
const body = nested && typeof nested === "object" && !Array.isArray(nested)
|
|
548
|
+
? nested
|
|
549
|
+
: record;
|
|
550
|
+
const styleRaw = body.style ?? record.name;
|
|
551
|
+
const style = typeof styleRaw === "string" && styleRaw.trim() ? styleRaw.trim() : "Custom";
|
|
552
|
+
const mode = body.mode;
|
|
553
|
+
if (mode !== "light" && mode !== "dark") {
|
|
554
|
+
throw new Error('Theme mode must be "light" or "dark".');
|
|
555
|
+
}
|
|
556
|
+
const config = {
|
|
557
|
+
style,
|
|
558
|
+
mode,
|
|
559
|
+
baseColor: requireKnownId(body.baseColor, STUDIO_BASE_COLORS, "base color"),
|
|
560
|
+
brand: requireKnownId(body.brand, STUDIO_BRANDS, "brand color"),
|
|
561
|
+
chart: requireKnownId(body.chart, STUDIO_CHARTS, "chart palette"),
|
|
562
|
+
headingFont: requireKnownId(body.headingFont, STUDIO_FONTS, "heading font"),
|
|
563
|
+
bodyFont: requireKnownId(body.bodyFont, STUDIO_FONTS, "body font"),
|
|
564
|
+
radius: requireRadius(body.radius),
|
|
565
|
+
};
|
|
566
|
+
const primaryColor = requireOptionalCssColor(body.primaryColor, "primaryColor");
|
|
567
|
+
if (primaryColor)
|
|
568
|
+
config.primaryColor = primaryColor;
|
|
569
|
+
const accentColor = requireOptionalCssColor(body.accentColor, "accentColor");
|
|
570
|
+
if (accentColor)
|
|
571
|
+
config.accentColor = accentColor;
|
|
572
|
+
return config;
|
|
573
|
+
}
|
|
574
|
+
/** Resolve the `<source>` argument: permalink URL, theme JSON path, or bare token. */
|
|
575
|
+
async function resolveStudioSource(source, cwd) {
|
|
576
|
+
const trimmed = source.trim();
|
|
577
|
+
if (!trimmed) {
|
|
578
|
+
throw new Error("Pass a Create Studio permalink, a `c=` payload, or a theme JSON file.");
|
|
579
|
+
}
|
|
580
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
581
|
+
let url;
|
|
582
|
+
try {
|
|
583
|
+
url = new URL(trimmed);
|
|
584
|
+
}
|
|
585
|
+
catch {
|
|
586
|
+
throw new Error(`Not a valid URL: ${trimmed}`);
|
|
587
|
+
}
|
|
588
|
+
const token = url.searchParams.get("c");
|
|
589
|
+
if (!token) {
|
|
590
|
+
throw new Error("The link has no `?c=` payload. In the Create Studio the URL gains `?c=…` as soon as you customize the theme — copy it after tweaking.");
|
|
591
|
+
}
|
|
592
|
+
return { config: configFromPermalinkToken(token), origin: "Create Studio permalink" };
|
|
593
|
+
}
|
|
594
|
+
const filePath = isAbsolute(trimmed) ? trimmed : join(cwd, trimmed);
|
|
595
|
+
if (existsSync(filePath) || /\.json$/i.test(trimmed)) {
|
|
596
|
+
if (!existsSync(filePath))
|
|
597
|
+
throw new Error(`Theme file not found: ${trimmed}`);
|
|
598
|
+
const raw = await readFile(filePath, "utf8");
|
|
599
|
+
return { config: configFromJsonFile(raw, trimmed), origin: trimmed };
|
|
600
|
+
}
|
|
601
|
+
return { config: configFromPermalinkToken(trimmed), origin: "c= payload" };
|
|
602
|
+
}
|
|
603
|
+
/** Port of the Studio's `configToThemeOverrides` — config → the 29 override tokens. */
|
|
604
|
+
function computeStudioOverrides(config) {
|
|
605
|
+
const base = studioEntry(STUDIO_BASE_COLORS, config.baseColor, "base color");
|
|
606
|
+
const brand = studioEntry(STUDIO_BRANDS, config.brand, "brand color");
|
|
607
|
+
const chart = studioEntry(STUDIO_CHARTS, config.chart, "chart palette");
|
|
608
|
+
const display = studioEntry(STUDIO_FONTS, config.headingFont, "heading font");
|
|
609
|
+
const body = studioEntry(STUDIO_FONTS, config.bodyFont, "body font");
|
|
610
|
+
const mono = studioEntry(STUDIO_FONTS, "mono", "font");
|
|
611
|
+
const ramp = config.mode === "dark" ? base.dark : base.light;
|
|
612
|
+
const primary = config.primaryColor?.trim() || (config.mode === "dark" ? brand.primary : brand.primaryLight);
|
|
613
|
+
const accent = config.accentColor?.trim() || (config.mode === "dark" ? brand.accent : brand.accentLight);
|
|
614
|
+
return {
|
|
615
|
+
...ramp,
|
|
616
|
+
primary,
|
|
617
|
+
accent,
|
|
618
|
+
primaryForeground: brand.primaryForeground,
|
|
619
|
+
accentForeground: brand.primaryForeground,
|
|
620
|
+
ring: primary,
|
|
621
|
+
info: accent,
|
|
622
|
+
radius: `${config.radius}px`,
|
|
623
|
+
fontDisplay: display,
|
|
624
|
+
fontSans: body,
|
|
625
|
+
fontMono: mono,
|
|
626
|
+
chart1: chart[0],
|
|
627
|
+
chart2: chart[1],
|
|
628
|
+
chart3: chart[2],
|
|
629
|
+
chart4: chart[3],
|
|
630
|
+
chart5: chart[4],
|
|
631
|
+
shadowGlow: `0 18px 58px color-mix(in oklch, ${primary} 32%, transparent)`,
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
/** Port of `serializeOverrides` from @cronus-ui/tokens, framed by the markers. */
|
|
635
|
+
function serializeStudioOverrides(overrides) {
|
|
636
|
+
const lines = Object.keys(STUDIO_CSS_VARS).map((key) => ` ${STUDIO_CSS_VARS[key]}: ${overrides[key]};`);
|
|
637
|
+
return `${OVERRIDES_BEGIN}\n${OVERRIDES_SELECTOR} {\n${lines.join("\n")}\n}\n${OVERRIDES_END}`;
|
|
638
|
+
}
|
|
639
|
+
/** Replace an existing marked block, else append one — idempotent by content. */
|
|
640
|
+
function upsertOverridesBlock(source, framedBlock) {
|
|
641
|
+
const begin = source.indexOf(OVERRIDES_BEGIN);
|
|
642
|
+
const end = source.indexOf(OVERRIDES_END);
|
|
643
|
+
if (begin !== -1 && end !== -1 && end > begin) {
|
|
644
|
+
return source.slice(0, begin) + framedBlock + source.slice(end + OVERRIDES_END.length);
|
|
645
|
+
}
|
|
646
|
+
const sep = source.length === 0 ? "" : source.endsWith("\n") ? "\n" : "\n\n";
|
|
647
|
+
return `${source}${sep}${framedBlock}\n`;
|
|
648
|
+
}
|
|
649
|
+
function resolveCssTarget(cwd, cssOption) {
|
|
650
|
+
if (cssOption) {
|
|
651
|
+
return { path: isAbsolute(cssOption) ? cssOption : join(cwd, cssOption), rel: cssOption };
|
|
652
|
+
}
|
|
653
|
+
for (const rel of CSS_CANDIDATES) {
|
|
654
|
+
const path = join(cwd, rel);
|
|
655
|
+
if (existsSync(path))
|
|
656
|
+
return { path, rel };
|
|
657
|
+
}
|
|
658
|
+
return undefined;
|
|
659
|
+
}
|
|
660
|
+
export async function themeAdd(options) {
|
|
661
|
+
const { cwd, dryRun = false } = options;
|
|
662
|
+
let config;
|
|
663
|
+
let origin;
|
|
664
|
+
try {
|
|
665
|
+
({ config, origin } = await resolveStudioSource(options.source, cwd));
|
|
666
|
+
}
|
|
667
|
+
catch (error) {
|
|
668
|
+
log.err(error instanceof Error ? error.message : String(error));
|
|
669
|
+
process.exitCode = 1;
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
const overrides = computeStudioOverrides(config);
|
|
673
|
+
const framedBlock = serializeStudioOverrides(overrides);
|
|
674
|
+
const tokenKeys = Object.keys(STUDIO_CSS_VARS);
|
|
675
|
+
log.title(`${dryRun ? "Previewing" : "Applying"} Create Studio theme "${config.style}"`);
|
|
676
|
+
log.step(`Source: ${origin}`);
|
|
677
|
+
log.step(`Base theme "${STUDIO_BASE_THEME}", ${config.mode} mode, ${tokenKeys.length} token overrides:`);
|
|
678
|
+
log.step(tokenKeys.join(", "));
|
|
679
|
+
// 1. Pin the layout to the base preset + the theme's mode (same mechanism as `theme set`).
|
|
680
|
+
const layout = await findLayout(cwd);
|
|
681
|
+
if (layout) {
|
|
682
|
+
const theme = rewriteAttr(layout.content, "defaultThemeName", STUDIO_BASE_THEME);
|
|
683
|
+
const colorMode = rewriteAttr(theme.content, "defaultModeName", config.mode);
|
|
684
|
+
const changed = theme.changed + colorMode.changed;
|
|
685
|
+
if (dryRun) {
|
|
686
|
+
log.step(`Would update ${layout.rel} (${changed} attribute(s))`);
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
if (colorMode.content !== layout.content) {
|
|
690
|
+
await writeFile(layout.path, colorMode.content, "utf8");
|
|
691
|
+
}
|
|
692
|
+
log.ok(`Updated ${layout.rel} (${changed} attribute(s) changed)`);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
log.warn("No app layout mounting <CronusThemeScript>/<CronusUIProvider> found — skipped the theme/mode attributes.");
|
|
697
|
+
}
|
|
698
|
+
// 2. The marked overrides block in the globals CSS.
|
|
699
|
+
const cssTarget = resolveCssTarget(cwd, options.css);
|
|
700
|
+
if (!cssTarget) {
|
|
701
|
+
log.warn(`No globals CSS found (looked for ${CSS_CANDIDATES.join(", ")}). Re-run with --css <file>, or paste this block yourself:`);
|
|
702
|
+
console.log(`\n${framedBlock}\n`);
|
|
703
|
+
}
|
|
704
|
+
else if (dryRun) {
|
|
705
|
+
log.step(`Would write this block to ${cssTarget.rel} (replacing any previous one):`);
|
|
706
|
+
console.log(`\n${framedBlock}\n`);
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
const current = existsSync(cssTarget.path) ? await readFile(cssTarget.path, "utf8") : "";
|
|
710
|
+
const hadBlock = current.includes(OVERRIDES_BEGIN);
|
|
711
|
+
const next = upsertOverridesBlock(current, framedBlock);
|
|
712
|
+
if (next !== current)
|
|
713
|
+
await writeFileEnsured(cssTarget.path, next);
|
|
714
|
+
log.ok(hadBlock
|
|
715
|
+
? `Replaced the override block in ${cssTarget.rel}`
|
|
716
|
+
: `Wrote the override block to ${cssTarget.rel}`);
|
|
717
|
+
}
|
|
718
|
+
// 3. cronus-ui.json — the same record `theme set` keeps.
|
|
719
|
+
if (hasConfig(cwd)) {
|
|
720
|
+
if (dryRun) {
|
|
721
|
+
log.step(`Would record theme { name: "${STUDIO_BASE_THEME}", mode: "${config.mode}" } in ${CONFIG_FILE}`);
|
|
722
|
+
}
|
|
723
|
+
else {
|
|
724
|
+
const projectConfig = await readConfig(cwd);
|
|
725
|
+
projectConfig.theme = { name: STUDIO_BASE_THEME, mode: config.mode };
|
|
726
|
+
await writeConfig(cwd, projectConfig);
|
|
727
|
+
log.ok(`Wrote theme to ${CONFIG_FILE}`);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
else {
|
|
731
|
+
log.step(`No ${CONFIG_FILE} — skipped config update.`);
|
|
732
|
+
}
|
|
733
|
+
log.title(dryRun ? "Dry run — nothing written" : "Done");
|
|
734
|
+
}
|
|
735
|
+
//# sourceMappingURL=theme.js.map
|