@sudajs/cli 0.9.4 → 0.10.1
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 +177 -45
- 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,41 +162,43 @@ 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
|
|
@@ -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,11 +784,20 @@ 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)) {
|
|
691
797
|
throw new Error(`Missing client entry: ${entryPoint}`);
|
|
692
798
|
}
|
|
799
|
+
const packageJson = JSON.parse(await readFile(path2.join(root, "package.json"), "utf8"));
|
|
800
|
+
const themeVersion = typeof packageJson.version === "string" ? packageJson.version : "0.0.0";
|
|
693
801
|
await mkdir(path2.join(root, "dist"), { recursive: true });
|
|
694
802
|
const hostReactShimPlugin = createHostReactShimPlugin();
|
|
695
803
|
const themeEngineRuntimeAliasPlugin = createThemeEngineRuntimeAliasPlugin(root);
|
|
@@ -710,6 +818,9 @@ async function buildClientRuntime(root, minify) {
|
|
|
710
818
|
minify,
|
|
711
819
|
outfile: path2.join(root, "dist", "runtime.client.js"),
|
|
712
820
|
platform: "browser",
|
|
821
|
+
define: {
|
|
822
|
+
__SUDA_THEME_VERSION__: JSON.stringify(themeVersion)
|
|
823
|
+
},
|
|
713
824
|
plugins: [hostReactShimPlugin, themeEngineRuntimeAliasPlugin, themeDependencyResolverPlugin],
|
|
714
825
|
sourcemap: false,
|
|
715
826
|
target: "es2022",
|
|
@@ -862,6 +973,7 @@ var __testUtils = {
|
|
|
862
973
|
slugifyThemeName,
|
|
863
974
|
validateThemeKey,
|
|
864
975
|
validateThemeName,
|
|
976
|
+
validateThemeLocales,
|
|
865
977
|
toViteModuleUrl,
|
|
866
978
|
updateRemotePageDraft
|
|
867
979
|
};
|
|
@@ -872,20 +984,24 @@ async function runThemeCheck(theme) {
|
|
|
872
984
|
const ok = result.ok && footerIssues.length === 0 && previewIssues.length === 0;
|
|
873
985
|
const lines = [formatThemeCheckResult(result)];
|
|
874
986
|
if (footerIssues.length === 0 && result.ok) {
|
|
875
|
-
lines.push(
|
|
987
|
+
lines.push(
|
|
988
|
+
success("Footer contract check passed: whiteLabel and ICP metadata render correctly.")
|
|
989
|
+
);
|
|
876
990
|
} else if (footerIssues.length > 0) {
|
|
877
991
|
for (const issue of footerIssues) {
|
|
878
|
-
lines.push(` error layoutConfig.footerContract: ${issue}`);
|
|
992
|
+
lines.push(` ${error("error")} layoutConfig.footerContract: ${issue}`);
|
|
879
993
|
}
|
|
880
|
-
lines.push(`Footer contract check: ${footerIssues.length} error(s).`);
|
|
994
|
+
lines.push(error(`Footer contract check: ${footerIssues.length} error(s).`));
|
|
881
995
|
}
|
|
882
996
|
if (previewIssues.length === 0) {
|
|
883
|
-
lines.push(
|
|
997
|
+
lines.push(
|
|
998
|
+
success("Preview contract check passed: desktop, tablet, and mobile screenshots exist.")
|
|
999
|
+
);
|
|
884
1000
|
} else {
|
|
885
1001
|
for (const issue of previewIssues) {
|
|
886
|
-
lines.push(` error assets.preview: ${issue}`);
|
|
1002
|
+
lines.push(` ${error("error")} assets.preview: ${issue}`);
|
|
887
1003
|
}
|
|
888
|
-
lines.push(`Preview contract check: ${previewIssues.length} error(s).`);
|
|
1004
|
+
lines.push(error(`Preview contract check: ${previewIssues.length} error(s).`));
|
|
889
1005
|
}
|
|
890
1006
|
const text = lines.join("\n");
|
|
891
1007
|
if (ok) {
|
|
@@ -900,8 +1016,8 @@ async function runPreviewContractCheck(theme) {
|
|
|
900
1016
|
for (const relativePath of REQUIRED_PREVIEW_ARTIFACTS) {
|
|
901
1017
|
const filePath = path2.join(theme.root, "dist", relativePath);
|
|
902
1018
|
try {
|
|
903
|
-
const
|
|
904
|
-
if (
|
|
1019
|
+
const info2 = await stat(filePath);
|
|
1020
|
+
if (info2.size <= 0) {
|
|
905
1021
|
issues.push(`${relativePath} is empty. Run \`suda theme capture\` and rebuild.`);
|
|
906
1022
|
}
|
|
907
1023
|
} catch {
|
|
@@ -912,7 +1028,7 @@ async function runPreviewContractCheck(theme) {
|
|
|
912
1028
|
}
|
|
913
1029
|
async function watchTheme(root, port) {
|
|
914
1030
|
const handle = await startViteDevPreviewServer(root, port);
|
|
915
|
-
console.log(`previewing Vite theme at ${handle.url}`);
|
|
1031
|
+
console.log(`previewing Vite theme at ${style.url(handle.url)}`);
|
|
916
1032
|
await new Promise(() => void 0);
|
|
917
1033
|
}
|
|
918
1034
|
async function startViteDevPreviewServer(root, port) {
|
|
@@ -990,9 +1106,9 @@ function createSudaPreviewVitePlugin(root) {
|
|
|
990
1106
|
);
|
|
991
1107
|
response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
992
1108
|
response.end(html);
|
|
993
|
-
} catch (
|
|
1109
|
+
} catch (error2) {
|
|
994
1110
|
response.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
|
|
995
|
-
response.end(
|
|
1111
|
+
response.end(error2 instanceof Error ? error2.stack : String(error2));
|
|
996
1112
|
}
|
|
997
1113
|
})();
|
|
998
1114
|
});
|
|
@@ -1238,7 +1354,7 @@ async function optimizePngIfAvailable(themeRoot, filePath) {
|
|
|
1238
1354
|
const sharp = await loadOptionalSharp(themeRoot);
|
|
1239
1355
|
if (!sharp) {
|
|
1240
1356
|
if (!warnedMissingSharp) {
|
|
1241
|
-
console.warn(formatMissingSharpWarning());
|
|
1357
|
+
console.warn(warning(formatMissingSharpWarning()));
|
|
1242
1358
|
warnedMissingSharp = true;
|
|
1243
1359
|
}
|
|
1244
1360
|
return;
|
|
@@ -1249,7 +1365,7 @@ async function optimizePngIfAvailable(themeRoot, filePath) {
|
|
|
1249
1365
|
} catch {
|
|
1250
1366
|
if (!warnedSharpOptimizeFailure) {
|
|
1251
1367
|
console.warn(
|
|
1252
|
-
"warning: PNG optimization failed. The screenshot was saved uncompressed."
|
|
1368
|
+
warning("warning: PNG optimization failed. The screenshot was saved uncompressed.")
|
|
1253
1369
|
);
|
|
1254
1370
|
warnedSharpOptimizeFailure = true;
|
|
1255
1371
|
}
|
|
@@ -1275,7 +1391,9 @@ async function captureScreenshot(root, options) {
|
|
|
1275
1391
|
await context.close();
|
|
1276
1392
|
}
|
|
1277
1393
|
await optimizePngIfAvailable(root, target.outputPath);
|
|
1278
|
-
console.log(
|
|
1394
|
+
console.log(
|
|
1395
|
+
success(`captured ${target.device} ${style.path(path2.relative(root, target.outputPath))}`)
|
|
1396
|
+
);
|
|
1279
1397
|
}
|
|
1280
1398
|
} finally {
|
|
1281
1399
|
await browser.close();
|
|
@@ -1491,8 +1609,8 @@ async function performConfirmedPageOperation(auth, input, fetcher = fetchJson) {
|
|
|
1491
1609
|
};
|
|
1492
1610
|
}
|
|
1493
1611
|
async function performActivateTheme(auth, input, fetcher = fetchJson) {
|
|
1494
|
-
const
|
|
1495
|
-
const impact = `This will activate theme "${
|
|
1612
|
+
const themeRef2 = input.themeVersion ? `${input.themeKey}@${input.themeVersion}` : input.themeKey;
|
|
1613
|
+
const impact = `This will activate theme "${themeRef2}" for project "${input.projectId}". Public pages will render with this theme after activation.`;
|
|
1496
1614
|
if (input.confirm !== true) {
|
|
1497
1615
|
return {
|
|
1498
1616
|
status: "needs_confirmation",
|
|
@@ -2059,7 +2177,9 @@ async function publishTheme(root, skipBuild, force) {
|
|
|
2059
2177
|
if (forceRes.ok) {
|
|
2060
2178
|
const { deletedObjects } = await forceRes.json();
|
|
2061
2179
|
console.log(
|
|
2062
|
-
|
|
2180
|
+
warning(
|
|
2181
|
+
`force-cleared ${themeRef(key, version)} (${deletedObjects ?? 0} objects removed). Republishing...`
|
|
2182
|
+
)
|
|
2063
2183
|
);
|
|
2064
2184
|
}
|
|
2065
2185
|
}
|
|
@@ -2098,7 +2218,9 @@ async function publishTheme(root, skipBuild, force) {
|
|
|
2098
2218
|
if (!putRes.ok) {
|
|
2099
2219
|
throw new Error(`Failed to upload ${file.relativePath}: ${putRes.statusText}`);
|
|
2100
2220
|
}
|
|
2101
|
-
console.log(
|
|
2221
|
+
console.log(
|
|
2222
|
+
success(`uploaded ${style.path(createThemeObjectKey(key, version, file.relativePath))}`)
|
|
2223
|
+
);
|
|
2102
2224
|
}
|
|
2103
2225
|
const bundleArtifactRelative = "index.js";
|
|
2104
2226
|
const previewArtifactRelative = "assets/preview/desktop.png";
|
|
@@ -2122,7 +2244,7 @@ async function publishTheme(root, skipBuild, force) {
|
|
|
2122
2244
|
if (!completeRes.ok) {
|
|
2123
2245
|
throw new Error(`Failed to complete publish: ${await formatHttpErrorBody(completeRes)}`);
|
|
2124
2246
|
}
|
|
2125
|
-
console.log(`published ${key
|
|
2247
|
+
console.log(success(`published ${themeRef(key, version)}`));
|
|
2126
2248
|
}
|
|
2127
2249
|
var cachedCliPackageVersions;
|
|
2128
2250
|
async function readCliPackageVersions() {
|
|
@@ -2222,22 +2344,22 @@ function createThemeInitPrompt(nameProvided) {
|
|
|
2222
2344
|
async function promptThemeName(rl) {
|
|
2223
2345
|
while (true) {
|
|
2224
2346
|
const answer = (await rl.question("Theme name: ")).trim();
|
|
2225
|
-
const
|
|
2226
|
-
if (
|
|
2347
|
+
const error2 = validateThemeName(answer);
|
|
2348
|
+
if (error2 === null) {
|
|
2227
2349
|
return answer;
|
|
2228
2350
|
}
|
|
2229
|
-
console.error(error);
|
|
2351
|
+
console.error(error(error2));
|
|
2230
2352
|
}
|
|
2231
2353
|
}
|
|
2232
2354
|
async function promptThemeKey(rl, defaultKey) {
|
|
2233
2355
|
while (true) {
|
|
2234
2356
|
const answer = (await rl.question(`Theme key (${defaultKey}): `)).trim();
|
|
2235
2357
|
const key = answer.length > 0 ? answer : defaultKey;
|
|
2236
|
-
const
|
|
2237
|
-
if (
|
|
2358
|
+
const error2 = validateThemeKey(key);
|
|
2359
|
+
if (error2 === null) {
|
|
2238
2360
|
return key;
|
|
2239
2361
|
}
|
|
2240
|
-
console.error(error);
|
|
2362
|
+
console.error(error(error2));
|
|
2241
2363
|
}
|
|
2242
2364
|
}
|
|
2243
2365
|
async function resolveThemeInitInput(name) {
|
|
@@ -2298,7 +2420,7 @@ async function initThemeWithKey(target, key) {
|
|
|
2298
2420
|
for (const relativePath of filesToReplace) {
|
|
2299
2421
|
await replaceTemplateTokens(path2.join(target, relativePath), replacements);
|
|
2300
2422
|
}
|
|
2301
|
-
console.log(`created theme scaffold at ${target}`);
|
|
2423
|
+
console.log(success(`created theme scaffold at ${style.path(target)}`));
|
|
2302
2424
|
}
|
|
2303
2425
|
function buildProgram() {
|
|
2304
2426
|
const program = new Command();
|
|
@@ -2310,16 +2432,22 @@ function buildProgram() {
|
|
|
2310
2432
|
});
|
|
2311
2433
|
theme.command("validate").description("Validate a built theme artifact.").option("--theme-root <path>", "Theme source/artifact root.").action(async (options) => {
|
|
2312
2434
|
const result = await validateTheme(resolveThemeRoot(options));
|
|
2313
|
-
console.log(
|
|
2435
|
+
console.log(
|
|
2436
|
+
success(`valid ${themeRef(result.module.manifest.key, result.module.manifest.version)}`)
|
|
2437
|
+
);
|
|
2314
2438
|
if (!result.clientEntryPath) {
|
|
2315
2439
|
console.warn(
|
|
2316
|
-
|
|
2440
|
+
warning(
|
|
2441
|
+
"warning: runtime.client.js is missing; editor runtime will not load until `suda theme build` runs."
|
|
2442
|
+
)
|
|
2317
2443
|
);
|
|
2318
2444
|
}
|
|
2319
2445
|
});
|
|
2320
2446
|
theme.command("build").description("Build a Vite Suda theme artifact.").option("--theme-root <path>", "Theme source/artifact root.").action(async (options) => {
|
|
2321
2447
|
const result = await buildTheme(resolveThemeRoot(options));
|
|
2322
|
-
console.log(
|
|
2448
|
+
console.log(
|
|
2449
|
+
success(`built ${themeRef(result.module.manifest.key, result.module.manifest.version)}`)
|
|
2450
|
+
);
|
|
2323
2451
|
const ok = await runThemeCheck(result);
|
|
2324
2452
|
if (!ok) {
|
|
2325
2453
|
throw new Error("AI metadata check failed. Fix the errors above.");
|
|
@@ -2327,7 +2455,11 @@ function buildProgram() {
|
|
|
2327
2455
|
});
|
|
2328
2456
|
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
2457
|
const result = await finalizeTheme(resolveThemeRoot(options));
|
|
2330
|
-
console.log(
|
|
2458
|
+
console.log(
|
|
2459
|
+
success(
|
|
2460
|
+
`finalized ${themeRef(result.module.manifest.key, result.module.manifest.version)}`
|
|
2461
|
+
)
|
|
2462
|
+
);
|
|
2331
2463
|
});
|
|
2332
2464
|
theme.command("check").description(
|
|
2333
2465
|
"Run AI metadata checks on a built theme without rebuilding. Reports missing component-level ai.instructions as errors and weak content as warnings."
|