@sudajs/cli 0.9.3 → 0.10.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/bin/suda-dev.js +2 -1
- package/bin/suda.js +4 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +176 -49
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/templates/theme/AGENTS.md +51 -9
- package/templates/theme/README.md +2 -0
- package/templates/theme/src/i18n.ts +13 -0
- package/templates/theme/src/layout.tsx +8 -6
- package/templates/theme/src/locales/en.json +69 -0
- package/templates/theme/src/sections.tsx +28 -27
package/bin/suda-dev.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
import pc from "picocolors";
|
|
5
6
|
|
|
6
7
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
7
8
|
const sourceEntry = path.resolve(scriptDir, "../src/index.ts");
|
|
@@ -10,6 +11,6 @@ try {
|
|
|
10
11
|
const mod = await import(pathToFileURL(sourceEntry).href);
|
|
11
12
|
await mod.main();
|
|
12
13
|
} catch (error) {
|
|
13
|
-
console.error(error instanceof Error ? error.message : error);
|
|
14
|
+
console.error(pc.red(error instanceof Error ? error.message : String(error)));
|
|
14
15
|
process.exitCode = 1;
|
|
15
16
|
}
|
package/bin/suda.js
CHANGED
|
@@ -5,6 +5,7 @@ import { createRequire } from "node:module";
|
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
|
+
import pc from "picocolors";
|
|
8
9
|
|
|
9
10
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
10
11
|
const sourceEntry = path.resolve(scriptDir, "../src/index.ts");
|
|
@@ -30,7 +31,7 @@ if (existsSync(sourceEntry)) {
|
|
|
30
31
|
);
|
|
31
32
|
|
|
32
33
|
if (result.error) {
|
|
33
|
-
console.error(result.error.message);
|
|
34
|
+
console.error(pc.red(result.error.message));
|
|
34
35
|
process.exit(1);
|
|
35
36
|
}
|
|
36
37
|
|
|
@@ -42,7 +43,7 @@ if (existsSync(sourceEntry)) {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
if (!existsSync(distEntry)) {
|
|
45
|
-
console.error("@sudajs/cli is not built yet. Run `pnpm --filter @sudajs/cli build` first.");
|
|
46
|
+
console.error(pc.red("@sudajs/cli is not built yet. Run `pnpm --filter @sudajs/cli build` first."));
|
|
46
47
|
process.exit(1);
|
|
47
48
|
}
|
|
48
49
|
|
|
@@ -50,6 +51,6 @@ const mod = await import(pathToFileURL(distEntry).href);
|
|
|
50
51
|
try {
|
|
51
52
|
await mod.main();
|
|
52
53
|
} catch (error) {
|
|
53
|
-
console.error(error instanceof Error ? error.message : error);
|
|
54
|
+
console.error(pc.red(error instanceof Error ? error.message : String(error)));
|
|
54
55
|
process.exitCode = 1;
|
|
55
56
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -90,6 +90,7 @@ declare function fetchJson<T>(url: string, options?: RequestInit & {
|
|
|
90
90
|
token?: string;
|
|
91
91
|
}): Promise<T>;
|
|
92
92
|
declare function validateTheme(root: string): Promise<ValidatedTheme>;
|
|
93
|
+
declare function validateThemeLocales(root: string): Promise<void>;
|
|
93
94
|
declare function toViteModuleUrl(root: string, absolutePath: string): string;
|
|
94
95
|
declare function buildTheme(root: string): Promise<ValidatedTheme>;
|
|
95
96
|
declare function finalizeTheme(root: string): Promise<ValidatedTheme>;
|
|
@@ -110,6 +111,7 @@ declare const __testUtils: {
|
|
|
110
111
|
slugifyThemeName: typeof slugifyThemeName;
|
|
111
112
|
validateThemeKey: typeof validateThemeKey;
|
|
112
113
|
validateThemeName: typeof validateThemeName;
|
|
114
|
+
validateThemeLocales: typeof validateThemeLocales;
|
|
113
115
|
toViteModuleUrl: typeof toViteModuleUrl;
|
|
114
116
|
updateRemotePageDraft: typeof updateRemotePageDraft;
|
|
115
117
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createHash } from 'crypto';
|
|
3
|
-
import fs, { writeFile, stat,
|
|
3
|
+
import fs, { writeFile, stat, readdir, readFile, mkdir, copyFile, rm } from 'fs/promises';
|
|
4
4
|
import { createRequire } from 'module';
|
|
5
5
|
import path2 from 'path';
|
|
6
6
|
import { createInterface } from 'readline/promises';
|
|
@@ -13,7 +13,35 @@ import { Command } from 'commander';
|
|
|
13
13
|
import { build } from 'esbuild';
|
|
14
14
|
import { z } from 'zod';
|
|
15
15
|
import os from 'os';
|
|
16
|
+
import pc from 'picocolors';
|
|
16
17
|
|
|
18
|
+
var style = {
|
|
19
|
+
success: pc.green,
|
|
20
|
+
warning: pc.yellow,
|
|
21
|
+
error: pc.red,
|
|
22
|
+
info: pc.cyan,
|
|
23
|
+
url: pc.cyan,
|
|
24
|
+
code: pc.bold,
|
|
25
|
+
value: pc.bold,
|
|
26
|
+
path: pc.magenta
|
|
27
|
+
};
|
|
28
|
+
function success(message) {
|
|
29
|
+
return style.success(message);
|
|
30
|
+
}
|
|
31
|
+
function warning(message) {
|
|
32
|
+
return style.warning(message);
|
|
33
|
+
}
|
|
34
|
+
function error(message) {
|
|
35
|
+
return style.error(message);
|
|
36
|
+
}
|
|
37
|
+
function info(message) {
|
|
38
|
+
return style.info(message);
|
|
39
|
+
}
|
|
40
|
+
function themeRef(key, version) {
|
|
41
|
+
return style.value(`${key}@${version}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/auth.ts
|
|
17
45
|
function getConfigPath() {
|
|
18
46
|
return path2.join(os.homedir(), ".config", "suda", "config.json");
|
|
19
47
|
}
|
|
@@ -78,7 +106,7 @@ async function cliAuthFetch(pathname, init = {}) {
|
|
|
78
106
|
}
|
|
79
107
|
async function login(host = "app.sudayun.cn") {
|
|
80
108
|
const baseUrl = `${protocolForHost(host)}://${host}`;
|
|
81
|
-
console.log(`Requesting device authorization from ${baseUrl}...`);
|
|
109
|
+
console.log(`Requesting device authorization from ${style.url(baseUrl)}...`);
|
|
82
110
|
const deviceRes = await fetch(`${baseUrl}/api/cli-auth/device`, {
|
|
83
111
|
method: "POST"
|
|
84
112
|
});
|
|
@@ -90,9 +118,9 @@ async function login(host = "app.sudayun.cn") {
|
|
|
90
118
|
console.log(`
|
|
91
119
|
Please open the following URL in your browser to authorize Suda CLI:
|
|
92
120
|
`);
|
|
93
|
-
console.log(` ${authUrl}
|
|
121
|
+
console.log(` ${style.url(authUrl)}
|
|
94
122
|
`);
|
|
95
|
-
console.log(`Your confirmation code is: ${userCode}
|
|
123
|
+
console.log(`Your confirmation code is: ${style.code(userCode)}
|
|
96
124
|
`);
|
|
97
125
|
try {
|
|
98
126
|
const open = (await import('open')).default;
|
|
@@ -102,7 +130,7 @@ Please open the following URL in your browser to authorize Suda CLI:
|
|
|
102
130
|
const pollInterval = (interval || 5) * 1e3;
|
|
103
131
|
const timeoutSeconds = expiresIn || 300;
|
|
104
132
|
const deadline = Date.now() + timeoutSeconds * 1e3;
|
|
105
|
-
console.log("Waiting for authorization...");
|
|
133
|
+
console.log(info("Waiting for authorization..."));
|
|
106
134
|
while (Date.now() < deadline) {
|
|
107
135
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
108
136
|
const pollRes = await fetch(`${baseUrl}/api/cli-auth/poll`, {
|
|
@@ -113,7 +141,7 @@ Please open the following URL in your browser to authorize Suda CLI:
|
|
|
113
141
|
const data = await pollRes.json();
|
|
114
142
|
if (pollRes.ok && data.status === "approved" && data.token) {
|
|
115
143
|
await writeAuthConfig({ sessionToken: data.token, host });
|
|
116
|
-
console.log("Successfully authorized!");
|
|
144
|
+
console.log(success("Successfully authorized!"));
|
|
117
145
|
return;
|
|
118
146
|
}
|
|
119
147
|
if (data.error === "authorization_pending") {
|
|
@@ -126,7 +154,7 @@ Please open the following URL in your browser to authorize Suda CLI:
|
|
|
126
154
|
async function status() {
|
|
127
155
|
const config = await readAuthConfig();
|
|
128
156
|
if (!config) {
|
|
129
|
-
console.log("Not logged in. Run `suda auth login` to authenticate.");
|
|
157
|
+
console.log(warning("Not logged in. Run `suda auth login` to authenticate."));
|
|
130
158
|
return;
|
|
131
159
|
}
|
|
132
160
|
let res;
|
|
@@ -134,45 +162,46 @@ async function status() {
|
|
|
134
162
|
res = await cliAuthFetch("/api/cli-auth/whoami");
|
|
135
163
|
} catch (err) {
|
|
136
164
|
if (err instanceof CliAuthExpiredError) {
|
|
137
|
-
console.log(err.message);
|
|
165
|
+
console.log(warning(err.message));
|
|
138
166
|
return;
|
|
139
167
|
}
|
|
140
|
-
console.error(`Failed to connect to ${config.host}.`);
|
|
168
|
+
console.error(error(`Failed to connect to ${config.host}.`));
|
|
141
169
|
return;
|
|
142
170
|
}
|
|
143
171
|
if (!res.ok) {
|
|
144
172
|
console.error(
|
|
145
|
-
`Failed to verify session (${res.status} ${res.statusText}). Try again later.`
|
|
173
|
+
error(`Failed to verify session (${res.status} ${res.statusText}). Try again later.`)
|
|
146
174
|
);
|
|
147
175
|
return;
|
|
148
176
|
}
|
|
149
177
|
const data = await res.json();
|
|
150
178
|
const label = data.user?.email ?? data.user?.name ?? "your account";
|
|
151
|
-
console.log(`Logged in as ${label} on ${config.host}`);
|
|
179
|
+
console.log(success(`Logged in as ${style.value(label)} on ${style.url(config.host)}`));
|
|
152
180
|
}
|
|
153
181
|
async function logout() {
|
|
154
182
|
const config = await readAuthConfig();
|
|
155
183
|
if (!config) {
|
|
156
|
-
console.log("Not logged in.");
|
|
184
|
+
console.log(warning("Not logged in."));
|
|
157
185
|
return;
|
|
158
186
|
}
|
|
159
187
|
try {
|
|
160
188
|
await cliAuthFetch("/api/cli-auth/revoke", { method: "POST" });
|
|
161
189
|
} catch (err) {
|
|
162
190
|
if (err instanceof CliAuthExpiredError) {
|
|
163
|
-
console.log("Logged out (token was already invalid).");
|
|
191
|
+
console.log(warning("Logged out (token was already invalid)."));
|
|
164
192
|
return;
|
|
165
193
|
}
|
|
166
194
|
console.warn(
|
|
167
|
-
|
|
195
|
+
warning(
|
|
196
|
+
`Could not contact ${config.host} to revoke token. The token will be cleared locally only.`
|
|
197
|
+
)
|
|
168
198
|
);
|
|
169
199
|
}
|
|
170
200
|
await clearAuthConfig();
|
|
171
|
-
console.log("Logged out successfully.");
|
|
201
|
+
console.log(success("Logged out successfully."));
|
|
172
202
|
}
|
|
173
203
|
|
|
174
204
|
// src/index.ts
|
|
175
|
-
var __dirname$1 = path2.dirname(fileURLToPath(import.meta.url));
|
|
176
205
|
var THEME_NAME_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
177
206
|
var THEME_KEY_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
178
207
|
var createPageDraftOutputSchema = z.discriminatedUnion("status", [
|
|
@@ -385,11 +414,12 @@ export const { createRoot, hydrateRoot, version } = ReactDOMClient;
|
|
|
385
414
|
}
|
|
386
415
|
};
|
|
387
416
|
}
|
|
388
|
-
function createThemeEngineRuntimeAliasPlugin() {
|
|
417
|
+
function createThemeEngineRuntimeAliasPlugin(root) {
|
|
389
418
|
return {
|
|
390
419
|
name: "suda-theme-engine-runtime-alias",
|
|
391
420
|
setup(build) {
|
|
392
|
-
const
|
|
421
|
+
const themeRequire = createRequire(path2.join(root, "package.json"));
|
|
422
|
+
const runtimeEntry = themeRequire.resolve("@sudajs/theme-engine/runtime");
|
|
393
423
|
build.onResolve({ filter: /^@sudajs\/theme-engine\/runtime$/ }, () => ({
|
|
394
424
|
path: runtimeEntry
|
|
395
425
|
}));
|
|
@@ -538,6 +568,7 @@ async function validateTheme(root) {
|
|
|
538
568
|
}
|
|
539
569
|
const module = await loadThemeModule(serverEntryPath);
|
|
540
570
|
validateThemeModule(module);
|
|
571
|
+
await validateThemeLocales(root);
|
|
541
572
|
const result = {
|
|
542
573
|
root,
|
|
543
574
|
module,
|
|
@@ -553,6 +584,73 @@ async function validateTheme(root) {
|
|
|
553
584
|
}
|
|
554
585
|
return result;
|
|
555
586
|
}
|
|
587
|
+
function isRecord(value) {
|
|
588
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
589
|
+
}
|
|
590
|
+
function collectLocaleKeys(value, pathSegments = [], keys = []) {
|
|
591
|
+
if (typeof value === "string") {
|
|
592
|
+
keys.push(pathSegments.join("."));
|
|
593
|
+
return keys;
|
|
594
|
+
}
|
|
595
|
+
if (!isRecord(value)) {
|
|
596
|
+
throw new Error(
|
|
597
|
+
`Theme locale value at ${pathSegments.join(".") || "<root>"} must be a string or object.`
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
for (const [key, child] of Object.entries(value)) {
|
|
601
|
+
collectLocaleKeys(child, [...pathSegments, key], keys);
|
|
602
|
+
}
|
|
603
|
+
return keys;
|
|
604
|
+
}
|
|
605
|
+
async function readLocaleJson(filePath) {
|
|
606
|
+
try {
|
|
607
|
+
return JSON.parse(await readFile(filePath, "utf8"));
|
|
608
|
+
} catch (error2) {
|
|
609
|
+
const reason = error2 instanceof Error ? error2.message : "invalid JSON";
|
|
610
|
+
throw new Error(`Invalid theme locale JSON at ${filePath}: ${reason}`);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
async function listLocaleFiles(localesDir) {
|
|
614
|
+
if (!await pathExists(localesDir)) {
|
|
615
|
+
return [];
|
|
616
|
+
}
|
|
617
|
+
const entries = await readdir(localesDir, { withFileTypes: true });
|
|
618
|
+
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
|
|
619
|
+
}
|
|
620
|
+
async function validateThemeLocales(root) {
|
|
621
|
+
const localesDir = path2.join(root, "dist", "locales");
|
|
622
|
+
const files = await listLocaleFiles(localesDir);
|
|
623
|
+
if (!files.includes("en.json")) {
|
|
624
|
+
throw new Error("Theme locales must include dist/locales/en.json.");
|
|
625
|
+
}
|
|
626
|
+
const referenceFile = "en.json";
|
|
627
|
+
const referenceMessages = await readLocaleJson(path2.join(localesDir, referenceFile));
|
|
628
|
+
const reference = {
|
|
629
|
+
file: referenceFile,
|
|
630
|
+
keys: new Set(collectLocaleKeys(referenceMessages).sort())
|
|
631
|
+
};
|
|
632
|
+
for (const file of files) {
|
|
633
|
+
if (file === referenceFile) {
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
const filePath = path2.join(localesDir, file);
|
|
637
|
+
const messages = await readLocaleJson(filePath);
|
|
638
|
+
const keys = new Set(collectLocaleKeys(messages).sort());
|
|
639
|
+
const expected = reference;
|
|
640
|
+
const missing = [...expected.keys].filter((key) => !keys.has(key));
|
|
641
|
+
const extra = [...keys].filter((key) => !expected.keys.has(key));
|
|
642
|
+
if (missing.length > 0 || extra.length > 0) {
|
|
643
|
+
const details = [
|
|
644
|
+
...missing.map((key) => ` - missing ${key}`),
|
|
645
|
+
...extra.map((key) => ` - extra ${key}`)
|
|
646
|
+
].join("\n");
|
|
647
|
+
throw new Error(
|
|
648
|
+
`Theme locale ${file} does not match ${expected.file} key set:
|
|
649
|
+
${details}`
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
556
654
|
var VITE_CONFIG_FILES = [
|
|
557
655
|
"vite.config.ts",
|
|
558
656
|
"vite.config.mts",
|
|
@@ -651,6 +749,7 @@ async function buildViteTheme(root) {
|
|
|
651
749
|
}
|
|
652
750
|
});
|
|
653
751
|
await copyThemeAssets(root);
|
|
752
|
+
await copyThemeLocales(root);
|
|
654
753
|
const stylesheetEntry = path2.join(root, ".suda-build", "styles-entry.ts");
|
|
655
754
|
await mkdir(path2.dirname(stylesheetEntry), { recursive: true });
|
|
656
755
|
await writeFile(stylesheetEntry, 'import "../src/styles.css";\n', "utf8");
|
|
@@ -685,6 +784,13 @@ async function copyThemeAssets(root) {
|
|
|
685
784
|
}
|
|
686
785
|
await copyDirectory(sourceAssets, path2.join(root, "dist", "assets"));
|
|
687
786
|
}
|
|
787
|
+
async function copyThemeLocales(root) {
|
|
788
|
+
const sourceLocales = path2.join(root, "src", "locales");
|
|
789
|
+
if (!await pathExists(sourceLocales)) {
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
await copyDirectory(sourceLocales, path2.join(root, "dist", "locales"));
|
|
793
|
+
}
|
|
688
794
|
async function buildClientRuntime(root, minify) {
|
|
689
795
|
const entryPoint = await findClientEntry(root);
|
|
690
796
|
if (!await pathExists(entryPoint)) {
|
|
@@ -692,7 +798,7 @@ async function buildClientRuntime(root, minify) {
|
|
|
692
798
|
}
|
|
693
799
|
await mkdir(path2.join(root, "dist"), { recursive: true });
|
|
694
800
|
const hostReactShimPlugin = createHostReactShimPlugin();
|
|
695
|
-
const themeEngineRuntimeAliasPlugin = createThemeEngineRuntimeAliasPlugin();
|
|
801
|
+
const themeEngineRuntimeAliasPlugin = createThemeEngineRuntimeAliasPlugin(root);
|
|
696
802
|
const themeDependencyResolverPlugin = createThemeDependencyResolverPlugin(root);
|
|
697
803
|
await build({
|
|
698
804
|
bundle: true,
|
|
@@ -862,6 +968,7 @@ var __testUtils = {
|
|
|
862
968
|
slugifyThemeName,
|
|
863
969
|
validateThemeKey,
|
|
864
970
|
validateThemeName,
|
|
971
|
+
validateThemeLocales,
|
|
865
972
|
toViteModuleUrl,
|
|
866
973
|
updateRemotePageDraft
|
|
867
974
|
};
|
|
@@ -872,20 +979,24 @@ async function runThemeCheck(theme) {
|
|
|
872
979
|
const ok = result.ok && footerIssues.length === 0 && previewIssues.length === 0;
|
|
873
980
|
const lines = [formatThemeCheckResult(result)];
|
|
874
981
|
if (footerIssues.length === 0 && result.ok) {
|
|
875
|
-
lines.push(
|
|
982
|
+
lines.push(
|
|
983
|
+
success("Footer contract check passed: whiteLabel and ICP metadata render correctly.")
|
|
984
|
+
);
|
|
876
985
|
} else if (footerIssues.length > 0) {
|
|
877
986
|
for (const issue of footerIssues) {
|
|
878
|
-
lines.push(` error layoutConfig.footerContract: ${issue}`);
|
|
987
|
+
lines.push(` ${error("error")} layoutConfig.footerContract: ${issue}`);
|
|
879
988
|
}
|
|
880
|
-
lines.push(`Footer contract check: ${footerIssues.length} error(s).`);
|
|
989
|
+
lines.push(error(`Footer contract check: ${footerIssues.length} error(s).`));
|
|
881
990
|
}
|
|
882
991
|
if (previewIssues.length === 0) {
|
|
883
|
-
lines.push(
|
|
992
|
+
lines.push(
|
|
993
|
+
success("Preview contract check passed: desktop, tablet, and mobile screenshots exist.")
|
|
994
|
+
);
|
|
884
995
|
} else {
|
|
885
996
|
for (const issue of previewIssues) {
|
|
886
|
-
lines.push(` error assets.preview: ${issue}`);
|
|
997
|
+
lines.push(` ${error("error")} assets.preview: ${issue}`);
|
|
887
998
|
}
|
|
888
|
-
lines.push(`Preview contract check: ${previewIssues.length} error(s).`);
|
|
999
|
+
lines.push(error(`Preview contract check: ${previewIssues.length} error(s).`));
|
|
889
1000
|
}
|
|
890
1001
|
const text = lines.join("\n");
|
|
891
1002
|
if (ok) {
|
|
@@ -900,8 +1011,8 @@ async function runPreviewContractCheck(theme) {
|
|
|
900
1011
|
for (const relativePath of REQUIRED_PREVIEW_ARTIFACTS) {
|
|
901
1012
|
const filePath = path2.join(theme.root, "dist", relativePath);
|
|
902
1013
|
try {
|
|
903
|
-
const
|
|
904
|
-
if (
|
|
1014
|
+
const info2 = await stat(filePath);
|
|
1015
|
+
if (info2.size <= 0) {
|
|
905
1016
|
issues.push(`${relativePath} is empty. Run \`suda theme capture\` and rebuild.`);
|
|
906
1017
|
}
|
|
907
1018
|
} catch {
|
|
@@ -912,7 +1023,7 @@ async function runPreviewContractCheck(theme) {
|
|
|
912
1023
|
}
|
|
913
1024
|
async function watchTheme(root, port) {
|
|
914
1025
|
const handle = await startViteDevPreviewServer(root, port);
|
|
915
|
-
console.log(`previewing Vite theme at ${handle.url}`);
|
|
1026
|
+
console.log(`previewing Vite theme at ${style.url(handle.url)}`);
|
|
916
1027
|
await new Promise(() => void 0);
|
|
917
1028
|
}
|
|
918
1029
|
async function startViteDevPreviewServer(root, port) {
|
|
@@ -990,9 +1101,9 @@ function createSudaPreviewVitePlugin(root) {
|
|
|
990
1101
|
);
|
|
991
1102
|
response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
992
1103
|
response.end(html);
|
|
993
|
-
} catch (
|
|
1104
|
+
} catch (error2) {
|
|
994
1105
|
response.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
|
|
995
|
-
response.end(
|
|
1106
|
+
response.end(error2 instanceof Error ? error2.stack : String(error2));
|
|
996
1107
|
}
|
|
997
1108
|
})();
|
|
998
1109
|
});
|
|
@@ -1238,7 +1349,7 @@ async function optimizePngIfAvailable(themeRoot, filePath) {
|
|
|
1238
1349
|
const sharp = await loadOptionalSharp(themeRoot);
|
|
1239
1350
|
if (!sharp) {
|
|
1240
1351
|
if (!warnedMissingSharp) {
|
|
1241
|
-
console.warn(formatMissingSharpWarning());
|
|
1352
|
+
console.warn(warning(formatMissingSharpWarning()));
|
|
1242
1353
|
warnedMissingSharp = true;
|
|
1243
1354
|
}
|
|
1244
1355
|
return;
|
|
@@ -1249,7 +1360,7 @@ async function optimizePngIfAvailable(themeRoot, filePath) {
|
|
|
1249
1360
|
} catch {
|
|
1250
1361
|
if (!warnedSharpOptimizeFailure) {
|
|
1251
1362
|
console.warn(
|
|
1252
|
-
"warning: PNG optimization failed. The screenshot was saved uncompressed."
|
|
1363
|
+
warning("warning: PNG optimization failed. The screenshot was saved uncompressed.")
|
|
1253
1364
|
);
|
|
1254
1365
|
warnedSharpOptimizeFailure = true;
|
|
1255
1366
|
}
|
|
@@ -1275,7 +1386,9 @@ async function captureScreenshot(root, options) {
|
|
|
1275
1386
|
await context.close();
|
|
1276
1387
|
}
|
|
1277
1388
|
await optimizePngIfAvailable(root, target.outputPath);
|
|
1278
|
-
console.log(
|
|
1389
|
+
console.log(
|
|
1390
|
+
success(`captured ${target.device} ${style.path(path2.relative(root, target.outputPath))}`)
|
|
1391
|
+
);
|
|
1279
1392
|
}
|
|
1280
1393
|
} finally {
|
|
1281
1394
|
await browser.close();
|
|
@@ -1491,8 +1604,8 @@ async function performConfirmedPageOperation(auth, input, fetcher = fetchJson) {
|
|
|
1491
1604
|
};
|
|
1492
1605
|
}
|
|
1493
1606
|
async function performActivateTheme(auth, input, fetcher = fetchJson) {
|
|
1494
|
-
const
|
|
1495
|
-
const impact = `This will activate theme "${
|
|
1607
|
+
const themeRef2 = input.themeVersion ? `${input.themeKey}@${input.themeVersion}` : input.themeKey;
|
|
1608
|
+
const impact = `This will activate theme "${themeRef2}" for project "${input.projectId}". Public pages will render with this theme after activation.`;
|
|
1496
1609
|
if (input.confirm !== true) {
|
|
1497
1610
|
return {
|
|
1498
1611
|
status: "needs_confirmation",
|
|
@@ -2059,7 +2172,9 @@ async function publishTheme(root, skipBuild, force) {
|
|
|
2059
2172
|
if (forceRes.ok) {
|
|
2060
2173
|
const { deletedObjects } = await forceRes.json();
|
|
2061
2174
|
console.log(
|
|
2062
|
-
|
|
2175
|
+
warning(
|
|
2176
|
+
`force-cleared ${themeRef(key, version)} (${deletedObjects ?? 0} objects removed). Republishing...`
|
|
2177
|
+
)
|
|
2063
2178
|
);
|
|
2064
2179
|
}
|
|
2065
2180
|
}
|
|
@@ -2098,7 +2213,9 @@ async function publishTheme(root, skipBuild, force) {
|
|
|
2098
2213
|
if (!putRes.ok) {
|
|
2099
2214
|
throw new Error(`Failed to upload ${file.relativePath}: ${putRes.statusText}`);
|
|
2100
2215
|
}
|
|
2101
|
-
console.log(
|
|
2216
|
+
console.log(
|
|
2217
|
+
success(`uploaded ${style.path(createThemeObjectKey(key, version, file.relativePath))}`)
|
|
2218
|
+
);
|
|
2102
2219
|
}
|
|
2103
2220
|
const bundleArtifactRelative = "index.js";
|
|
2104
2221
|
const previewArtifactRelative = "assets/preview/desktop.png";
|
|
@@ -2122,7 +2239,7 @@ async function publishTheme(root, skipBuild, force) {
|
|
|
2122
2239
|
if (!completeRes.ok) {
|
|
2123
2240
|
throw new Error(`Failed to complete publish: ${await formatHttpErrorBody(completeRes)}`);
|
|
2124
2241
|
}
|
|
2125
|
-
console.log(`published ${key
|
|
2242
|
+
console.log(success(`published ${themeRef(key, version)}`));
|
|
2126
2243
|
}
|
|
2127
2244
|
var cachedCliPackageVersions;
|
|
2128
2245
|
async function readCliPackageVersions() {
|
|
@@ -2222,22 +2339,22 @@ function createThemeInitPrompt(nameProvided) {
|
|
|
2222
2339
|
async function promptThemeName(rl) {
|
|
2223
2340
|
while (true) {
|
|
2224
2341
|
const answer = (await rl.question("Theme name: ")).trim();
|
|
2225
|
-
const
|
|
2226
|
-
if (
|
|
2342
|
+
const error2 = validateThemeName(answer);
|
|
2343
|
+
if (error2 === null) {
|
|
2227
2344
|
return answer;
|
|
2228
2345
|
}
|
|
2229
|
-
console.error(error);
|
|
2346
|
+
console.error(error(error2));
|
|
2230
2347
|
}
|
|
2231
2348
|
}
|
|
2232
2349
|
async function promptThemeKey(rl, defaultKey) {
|
|
2233
2350
|
while (true) {
|
|
2234
2351
|
const answer = (await rl.question(`Theme key (${defaultKey}): `)).trim();
|
|
2235
2352
|
const key = answer.length > 0 ? answer : defaultKey;
|
|
2236
|
-
const
|
|
2237
|
-
if (
|
|
2353
|
+
const error2 = validateThemeKey(key);
|
|
2354
|
+
if (error2 === null) {
|
|
2238
2355
|
return key;
|
|
2239
2356
|
}
|
|
2240
|
-
console.error(error);
|
|
2357
|
+
console.error(error(error2));
|
|
2241
2358
|
}
|
|
2242
2359
|
}
|
|
2243
2360
|
async function resolveThemeInitInput(name) {
|
|
@@ -2298,7 +2415,7 @@ async function initThemeWithKey(target, key) {
|
|
|
2298
2415
|
for (const relativePath of filesToReplace) {
|
|
2299
2416
|
await replaceTemplateTokens(path2.join(target, relativePath), replacements);
|
|
2300
2417
|
}
|
|
2301
|
-
console.log(`created theme scaffold at ${target}`);
|
|
2418
|
+
console.log(success(`created theme scaffold at ${style.path(target)}`));
|
|
2302
2419
|
}
|
|
2303
2420
|
function buildProgram() {
|
|
2304
2421
|
const program = new Command();
|
|
@@ -2310,16 +2427,22 @@ function buildProgram() {
|
|
|
2310
2427
|
});
|
|
2311
2428
|
theme.command("validate").description("Validate a built theme artifact.").option("--theme-root <path>", "Theme source/artifact root.").action(async (options) => {
|
|
2312
2429
|
const result = await validateTheme(resolveThemeRoot(options));
|
|
2313
|
-
console.log(
|
|
2430
|
+
console.log(
|
|
2431
|
+
success(`valid ${themeRef(result.module.manifest.key, result.module.manifest.version)}`)
|
|
2432
|
+
);
|
|
2314
2433
|
if (!result.clientEntryPath) {
|
|
2315
2434
|
console.warn(
|
|
2316
|
-
|
|
2435
|
+
warning(
|
|
2436
|
+
"warning: runtime.client.js is missing; editor runtime will not load until `suda theme build` runs."
|
|
2437
|
+
)
|
|
2317
2438
|
);
|
|
2318
2439
|
}
|
|
2319
2440
|
});
|
|
2320
2441
|
theme.command("build").description("Build a Vite Suda theme artifact.").option("--theme-root <path>", "Theme source/artifact root.").action(async (options) => {
|
|
2321
2442
|
const result = await buildTheme(resolveThemeRoot(options));
|
|
2322
|
-
console.log(
|
|
2443
|
+
console.log(
|
|
2444
|
+
success(`built ${themeRef(result.module.manifest.key, result.module.manifest.version)}`)
|
|
2445
|
+
);
|
|
2323
2446
|
const ok = await runThemeCheck(result);
|
|
2324
2447
|
if (!ok) {
|
|
2325
2448
|
throw new Error("AI metadata check failed. Fix the errors above.");
|
|
@@ -2327,7 +2450,11 @@ function buildProgram() {
|
|
|
2327
2450
|
});
|
|
2328
2451
|
theme.command("finalize").description("Finalize an existing Vite build into the Suda theme artifact contract.").option("--theme-root <path>", "Theme source/artifact root.").action(async (options) => {
|
|
2329
2452
|
const result = await finalizeTheme(resolveThemeRoot(options));
|
|
2330
|
-
console.log(
|
|
2453
|
+
console.log(
|
|
2454
|
+
success(
|
|
2455
|
+
`finalized ${themeRef(result.module.manifest.key, result.module.manifest.version)}`
|
|
2456
|
+
)
|
|
2457
|
+
);
|
|
2331
2458
|
});
|
|
2332
2459
|
theme.command("check").description(
|
|
2333
2460
|
"Run AI metadata checks on a built theme without rebuilding. Reports missing component-level ai.instructions as errors and weak content as warnings."
|