@sudajs/cli 0.0.2
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.js +15 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1054 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
package/bin/suda.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
+
|
|
7
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const distEntry = path.resolve(scriptDir, "../dist/index.js");
|
|
9
|
+
|
|
10
|
+
if (!existsSync(distEntry)) {
|
|
11
|
+
console.error("@sudajs/cli is not built yet. Run `pnpm --filter @sudajs/cli build` first.");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
await import(pathToFileURL(distEntry).href);
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,1054 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from 'crypto';
|
|
3
|
+
import fs, { mkdir, writeFile, readFile, stat, readdir } from 'fs/promises';
|
|
4
|
+
import path2 from 'path';
|
|
5
|
+
import { pathToFileURL } from 'url';
|
|
6
|
+
import { extractLayoutChrome, ThemeRender } from '@sudajs/theme-engine/server';
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
import { build, context } from 'esbuild';
|
|
9
|
+
import { createElement } from 'react';
|
|
10
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
11
|
+
import os from 'os';
|
|
12
|
+
|
|
13
|
+
function getConfigPath() {
|
|
14
|
+
return path2.join(os.homedir(), ".config", "suda", "config.json");
|
|
15
|
+
}
|
|
16
|
+
async function readAuthConfig() {
|
|
17
|
+
try {
|
|
18
|
+
const configPath = getConfigPath();
|
|
19
|
+
const content = await fs.readFile(configPath, "utf-8");
|
|
20
|
+
return JSON.parse(content);
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function writeAuthConfig(config) {
|
|
26
|
+
const configPath = getConfigPath();
|
|
27
|
+
await fs.mkdir(path2.dirname(configPath), { recursive: true });
|
|
28
|
+
await fs.chmod(path2.dirname(configPath), 448).catch(() => void 0);
|
|
29
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
30
|
+
}
|
|
31
|
+
async function clearAuthConfig() {
|
|
32
|
+
const configPath = getConfigPath();
|
|
33
|
+
try {
|
|
34
|
+
await fs.unlink(configPath);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
if (err instanceof Error && "code" in err && err.code !== "ENOENT") {
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function login(host = "workspace.sudacloud.com") {
|
|
42
|
+
const protocol = host.includes("localhost") || host.includes("127.0.0.1") ? "http" : "https";
|
|
43
|
+
const baseUrl = `${protocol}://${host}`;
|
|
44
|
+
console.log(`Requesting device authorization from ${baseUrl}...`);
|
|
45
|
+
const deviceRes = await fetch(`${baseUrl}/api/cli-auth/device`, {
|
|
46
|
+
method: "POST"
|
|
47
|
+
});
|
|
48
|
+
if (!deviceRes.ok) {
|
|
49
|
+
throw new Error(`Failed to initialize auth: ${deviceRes.statusText}`);
|
|
50
|
+
}
|
|
51
|
+
const { deviceCode, userCode, verificationUri, interval, expiresIn } = await deviceRes.json();
|
|
52
|
+
const authUrl = `${verificationUri}?code=${userCode}`;
|
|
53
|
+
console.log(`
|
|
54
|
+
Please open the following URL in your browser to authorize Suda CLI:
|
|
55
|
+
`);
|
|
56
|
+
console.log(` ${authUrl}
|
|
57
|
+
`);
|
|
58
|
+
console.log(`Your confirmation code is: ${userCode}
|
|
59
|
+
`);
|
|
60
|
+
console.log("Waiting for authorization...");
|
|
61
|
+
try {
|
|
62
|
+
const open = (await import('open')).default;
|
|
63
|
+
await open(authUrl);
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
const pollInterval = (interval || 5) * 1e3;
|
|
67
|
+
const deadline = Date.now() + (expiresIn || 900) * 1e3;
|
|
68
|
+
while (Date.now() < deadline) {
|
|
69
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
70
|
+
const pollRes = await fetch(`${baseUrl}/api/cli-auth/poll`, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: { "Content-Type": "application/json" },
|
|
73
|
+
body: JSON.stringify({ deviceCode })
|
|
74
|
+
});
|
|
75
|
+
const data = await pollRes.json();
|
|
76
|
+
if (pollRes.ok && data.status === "approved" && data.token) {
|
|
77
|
+
await writeAuthConfig({ sessionToken: data.token, host });
|
|
78
|
+
console.log("\u2705 Successfully authorized!");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (data.error === "authorization_pending") {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
throw new Error(`Authorization failed: ${data.error || "Unknown error"}`);
|
|
85
|
+
}
|
|
86
|
+
throw new Error("Authorization timed out.");
|
|
87
|
+
}
|
|
88
|
+
async function status() {
|
|
89
|
+
const config = await readAuthConfig();
|
|
90
|
+
if (!config) {
|
|
91
|
+
console.log("Not logged in. Run `suda auth login` to authenticate.");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const protocol = config.host.includes("localhost") || config.host.includes("127.0.0.1") ? "http" : "https";
|
|
95
|
+
try {
|
|
96
|
+
const res = await fetch(`${protocol}://${config.host}/api/auth/get-session`, {
|
|
97
|
+
headers: {
|
|
98
|
+
Authorization: `Bearer ${config.sessionToken}`
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
if (res.ok) {
|
|
102
|
+
const session = await res.json();
|
|
103
|
+
console.log(`Logged in as ${session.user.email || session.user.name} on ${config.host}`);
|
|
104
|
+
} else {
|
|
105
|
+
console.log("Session expired or invalid. Please run `suda auth login` again.");
|
|
106
|
+
await clearAuthConfig();
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
console.error(`Failed to connect to ${config.host}.`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function logout() {
|
|
113
|
+
const config = await readAuthConfig();
|
|
114
|
+
if (!config) {
|
|
115
|
+
console.log("Not logged in.");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
await clearAuthConfig();
|
|
119
|
+
console.log("Logged out successfully.");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/index.ts
|
|
123
|
+
function createHostReactShimPlugin() {
|
|
124
|
+
return {
|
|
125
|
+
name: "suda-host-react-shim",
|
|
126
|
+
setup(build) {
|
|
127
|
+
build.onResolve({ filter: /^react$/ }, () => ({
|
|
128
|
+
path: "react-host-shim",
|
|
129
|
+
namespace: "suda-shim"
|
|
130
|
+
}));
|
|
131
|
+
build.onResolve({ filter: /^react\/jsx-runtime$/ }, () => ({
|
|
132
|
+
path: "react-jsx-runtime-host-shim",
|
|
133
|
+
namespace: "suda-shim"
|
|
134
|
+
}));
|
|
135
|
+
build.onResolve({ filter: /^react-dom$/ }, () => ({
|
|
136
|
+
path: "react-dom-host-shim",
|
|
137
|
+
namespace: "suda-shim"
|
|
138
|
+
}));
|
|
139
|
+
build.onResolve({ filter: /^react-dom\/client$/ }, () => ({
|
|
140
|
+
path: "react-dom-client-host-shim",
|
|
141
|
+
namespace: "suda-shim"
|
|
142
|
+
}));
|
|
143
|
+
build.onLoad({ filter: /^react-host-shim$/, namespace: "suda-shim" }, () => ({
|
|
144
|
+
loader: "js",
|
|
145
|
+
contents: `
|
|
146
|
+
const React = globalThis.__SUDA_REACT__;
|
|
147
|
+
if (!React) {
|
|
148
|
+
throw new Error("Missing host React runtime (__SUDA_REACT__).");
|
|
149
|
+
}
|
|
150
|
+
export default React;
|
|
151
|
+
export const {
|
|
152
|
+
Children,
|
|
153
|
+
Component,
|
|
154
|
+
Fragment,
|
|
155
|
+
Profiler,
|
|
156
|
+
PureComponent,
|
|
157
|
+
StrictMode,
|
|
158
|
+
Suspense,
|
|
159
|
+
cloneElement,
|
|
160
|
+
createContext,
|
|
161
|
+
createElement,
|
|
162
|
+
createRef,
|
|
163
|
+
forwardRef,
|
|
164
|
+
isValidElement,
|
|
165
|
+
lazy,
|
|
166
|
+
memo,
|
|
167
|
+
startTransition,
|
|
168
|
+
use,
|
|
169
|
+
useActionState,
|
|
170
|
+
useCallback,
|
|
171
|
+
useContext,
|
|
172
|
+
useDeferredValue,
|
|
173
|
+
useEffect,
|
|
174
|
+
useId,
|
|
175
|
+
useImperativeHandle,
|
|
176
|
+
useInsertionEffect,
|
|
177
|
+
useLayoutEffect,
|
|
178
|
+
useMemo,
|
|
179
|
+
useOptimistic,
|
|
180
|
+
useReducer,
|
|
181
|
+
useRef,
|
|
182
|
+
useState,
|
|
183
|
+
useSyncExternalStore,
|
|
184
|
+
useTransition,
|
|
185
|
+
version,
|
|
186
|
+
} = React;
|
|
187
|
+
`
|
|
188
|
+
}));
|
|
189
|
+
build.onLoad(
|
|
190
|
+
{
|
|
191
|
+
filter: /^react-jsx-runtime-host-shim$/,
|
|
192
|
+
namespace: "suda-shim"
|
|
193
|
+
},
|
|
194
|
+
() => ({
|
|
195
|
+
loader: "js",
|
|
196
|
+
contents: `
|
|
197
|
+
const runtime = globalThis.__SUDA_REACT_JSX_RUNTIME__;
|
|
198
|
+
if (!runtime) {
|
|
199
|
+
throw new Error("Missing host React JSX runtime (__SUDA_REACT_JSX_RUNTIME__).");
|
|
200
|
+
}
|
|
201
|
+
export const { Fragment, jsx, jsxs, jsxDEV } = runtime;
|
|
202
|
+
`
|
|
203
|
+
})
|
|
204
|
+
);
|
|
205
|
+
build.onLoad({ filter: /^react-dom-host-shim$/, namespace: "suda-shim" }, () => ({
|
|
206
|
+
loader: "js",
|
|
207
|
+
contents: `
|
|
208
|
+
const ReactDOM = globalThis.__SUDA_REACT_DOM__;
|
|
209
|
+
if (!ReactDOM) {
|
|
210
|
+
throw new Error("Missing host React DOM runtime (__SUDA_REACT_DOM__).");
|
|
211
|
+
}
|
|
212
|
+
export default ReactDOM;
|
|
213
|
+
export const {
|
|
214
|
+
createPortal,
|
|
215
|
+
flushSync,
|
|
216
|
+
preconnect,
|
|
217
|
+
prefetchDNS,
|
|
218
|
+
preinit,
|
|
219
|
+
preinitModule,
|
|
220
|
+
preload,
|
|
221
|
+
preloadModule,
|
|
222
|
+
requestFormReset,
|
|
223
|
+
unstable_batchedUpdates,
|
|
224
|
+
useFormState,
|
|
225
|
+
useFormStatus,
|
|
226
|
+
version,
|
|
227
|
+
} = ReactDOM;
|
|
228
|
+
`
|
|
229
|
+
}));
|
|
230
|
+
build.onLoad({ filter: /^react-dom-client-host-shim$/, namespace: "suda-shim" }, () => ({
|
|
231
|
+
loader: "js",
|
|
232
|
+
contents: `
|
|
233
|
+
const ReactDOMClient = globalThis.__SUDA_REACT_DOM_CLIENT__;
|
|
234
|
+
if (!ReactDOMClient) {
|
|
235
|
+
throw new Error("Missing host React DOM client runtime (__SUDA_REACT_DOM_CLIENT__).");
|
|
236
|
+
}
|
|
237
|
+
export default ReactDOMClient;
|
|
238
|
+
export const { createRoot, hydrateRoot, version } = ReactDOMClient;
|
|
239
|
+
`
|
|
240
|
+
}));
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function safeObjectKeyPart(value) {
|
|
245
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
246
|
+
}
|
|
247
|
+
function createThemePrefix(themeKey, version) {
|
|
248
|
+
return `themes/${safeObjectKeyPart(themeKey)}/${safeObjectKeyPart(version)}`;
|
|
249
|
+
}
|
|
250
|
+
function createThemeObjectKey(themeKey, version, relativePath) {
|
|
251
|
+
const safeRelativePath = relativePath.replace(/^\/+/, "").split("/").map(safeObjectKeyPart).filter(Boolean).join("/");
|
|
252
|
+
return `${createThemePrefix(themeKey, version)}/${safeRelativePath}`;
|
|
253
|
+
}
|
|
254
|
+
function resolveThemeRoot(options) {
|
|
255
|
+
return path2.resolve(options.themeRoot ?? process.cwd());
|
|
256
|
+
}
|
|
257
|
+
async function pathExists(filePath) {
|
|
258
|
+
try {
|
|
259
|
+
await stat(filePath);
|
|
260
|
+
return true;
|
|
261
|
+
} catch {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async function readJson(filePath) {
|
|
266
|
+
const raw = await readFile(filePath, "utf8");
|
|
267
|
+
return JSON.parse(raw);
|
|
268
|
+
}
|
|
269
|
+
async function loadThemeModule(serverEntryPath) {
|
|
270
|
+
const imported = await import(`${pathToFileURL(serverEntryPath).href}?t=${Date.now()}`);
|
|
271
|
+
if (!imported.default) {
|
|
272
|
+
throw new Error(`${serverEntryPath} must export a default ThemeModule.`);
|
|
273
|
+
}
|
|
274
|
+
return imported.default;
|
|
275
|
+
}
|
|
276
|
+
function assertRecord(value, label) {
|
|
277
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
278
|
+
throw new Error(`${label} must be an object.`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function validateThemeModule(module) {
|
|
282
|
+
assertRecord(module.manifest, "manifest");
|
|
283
|
+
if (!module.manifest.key || !module.manifest.version || !module.manifest.name) {
|
|
284
|
+
throw new Error("manifest.key, manifest.version and manifest.name are required.");
|
|
285
|
+
}
|
|
286
|
+
if (module.manifest.entry !== "dist/index.js") {
|
|
287
|
+
throw new Error('manifest.entry must be artifact-local: "dist/index.js".');
|
|
288
|
+
}
|
|
289
|
+
if (module.manifest.clientEntry !== void 0 && module.manifest.clientEntry !== "dist/runtime.client.js") {
|
|
290
|
+
throw new Error(
|
|
291
|
+
'manifest.clientEntry must be artifact-local: "dist/runtime.client.js" when provided.'
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
assertRecord(module.pageConfig?.components, "pageConfig.components");
|
|
295
|
+
assertRecord(module.layoutConfig, "layoutConfig");
|
|
296
|
+
assertRecord(module.defaultLayout, "defaultLayout");
|
|
297
|
+
if (!Array.isArray(module.starterPages)) {
|
|
298
|
+
throw new Error("starterPages must be an array.");
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async function validateTheme(root) {
|
|
302
|
+
const packageJsonPath = path2.join(root, "package.json");
|
|
303
|
+
const serverEntryPath = path2.join(root, "dist", "index.js");
|
|
304
|
+
const clientEntryPath = path2.join(root, "dist", "runtime.client.js");
|
|
305
|
+
const stylesheetPath = path2.join(root, "styles.css");
|
|
306
|
+
if (!await pathExists(packageJsonPath)) {
|
|
307
|
+
throw new Error(`Missing package.json in ${root}`);
|
|
308
|
+
}
|
|
309
|
+
if (!await pathExists(serverEntryPath)) {
|
|
310
|
+
throw new Error(`Missing ${serverEntryPath}. Run the theme package build first.`);
|
|
311
|
+
}
|
|
312
|
+
const module = await loadThemeModule(serverEntryPath);
|
|
313
|
+
validateThemeModule(module);
|
|
314
|
+
const result = {
|
|
315
|
+
root,
|
|
316
|
+
module,
|
|
317
|
+
serverEntryPath,
|
|
318
|
+
clientEntryPath: null,
|
|
319
|
+
stylesheetPath: null
|
|
320
|
+
};
|
|
321
|
+
if (await pathExists(clientEntryPath)) {
|
|
322
|
+
result.clientEntryPath = clientEntryPath;
|
|
323
|
+
}
|
|
324
|
+
if (await pathExists(stylesheetPath)) {
|
|
325
|
+
result.stylesheetPath = stylesheetPath;
|
|
326
|
+
}
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
329
|
+
async function runCommand(command, args, cwd) {
|
|
330
|
+
const { spawn } = await import('child_process');
|
|
331
|
+
await new Promise((resolve, reject) => {
|
|
332
|
+
const child = spawn(command, args, { cwd, stdio: "inherit" });
|
|
333
|
+
child.on("error", reject);
|
|
334
|
+
child.on("exit", (code) => {
|
|
335
|
+
if (code === 0) {
|
|
336
|
+
resolve();
|
|
337
|
+
} else {
|
|
338
|
+
reject(new Error(`${command} ${args.join(" ")} exited with ${code ?? "unknown"}.`));
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
async function runThemePackageBuild(root) {
|
|
344
|
+
const packageJson = await readJson(
|
|
345
|
+
path2.join(root, "package.json")
|
|
346
|
+
);
|
|
347
|
+
const buildScript = packageJson.scripts?.build;
|
|
348
|
+
if (!buildScript || buildScript.includes("suda theme") || buildScript.includes("suda-theme")) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
await runCommand("pnpm", ["--dir", root, "run", "build"], process.cwd());
|
|
352
|
+
}
|
|
353
|
+
async function findClientEntry(root) {
|
|
354
|
+
const candidates = [
|
|
355
|
+
path2.join(root, "src", "runtime.client.tsx"),
|
|
356
|
+
path2.join(root, "src", "runtime.client.ts")
|
|
357
|
+
];
|
|
358
|
+
for (const candidate of candidates) {
|
|
359
|
+
if (await pathExists(candidate)) {
|
|
360
|
+
return candidate;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
throw new Error(`Missing runtime entry. Add src/runtime.client.ts(x) in ${root}.`);
|
|
364
|
+
}
|
|
365
|
+
async function findServerEntry(root) {
|
|
366
|
+
const candidates = [path2.join(root, "src", "index.tsx"), path2.join(root, "src", "index.ts")];
|
|
367
|
+
for (const candidate of candidates) {
|
|
368
|
+
if (await pathExists(candidate)) {
|
|
369
|
+
return candidate;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
throw new Error(`Missing server entry. Add src/index.ts(x) in ${root}.`);
|
|
373
|
+
}
|
|
374
|
+
async function buildServerBundle(root) {
|
|
375
|
+
const entryPoint = await findServerEntry(root);
|
|
376
|
+
await build({
|
|
377
|
+
bundle: true,
|
|
378
|
+
entryPoints: [entryPoint],
|
|
379
|
+
external: [
|
|
380
|
+
"react",
|
|
381
|
+
"react/jsx-runtime",
|
|
382
|
+
"react-dom",
|
|
383
|
+
"react-dom/*",
|
|
384
|
+
"@puckeditor/core",
|
|
385
|
+
"@puckeditor/core/*",
|
|
386
|
+
"@sudajs/theme-engine",
|
|
387
|
+
"@sudajs/theme-engine/*"
|
|
388
|
+
],
|
|
389
|
+
format: "esm",
|
|
390
|
+
jsx: "automatic",
|
|
391
|
+
minify: false,
|
|
392
|
+
outfile: path2.join(root, "dist", "index.js"),
|
|
393
|
+
platform: "node",
|
|
394
|
+
sourcemap: false,
|
|
395
|
+
target: "es2022",
|
|
396
|
+
treeShaking: true
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
async function buildTheme(root, skipThemeBuild) {
|
|
400
|
+
if (!skipThemeBuild) {
|
|
401
|
+
await runThemePackageBuild(root);
|
|
402
|
+
}
|
|
403
|
+
await buildServerBundle(root);
|
|
404
|
+
const entryPoint = await findClientEntry(root);
|
|
405
|
+
if (!await pathExists(entryPoint)) {
|
|
406
|
+
throw new Error(`Missing client entry: ${entryPoint}`);
|
|
407
|
+
}
|
|
408
|
+
await mkdir(path2.join(root, "dist"), { recursive: true });
|
|
409
|
+
const hostReactShimPlugin = createHostReactShimPlugin();
|
|
410
|
+
await build({
|
|
411
|
+
bundle: true,
|
|
412
|
+
entryPoints: [entryPoint],
|
|
413
|
+
format: "esm",
|
|
414
|
+
jsx: "automatic",
|
|
415
|
+
minify: true,
|
|
416
|
+
outfile: path2.join(root, "dist", "runtime.client.js"),
|
|
417
|
+
platform: "browser",
|
|
418
|
+
plugins: [hostReactShimPlugin],
|
|
419
|
+
sourcemap: false,
|
|
420
|
+
target: "es2022",
|
|
421
|
+
treeShaking: true
|
|
422
|
+
});
|
|
423
|
+
const validated = await validateTheme(root);
|
|
424
|
+
await writeThemeArtifacts(validated);
|
|
425
|
+
return validateTheme(root);
|
|
426
|
+
}
|
|
427
|
+
async function watchTheme(root, skipThemeBuild) {
|
|
428
|
+
if (!skipThemeBuild) {
|
|
429
|
+
await runThemePackageBuild(root);
|
|
430
|
+
}
|
|
431
|
+
await buildServerBundle(root);
|
|
432
|
+
const entryPoint = await findClientEntry(root);
|
|
433
|
+
await mkdir(path2.join(root, "dist"), { recursive: true });
|
|
434
|
+
const hostReactShimPlugin = createHostReactShimPlugin();
|
|
435
|
+
const context$1 = await context({
|
|
436
|
+
bundle: true,
|
|
437
|
+
entryPoints: [entryPoint],
|
|
438
|
+
format: "esm",
|
|
439
|
+
jsx: "automatic",
|
|
440
|
+
minify: false,
|
|
441
|
+
outfile: path2.join(root, "dist", "runtime.client.js"),
|
|
442
|
+
platform: "browser",
|
|
443
|
+
plugins: [hostReactShimPlugin],
|
|
444
|
+
sourcemap: true,
|
|
445
|
+
target: "es2022",
|
|
446
|
+
treeShaking: true
|
|
447
|
+
});
|
|
448
|
+
await context$1.watch();
|
|
449
|
+
console.log("watching theme runtime; press Ctrl+C to stop");
|
|
450
|
+
await new Promise(() => void 0);
|
|
451
|
+
}
|
|
452
|
+
function pickStarterPage(theme) {
|
|
453
|
+
const pages = theme.module.starterPages;
|
|
454
|
+
if (pages.length === 0) {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
return pages.find((page) => page.isHome) ?? pages[0] ?? null;
|
|
458
|
+
}
|
|
459
|
+
function escapeHtml(value) {
|
|
460
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
461
|
+
}
|
|
462
|
+
function renderStarterPageHtml(theme, page) {
|
|
463
|
+
const chrome = extractLayoutChrome(theme.module.defaultLayout);
|
|
464
|
+
const body = renderToStaticMarkup(
|
|
465
|
+
createElement(ThemeRender, {
|
|
466
|
+
theme: theme.module,
|
|
467
|
+
pageData: page.data,
|
|
468
|
+
layoutData: theme.module.defaultLayout
|
|
469
|
+
})
|
|
470
|
+
);
|
|
471
|
+
const cssVarStyle = Object.entries(chrome.cssVariables).map(([key, value]) => `${key}: ${value};`).join(" ");
|
|
472
|
+
const stylesheetTag = theme.stylesheetPath ? '<link rel="stylesheet" href="/styles.css" />' : "";
|
|
473
|
+
const customHead = chrome.customHeadCode ?? "";
|
|
474
|
+
const customBody = chrome.customBodyCode ?? "";
|
|
475
|
+
return [
|
|
476
|
+
"<!doctype html>",
|
|
477
|
+
'<html lang="en">',
|
|
478
|
+
"<head>",
|
|
479
|
+
'<meta charset="utf-8" />',
|
|
480
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
481
|
+
`<title>${escapeHtml(`${theme.module.manifest.name} \u2014 ${page.title}`)}</title>`,
|
|
482
|
+
stylesheetTag,
|
|
483
|
+
customHead,
|
|
484
|
+
"</head>",
|
|
485
|
+
`<body${cssVarStyle ? ` style="${cssVarStyle}"` : ""}>`,
|
|
486
|
+
body,
|
|
487
|
+
customBody,
|
|
488
|
+
"</body>",
|
|
489
|
+
"</html>"
|
|
490
|
+
].join("");
|
|
491
|
+
}
|
|
492
|
+
async function startPreviewServer(theme, port) {
|
|
493
|
+
const { createServer } = await import('http');
|
|
494
|
+
const starterPage = pickStarterPage(theme);
|
|
495
|
+
const server = createServer((request, response) => {
|
|
496
|
+
void (async () => {
|
|
497
|
+
const url = new URL(request.url ?? "/", `http://localhost:${port}`);
|
|
498
|
+
const pathname = url.pathname;
|
|
499
|
+
if (pathname === "/" || pathname === "/index.html") {
|
|
500
|
+
if (!starterPage) {
|
|
501
|
+
response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
502
|
+
response.end("No starter page available for preview.");
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
506
|
+
response.end(renderStarterPageHtml(theme, starterPage));
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
if (pathname === "/styles.css" && theme.stylesheetPath) {
|
|
510
|
+
response.writeHead(200, { "Content-Type": "text/css; charset=utf-8" });
|
|
511
|
+
response.end(await readFile(theme.stylesheetPath));
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
const relativePath = pathname.replace(/^\/+/, "");
|
|
515
|
+
const filePath = path2.resolve(theme.root, relativePath);
|
|
516
|
+
const normalizedRoot = path2.resolve(theme.root) + path2.sep;
|
|
517
|
+
if (!filePath.startsWith(normalizedRoot) || !await pathExists(filePath)) {
|
|
518
|
+
response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
519
|
+
response.end("Not found");
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
response.writeHead(200, { "Content-Type": contentTypeFor(relativePath) });
|
|
523
|
+
response.end(await readFile(filePath));
|
|
524
|
+
})().catch((error) => {
|
|
525
|
+
response.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
|
|
526
|
+
response.end(error instanceof Error ? error.message : "Internal error");
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
await new Promise((resolve, reject) => {
|
|
530
|
+
const onError = (err) => {
|
|
531
|
+
reject(err);
|
|
532
|
+
};
|
|
533
|
+
server.once("error", onError);
|
|
534
|
+
server.listen(port, () => {
|
|
535
|
+
server.off("error", onError);
|
|
536
|
+
resolve();
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
return {
|
|
540
|
+
url: `http://localhost:${port}`,
|
|
541
|
+
port,
|
|
542
|
+
close: () => new Promise((resolve) => {
|
|
543
|
+
server.close(() => {
|
|
544
|
+
resolve();
|
|
545
|
+
});
|
|
546
|
+
})
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
async function previewTheme(root, port) {
|
|
550
|
+
const theme = await buildTheme(root, false);
|
|
551
|
+
const handle = await startPreviewServer(theme, port);
|
|
552
|
+
console.log(
|
|
553
|
+
`previewing ${theme.module.manifest.key}@${theme.module.manifest.version} at ${handle.url}`
|
|
554
|
+
);
|
|
555
|
+
await new Promise(() => void 0);
|
|
556
|
+
}
|
|
557
|
+
var DEFAULT_SCREENSHOT_RELATIVE = path2.join("dist", "preview", "desktop.png");
|
|
558
|
+
function parsePositiveInt(value, fallback) {
|
|
559
|
+
if (!value) {
|
|
560
|
+
return fallback;
|
|
561
|
+
}
|
|
562
|
+
const parsed = Number.parseInt(value, 10);
|
|
563
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
564
|
+
}
|
|
565
|
+
function resolveScreenshotOptions(root, options) {
|
|
566
|
+
return {
|
|
567
|
+
outputPath: path2.resolve(root, options.output ?? DEFAULT_SCREENSHOT_RELATIVE),
|
|
568
|
+
viewport: {
|
|
569
|
+
width: parsePositiveInt(options.width, 1280),
|
|
570
|
+
height: parsePositiveInt(options.height, 800)
|
|
571
|
+
},
|
|
572
|
+
port: parsePositiveInt(options.port, 4178),
|
|
573
|
+
skipBuild: options.skipBuild === true
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
async function loadPlaywright() {
|
|
577
|
+
const candidates = ["playwright", "@playwright/test"];
|
|
578
|
+
for (const id of candidates) {
|
|
579
|
+
try {
|
|
580
|
+
const mod = await import(
|
|
581
|
+
/* @vite-ignore */
|
|
582
|
+
id
|
|
583
|
+
);
|
|
584
|
+
return mod;
|
|
585
|
+
} catch {
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
throw new Error(
|
|
589
|
+
"Playwright is required for screenshot capture. Install it as a dev dependency: `pnpm add -D playwright && pnpm exec playwright install chromium`."
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
async function captureScreenshot(theme, options) {
|
|
593
|
+
const playwright = await loadPlaywright();
|
|
594
|
+
const handle = await startPreviewServer(theme, options.port);
|
|
595
|
+
try {
|
|
596
|
+
await mkdir(path2.dirname(options.outputPath), { recursive: true });
|
|
597
|
+
const browser = await playwright.chromium.launch({ headless: true });
|
|
598
|
+
try {
|
|
599
|
+
const context = await browser.newContext({
|
|
600
|
+
viewport: options.viewport,
|
|
601
|
+
deviceScaleFactor: 1
|
|
602
|
+
});
|
|
603
|
+
const page = await context.newPage();
|
|
604
|
+
await page.goto(handle.url, { waitUntil: "networkidle" });
|
|
605
|
+
await page.screenshot({ path: options.outputPath, fullPage: false });
|
|
606
|
+
await context.close();
|
|
607
|
+
} finally {
|
|
608
|
+
await browser.close();
|
|
609
|
+
}
|
|
610
|
+
} finally {
|
|
611
|
+
await handle.close();
|
|
612
|
+
}
|
|
613
|
+
console.log(`captured ${path2.relative(theme.root, options.outputPath)}`);
|
|
614
|
+
}
|
|
615
|
+
async function screenshotTheme(root, options) {
|
|
616
|
+
const resolved = resolveScreenshotOptions(root, options);
|
|
617
|
+
const theme = resolved.skipBuild ? await validateTheme(root) : await buildTheme(root, false);
|
|
618
|
+
await captureScreenshot(theme, resolved);
|
|
619
|
+
}
|
|
620
|
+
async function writeThemeArtifacts(theme) {
|
|
621
|
+
const dist = path2.join(theme.root, "dist");
|
|
622
|
+
await writeFile(
|
|
623
|
+
path2.join(dist, "manifest.json"),
|
|
624
|
+
`${JSON.stringify(theme.module.manifest, null, 2)}
|
|
625
|
+
`
|
|
626
|
+
);
|
|
627
|
+
await writeFile(
|
|
628
|
+
path2.join(dist, "starter-pages.json"),
|
|
629
|
+
`${JSON.stringify(theme.module.starterPages, null, 2)}
|
|
630
|
+
`
|
|
631
|
+
);
|
|
632
|
+
await writeFile(
|
|
633
|
+
path2.join(dist, "default-layout.json"),
|
|
634
|
+
`${JSON.stringify(theme.module.defaultLayout, null, 2)}
|
|
635
|
+
`
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
function contentTypeFor(relativePath) {
|
|
639
|
+
if (relativePath.endsWith(".css")) {
|
|
640
|
+
return "text/css; charset=utf-8";
|
|
641
|
+
}
|
|
642
|
+
if (relativePath.endsWith(".js")) {
|
|
643
|
+
return "application/javascript; charset=utf-8";
|
|
644
|
+
}
|
|
645
|
+
if (relativePath.endsWith(".json")) {
|
|
646
|
+
return "application/json; charset=utf-8";
|
|
647
|
+
}
|
|
648
|
+
if (relativePath.endsWith(".svg")) {
|
|
649
|
+
return "image/svg+xml";
|
|
650
|
+
}
|
|
651
|
+
if (relativePath.endsWith(".png")) {
|
|
652
|
+
return "image/png";
|
|
653
|
+
}
|
|
654
|
+
if (relativePath.endsWith(".jpg") || relativePath.endsWith(".jpeg")) {
|
|
655
|
+
return "image/jpeg";
|
|
656
|
+
}
|
|
657
|
+
if (relativePath.endsWith(".webp")) {
|
|
658
|
+
return "image/webp";
|
|
659
|
+
}
|
|
660
|
+
return "application/octet-stream";
|
|
661
|
+
}
|
|
662
|
+
async function collectFiles(root, directory) {
|
|
663
|
+
const files = [];
|
|
664
|
+
if (!await pathExists(directory)) {
|
|
665
|
+
return files;
|
|
666
|
+
}
|
|
667
|
+
const children = await readdir(directory, { withFileTypes: true });
|
|
668
|
+
for (const child of children) {
|
|
669
|
+
const absolutePath = path2.join(directory, child.name);
|
|
670
|
+
if (child.isDirectory()) {
|
|
671
|
+
files.push(...await collectFiles(root, absolutePath));
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
const relativePath = path2.relative(root, absolutePath).replaceAll(path2.sep, "/");
|
|
675
|
+
files.push({
|
|
676
|
+
absolutePath,
|
|
677
|
+
relativePath,
|
|
678
|
+
contentType: contentTypeFor(relativePath)
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
return files;
|
|
682
|
+
}
|
|
683
|
+
async function collectThemeArtifactFiles(theme) {
|
|
684
|
+
const files = await collectFiles(theme.root, path2.join(theme.root, "dist"));
|
|
685
|
+
if (theme.stylesheetPath) {
|
|
686
|
+
files.push({
|
|
687
|
+
absolutePath: theme.stylesheetPath,
|
|
688
|
+
relativePath: "styles.css",
|
|
689
|
+
contentType: "text/css; charset=utf-8"
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
return files;
|
|
693
|
+
}
|
|
694
|
+
async function checksum(files) {
|
|
695
|
+
const hash = createHash("sha256");
|
|
696
|
+
for (const file of files.sort((a, b) => a.relativePath.localeCompare(b.relativePath))) {
|
|
697
|
+
hash.update(file.relativePath);
|
|
698
|
+
hash.update(await readFile(file.absolutePath));
|
|
699
|
+
}
|
|
700
|
+
return hash.digest("hex");
|
|
701
|
+
}
|
|
702
|
+
async function publishTheme(root, skipBuild) {
|
|
703
|
+
const theme = skipBuild ? await validateTheme(root) : await buildTheme(root, false);
|
|
704
|
+
const files = await collectThemeArtifactFiles(theme);
|
|
705
|
+
if (!theme.clientEntryPath) {
|
|
706
|
+
throw new Error("Missing dist/runtime.client.js. Run `suda theme build` first.");
|
|
707
|
+
}
|
|
708
|
+
const config = await readAuthConfig();
|
|
709
|
+
if (!config) {
|
|
710
|
+
throw new Error("Not logged in. Run `suda auth login` to authenticate first.");
|
|
711
|
+
}
|
|
712
|
+
const protocol = config.host.includes("localhost") || config.host.includes("127.0.0.1") ? "http" : "https";
|
|
713
|
+
const baseUrl = `${protocol}://${config.host}`;
|
|
714
|
+
const { key, version } = theme.module.manifest;
|
|
715
|
+
const intentRes = await fetch(`${baseUrl}/api/cli/themes/publish-intent`, {
|
|
716
|
+
method: "POST",
|
|
717
|
+
headers: {
|
|
718
|
+
"Content-Type": "application/json",
|
|
719
|
+
Authorization: `Bearer ${config.sessionToken}`
|
|
720
|
+
},
|
|
721
|
+
body: JSON.stringify({
|
|
722
|
+
key,
|
|
723
|
+
version,
|
|
724
|
+
files: files.map((f) => ({ relativePath: f.relativePath, contentType: f.contentType }))
|
|
725
|
+
})
|
|
726
|
+
});
|
|
727
|
+
if (!intentRes.ok) {
|
|
728
|
+
const errorData = await intentRes.json().catch(() => ({}));
|
|
729
|
+
throw new Error(`Failed to get publish intent: ${errorData.error || intentRes.statusText}`);
|
|
730
|
+
}
|
|
731
|
+
const { urls } = await intentRes.json();
|
|
732
|
+
for (const file of files) {
|
|
733
|
+
const urlObj = urls.find((u) => u.relativePath === file.relativePath);
|
|
734
|
+
if (!urlObj) {
|
|
735
|
+
throw new Error(`Missing presigned URL for ${file.relativePath}`);
|
|
736
|
+
}
|
|
737
|
+
const body = await readFile(file.absolutePath);
|
|
738
|
+
const putRes = await fetch(urlObj.url, {
|
|
739
|
+
method: "PUT",
|
|
740
|
+
headers: {
|
|
741
|
+
"Content-Type": file.contentType
|
|
742
|
+
},
|
|
743
|
+
body
|
|
744
|
+
});
|
|
745
|
+
if (!putRes.ok) {
|
|
746
|
+
throw new Error(`Failed to upload ${file.relativePath}: ${putRes.statusText}`);
|
|
747
|
+
}
|
|
748
|
+
console.log(`uploaded ${createThemeObjectKey(key, version, file.relativePath)}`);
|
|
749
|
+
}
|
|
750
|
+
const digest = await checksum(files);
|
|
751
|
+
const bundleArtifactRelative = "dist/index.js";
|
|
752
|
+
const bundleArtifactExists = files.some((f) => f.relativePath === bundleArtifactRelative);
|
|
753
|
+
if (!bundleArtifactExists) {
|
|
754
|
+
throw new Error(`Missing ${bundleArtifactRelative} in theme artifacts.`);
|
|
755
|
+
}
|
|
756
|
+
const previewArtifactRelative = "dist/preview/desktop.png";
|
|
757
|
+
const previewArtifactExists = await pathExists(path2.join(theme.root, previewArtifactRelative));
|
|
758
|
+
const completeRes = await fetch(`${baseUrl}/api/cli/themes/publish-complete`, {
|
|
759
|
+
method: "POST",
|
|
760
|
+
headers: {
|
|
761
|
+
"Content-Type": "application/json",
|
|
762
|
+
Authorization: `Bearer ${config.sessionToken}`
|
|
763
|
+
},
|
|
764
|
+
body: JSON.stringify({
|
|
765
|
+
key,
|
|
766
|
+
version,
|
|
767
|
+
checksum: digest,
|
|
768
|
+
manifest: theme.module.manifest,
|
|
769
|
+
bundleArtifactRelative,
|
|
770
|
+
previewArtifactRelative: previewArtifactExists ? previewArtifactRelative : void 0
|
|
771
|
+
})
|
|
772
|
+
});
|
|
773
|
+
if (!completeRes.ok) {
|
|
774
|
+
const errorData = await completeRes.json().catch(() => ({}));
|
|
775
|
+
throw new Error(`Failed to complete publish: ${errorData.error || completeRes.statusText}`);
|
|
776
|
+
}
|
|
777
|
+
console.log(`published ${key}@${version}`);
|
|
778
|
+
}
|
|
779
|
+
async function initTheme(target) {
|
|
780
|
+
const key = path2.basename(target).replace(/[^a-zA-Z0-9._-]/g, "-").toLowerCase();
|
|
781
|
+
await mkdir(path2.join(target, "src"), { recursive: true });
|
|
782
|
+
await writeFile(
|
|
783
|
+
path2.join(target, "package.json"),
|
|
784
|
+
`${JSON.stringify(
|
|
785
|
+
{
|
|
786
|
+
name: `@suda-themes/${key}`,
|
|
787
|
+
version: "0.1.0",
|
|
788
|
+
private: true,
|
|
789
|
+
type: "module",
|
|
790
|
+
main: "./dist/index.js",
|
|
791
|
+
types: "./dist/index.d.ts",
|
|
792
|
+
scripts: {
|
|
793
|
+
build: "tsc -p tsconfig.json",
|
|
794
|
+
typecheck: "tsc -p tsconfig.json --noEmit"
|
|
795
|
+
},
|
|
796
|
+
dependencies: { "@sudajs/theme-engine": "workspace:*" },
|
|
797
|
+
peerDependencies: {
|
|
798
|
+
"@puckeditor/core": "^0.21.2",
|
|
799
|
+
react: "^19.0.0",
|
|
800
|
+
"react-dom": "^19.0.0"
|
|
801
|
+
}
|
|
802
|
+
},
|
|
803
|
+
null,
|
|
804
|
+
2
|
|
805
|
+
)}
|
|
806
|
+
`
|
|
807
|
+
);
|
|
808
|
+
await writeFile(
|
|
809
|
+
path2.join(target, "tsconfig.json"),
|
|
810
|
+
'{\n "extends": "@suda/tsconfig/node.json",\n "compilerOptions": { "lib": ["DOM", "DOM.Iterable", "ES2022"], "jsx": "react-jsx", "rootDir": "src", "outDir": "dist" },\n "include": ["src"]\n}\n'
|
|
811
|
+
);
|
|
812
|
+
await writeFile(
|
|
813
|
+
path2.join(target, "src", "manifest.ts"),
|
|
814
|
+
`import type { ThemeManifest } from "@sudajs/theme-engine";
|
|
815
|
+
|
|
816
|
+
export const manifest: ThemeManifest = {
|
|
817
|
+
key: "${key}",
|
|
818
|
+
name: "${key}",
|
|
819
|
+
version: "0.1.0",
|
|
820
|
+
categories: ["other"],
|
|
821
|
+
minEngineVersion: "0.0.0",
|
|
822
|
+
entry: "dist/index.js",
|
|
823
|
+
clientEntry: "dist/runtime.client.js",
|
|
824
|
+
};
|
|
825
|
+
`
|
|
826
|
+
);
|
|
827
|
+
await writeFile(
|
|
828
|
+
path2.join(target, "src", "sections.tsx"),
|
|
829
|
+
`import type { ComponentConfig } from "@puckeditor/core";
|
|
830
|
+
|
|
831
|
+
export const Hero: ComponentConfig = {
|
|
832
|
+
label: "Hero",
|
|
833
|
+
fields: {
|
|
834
|
+
eyebrow: { type: "text", label: "Eyebrow" },
|
|
835
|
+
title: { type: "text", label: "Title" },
|
|
836
|
+
description: { type: "textarea", label: "Description" },
|
|
837
|
+
},
|
|
838
|
+
defaultProps: {
|
|
839
|
+
eyebrow: "New theme",
|
|
840
|
+
title: "Build with SudaCloud",
|
|
841
|
+
description: "Edit this starter section in the visual editor.",
|
|
842
|
+
},
|
|
843
|
+
render: ({ eyebrow, title, description }) => (
|
|
844
|
+
<section className="${key}-hero">
|
|
845
|
+
<p>{eyebrow}</p>
|
|
846
|
+
<h1>{title}</h1>
|
|
847
|
+
<div>{description}</div>
|
|
848
|
+
</section>
|
|
849
|
+
),
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
export const SECTION_COMPONENTS = { Hero };
|
|
853
|
+
`
|
|
854
|
+
);
|
|
855
|
+
await writeFile(
|
|
856
|
+
path2.join(target, "src", "layout.tsx"),
|
|
857
|
+
`import { getPageSlot } from "@sudajs/theme-engine/runtime";
|
|
858
|
+
import type { ComponentConfig, Config } from "@puckeditor/core";
|
|
859
|
+
import type { ReactElement, ReactNode } from "react";
|
|
860
|
+
|
|
861
|
+
type PuckExtras = { puck?: { metadata?: Record<string, unknown> } };
|
|
862
|
+
|
|
863
|
+
export const rootConfig: NonNullable<Config["root"]> = {
|
|
864
|
+
fields: {
|
|
865
|
+
siteName: { type: "text", label: "Site name" },
|
|
866
|
+
},
|
|
867
|
+
defaultProps: { siteName: "${key}" },
|
|
868
|
+
render: ({ children, siteName }: { children?: ReactNode; siteName?: string }) => (
|
|
869
|
+
<div className="${key}-root" data-site-name={siteName}>{children}</div>
|
|
870
|
+
),
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
export const Header: ComponentConfig = {
|
|
874
|
+
label: "Header",
|
|
875
|
+
fields: { siteName: { type: "text", label: "Site name" } },
|
|
876
|
+
defaultProps: { siteName: "${key}" },
|
|
877
|
+
render: ({ siteName }) => <header className="${key}-header">{siteName}</header>,
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
export const PageOutlet: ComponentConfig = {
|
|
881
|
+
label: "Page outlet",
|
|
882
|
+
fields: {},
|
|
883
|
+
defaultProps: {},
|
|
884
|
+
render: (props: PuckExtras): ReactElement => <>{getPageSlot(props.puck?.metadata)}</>,
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
export const Footer: ComponentConfig = {
|
|
888
|
+
label: "Footer",
|
|
889
|
+
fields: { text: { type: "text", label: "Text" } },
|
|
890
|
+
defaultProps: { text: "\xA9 ${key}" },
|
|
891
|
+
render: ({ text }) => <footer className="${key}-footer">{text}</footer>,
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
export const LAYOUT_COMPONENTS = { Header, PageOutlet, Footer };
|
|
895
|
+
`
|
|
896
|
+
);
|
|
897
|
+
await writeFile(
|
|
898
|
+
path2.join(target, "src", "config.ts"),
|
|
899
|
+
`import type { Config, Data } from "@puckeditor/core";
|
|
900
|
+
|
|
901
|
+
import { LAYOUT_COMPONENTS, rootConfig } from "./layout.js";
|
|
902
|
+
import { SECTION_COMPONENTS } from "./sections.js";
|
|
903
|
+
|
|
904
|
+
export const pageConfig: Config = {
|
|
905
|
+
components: SECTION_COMPONENTS,
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
export const layoutConfig: Config = {
|
|
909
|
+
root: rootConfig,
|
|
910
|
+
components: LAYOUT_COMPONENTS,
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
export const defaultLayout: Data = {
|
|
914
|
+
root: { props: { siteName: "${key}" } },
|
|
915
|
+
content: [
|
|
916
|
+
{ type: "Header", props: { id: "Header-1", siteName: "${key}" } },
|
|
917
|
+
{ type: "PageOutlet", props: { id: "PageOutlet-1" } },
|
|
918
|
+
{ type: "Footer", props: { id: "Footer-1", text: "\xA9 ${key}" } },
|
|
919
|
+
],
|
|
920
|
+
};
|
|
921
|
+
`
|
|
922
|
+
);
|
|
923
|
+
await writeFile(
|
|
924
|
+
path2.join(target, "src", "templates.ts"),
|
|
925
|
+
`import type { ThemeStarterPage } from "@sudajs/theme-engine";
|
|
926
|
+
|
|
927
|
+
export const starterPages: ThemeStarterPage[] = [
|
|
928
|
+
{
|
|
929
|
+
slug: "home",
|
|
930
|
+
title: "Home",
|
|
931
|
+
isHome: true,
|
|
932
|
+
data: {
|
|
933
|
+
root: { props: {} },
|
|
934
|
+
content: [
|
|
935
|
+
{
|
|
936
|
+
type: "Hero",
|
|
937
|
+
props: {
|
|
938
|
+
id: "Hero-1",
|
|
939
|
+
eyebrow: "Starter page",
|
|
940
|
+
title: "Welcome to ${key}",
|
|
941
|
+
description: "This page was generated by suda theme init.",
|
|
942
|
+
},
|
|
943
|
+
},
|
|
944
|
+
],
|
|
945
|
+
},
|
|
946
|
+
},
|
|
947
|
+
];
|
|
948
|
+
`
|
|
949
|
+
);
|
|
950
|
+
await writeFile(
|
|
951
|
+
path2.join(target, "src", "index.tsx"),
|
|
952
|
+
`import type { ThemeModule } from "@sudajs/theme-engine";
|
|
953
|
+
|
|
954
|
+
import { defaultLayout, layoutConfig, pageConfig } from "./config.js";
|
|
955
|
+
import { manifest } from "./manifest.js";
|
|
956
|
+
import { starterPages } from "./templates.js";
|
|
957
|
+
|
|
958
|
+
const theme: ThemeModule = {
|
|
959
|
+
manifest,
|
|
960
|
+
pageConfig,
|
|
961
|
+
layoutConfig,
|
|
962
|
+
defaultLayout,
|
|
963
|
+
starterPages,
|
|
964
|
+
};
|
|
965
|
+
|
|
966
|
+
export default theme;
|
|
967
|
+
export { manifest, pageConfig, layoutConfig, defaultLayout, starterPages };
|
|
968
|
+
`
|
|
969
|
+
);
|
|
970
|
+
await writeFile(
|
|
971
|
+
path2.join(target, "src", "runtime.client.ts"),
|
|
972
|
+
'"use client";\n\nimport theme from "./index.js";\n\nexport default theme;\n'
|
|
973
|
+
);
|
|
974
|
+
await writeFile(path2.join(target, "styles.css"), "\n");
|
|
975
|
+
await writeFile(path2.join(target, ".gitignore"), "node_modules\ndist\n*.tsbuildinfo\n");
|
|
976
|
+
await writeFile(
|
|
977
|
+
path2.join(target, "README.md"),
|
|
978
|
+
`# ${key}
|
|
979
|
+
|
|
980
|
+
A Suda theme scaffolded with \`suda theme init\`.
|
|
981
|
+
|
|
982
|
+
## Develop
|
|
983
|
+
|
|
984
|
+
\`\`\`bash
|
|
985
|
+
suda theme dev # rebuild the browser runtime on change
|
|
986
|
+
suda theme preview # build and preview the home starter page
|
|
987
|
+
\`\`\`
|
|
988
|
+
|
|
989
|
+
## Publish
|
|
990
|
+
|
|
991
|
+
\`\`\`bash
|
|
992
|
+
suda theme build
|
|
993
|
+
suda theme screenshot # optional: capture dist/preview/desktop.png
|
|
994
|
+
suda theme publish
|
|
995
|
+
\`\`\`
|
|
996
|
+
`
|
|
997
|
+
);
|
|
998
|
+
console.log(`created theme scaffold at ${target}`);
|
|
999
|
+
}
|
|
1000
|
+
async function main() {
|
|
1001
|
+
const program = new Command();
|
|
1002
|
+
program.name("suda").description("Suda CLI for managing themes, sites, posts and AI tooling.").version("0.0.0");
|
|
1003
|
+
const theme = program.command("theme").description("Manage Suda theme artifacts.");
|
|
1004
|
+
theme.command("init").description("Scaffold a new theme source directory.").argument("[dir]", "Theme directory to create.", "./theme").action(async (dir) => {
|
|
1005
|
+
await initTheme(path2.resolve(dir));
|
|
1006
|
+
});
|
|
1007
|
+
theme.command("validate").description("Validate a built theme artifact.").option("--theme-root <path>", "Theme source/artifact root.").action(async (options) => {
|
|
1008
|
+
const result = await validateTheme(resolveThemeRoot(options));
|
|
1009
|
+
console.log(`valid ${result.module.manifest.key}@${result.module.manifest.version}`);
|
|
1010
|
+
if (!result.clientEntryPath) {
|
|
1011
|
+
console.warn(
|
|
1012
|
+
"warning: dist/runtime.client.js is missing; editor runtime will not load until `suda theme build` runs."
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
theme.command("build").description("Build server dist (via package script) and browser runtime.").option("--theme-root <path>", "Theme source/artifact root.").option("--skip-theme-build", "Skip the theme package build script.").action(async (options) => {
|
|
1017
|
+
const result = await buildTheme(resolveThemeRoot(options), options.skipThemeBuild === true);
|
|
1018
|
+
console.log(`built ${result.module.manifest.key}@${result.module.manifest.version}`);
|
|
1019
|
+
});
|
|
1020
|
+
theme.command("dev").description("Watch and rebuild the browser runtime.").option("--theme-root <path>", "Theme source/artifact root.").option("--skip-theme-build", "Skip the theme package build script.").action(async (options) => {
|
|
1021
|
+
await watchTheme(resolveThemeRoot(options), options.skipThemeBuild === true);
|
|
1022
|
+
});
|
|
1023
|
+
theme.command("preview").description("Build and serve the local theme artifact files.").option("--theme-root <path>", "Theme source/artifact root.").option("--port <port>", "Preview server port.", "4177").action(async (options) => {
|
|
1024
|
+
const port = Number(options.port ?? "4177");
|
|
1025
|
+
await previewTheme(resolveThemeRoot(options), Number.isFinite(port) ? port : 4177);
|
|
1026
|
+
});
|
|
1027
|
+
theme.command("screenshot").description(
|
|
1028
|
+
"Capture a desktop preview screenshot of the home starter page using Playwright."
|
|
1029
|
+
).option("--theme-root <path>", "Theme source/artifact root.").option("--output <path>", "Output PNG path relative to theme root.").option("--width <px>", "Viewport width in pixels.", "1280").option("--height <px>", "Viewport height in pixels.", "800").option("--port <port>", "Preview server port used during capture.", "4178").option("--skip-build", "Skip rebuilding the theme before capturing.").action(async (options) => {
|
|
1030
|
+
await screenshotTheme(resolveThemeRoot(options), options);
|
|
1031
|
+
});
|
|
1032
|
+
theme.command("publish").description("Upload artifact to S3 and upsert ThemePackage/ThemeVersion.").option("--theme-root <path>", "Theme source/artifact root.").option("--skip-build", "Publish existing dist files without rebuilding.").action(async (options) => {
|
|
1033
|
+
await publishTheme(resolveThemeRoot(options), options.skipBuild === true);
|
|
1034
|
+
});
|
|
1035
|
+
const authCmd = program.command("auth").description("Manage Suda authentication.");
|
|
1036
|
+
authCmd.command("login").description("Authenticate Suda CLI with a SudaCloud workspace.").option("--host <host>", "The SudaCloud workspace host to authenticate against.", "workspace.sudacloud.com").action(async (options) => {
|
|
1037
|
+
await login(options.host);
|
|
1038
|
+
});
|
|
1039
|
+
authCmd.command("status").description("Check current authentication status.").action(async () => {
|
|
1040
|
+
await status();
|
|
1041
|
+
});
|
|
1042
|
+
authCmd.command("logout").description("Clear local authentication configuration.").action(async () => {
|
|
1043
|
+
await logout();
|
|
1044
|
+
});
|
|
1045
|
+
program.showHelpAfterError();
|
|
1046
|
+
const argv = process.argv.filter((arg, index) => index < 2 || arg !== "--");
|
|
1047
|
+
await program.parseAsync(argv);
|
|
1048
|
+
}
|
|
1049
|
+
main().catch((error) => {
|
|
1050
|
+
console.error(error instanceof Error ? error.message : error);
|
|
1051
|
+
process.exitCode = 1;
|
|
1052
|
+
});
|
|
1053
|
+
//# sourceMappingURL=index.js.map
|
|
1054
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/auth.ts","../src/index.ts"],"names":["path","esbuild","context","esbuildContext"],"mappings":"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","file":"index.js","sourcesContent":["import fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\ninterface AuthConfig {\n sessionToken: string;\n host: string;\n}\n\nfunction getConfigPath() {\n return path.join(os.homedir(), \".config\", \"suda\", \"config.json\");\n}\n\nexport async function readAuthConfig(): Promise<AuthConfig | null> {\n try {\n const configPath = getConfigPath();\n const content = await fs.readFile(configPath, \"utf-8\");\n return JSON.parse(content) as AuthConfig;\n } catch {\n return null;\n }\n}\n\nexport async function writeAuthConfig(config: AuthConfig): Promise<void> {\n const configPath = getConfigPath();\n await fs.mkdir(path.dirname(configPath), { recursive: true });\n await fs.chmod(path.dirname(configPath), 0o700).catch(() => undefined);\n await fs.writeFile(configPath, JSON.stringify(config, null, 2), { mode: 0o600 });\n}\n\nexport async function clearAuthConfig(): Promise<void> {\n const configPath = getConfigPath();\n try {\n await fs.unlink(configPath);\n } catch (err: unknown) {\n if (err instanceof Error && \"code\" in err && (err as { code: string }).code !== \"ENOENT\") {\n throw err;\n }\n }\n}\n\nexport async function login(host: string = \"workspace.sudacloud.com\") {\n const protocol = host.includes(\"localhost\") || host.includes(\"127.0.0.1\") ? \"http\" : \"https\";\n const baseUrl = `${protocol}://${host}`;\n\n console.log(`Requesting device authorization from ${baseUrl}...`);\n\n const deviceRes = await fetch(`${baseUrl}/api/cli-auth/device`, {\n method: \"POST\",\n });\n\n if (!deviceRes.ok) {\n throw new Error(`Failed to initialize auth: ${deviceRes.statusText}`);\n }\n\n const { deviceCode, userCode, verificationUri, interval, expiresIn } = (await deviceRes.json()) as {\n deviceCode: string;\n userCode: string;\n verificationUri: string;\n expiresIn: number;\n interval: number;\n };\n\n const authUrl = `${verificationUri}?code=${userCode}`;\n console.log(`\\nPlease open the following URL in your browser to authorize Suda CLI:\\n`);\n console.log(` ${authUrl}\\n`);\n console.log(`Your confirmation code is: ${userCode}\\n`);\n console.log(\"Waiting for authorization...\");\n\n // Try to open browser\n try {\n const open = (await import(\"open\")).default;\n await open(authUrl);\n } catch {\n // ignore\n }\n\n const pollInterval = (interval || 5) * 1000;\n const deadline = Date.now() + (expiresIn || 900) * 1000;\n\n while (Date.now() < deadline) {\n await new Promise((resolve) => setTimeout(resolve, pollInterval));\n\n const pollRes = await fetch(`${baseUrl}/api/cli-auth/poll`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ deviceCode }),\n });\n\n const data = (await pollRes.json()) as { status?: string; token?: string; error?: string };\n\n if (pollRes.ok && data.status === \"approved\" && data.token) {\n await writeAuthConfig({ sessionToken: data.token, host });\n console.log(\"✅ Successfully authorized!\");\n return;\n }\n\n if (data.error === \"authorization_pending\") {\n continue;\n }\n\n throw new Error(`Authorization failed: ${data.error || \"Unknown error\"}`);\n }\n\n throw new Error(\"Authorization timed out.\");\n}\n\nexport async function status() {\n const config = await readAuthConfig();\n if (!config) {\n console.log(\"Not logged in. Run `suda auth login` to authenticate.\");\n return;\n }\n\n const protocol = config.host.includes(\"localhost\") || config.host.includes(\"127.0.0.1\") ? \"http\" : \"https\";\n \n try {\n const res = await fetch(`${protocol}://${config.host}/api/auth/get-session`, {\n headers: {\n Authorization: `Bearer ${config.sessionToken}`,\n },\n });\n\n if (res.ok) {\n const session = (await res.json()) as { user: { email?: string; name?: string } };\n console.log(`Logged in as ${session.user.email || session.user.name} on ${config.host}`);\n } else {\n console.log(\"Session expired or invalid. Please run `suda auth login` again.\");\n await clearAuthConfig();\n }\n } catch {\n console.error(`Failed to connect to ${config.host}.`);\n }\n}\n\nexport async function logout() {\n const config = await readAuthConfig();\n if (!config) {\n console.log(\"Not logged in.\");\n return;\n }\n \n await clearAuthConfig();\n console.log(\"Logged out successfully.\");\n}\n","#!/usr/bin/env node\n\nimport { createHash } from \"node:crypto\";\nimport { mkdir, readFile, readdir, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nimport type { ThemeModule, ThemeStarterPage } from \"@sudajs/theme-engine\";\nimport { ThemeRender, extractLayoutChrome } from \"@sudajs/theme-engine/server\";\nimport { Command } from \"commander\";\nimport { build as esbuild, context as esbuildContext, type Plugin } from \"esbuild\";\nimport { createElement } from \"react\";\nimport { renderToStaticMarkup } from \"react-dom/server\";\n\ninterface ValidatedTheme {\n root: string;\n module: ThemeModule;\n serverEntryPath: string;\n clientEntryPath: string | null;\n stylesheetPath: string | null;\n}\n\ninterface UploadFile {\n absolutePath: string;\n relativePath: string;\n contentType: string;\n}\n\ninterface ThemeRootOptions {\n themeRoot?: string;\n}\n\ninterface BuildOptions extends ThemeRootOptions {\n skipThemeBuild?: boolean;\n}\n\ninterface PublishOptions extends ThemeRootOptions {\n skipBuild?: boolean;\n bucket?: string;\n}\n\ninterface PreviewOptions extends ThemeRootOptions {\n port?: string;\n}\n\nfunction createHostReactShimPlugin(): Plugin {\n return {\n name: \"suda-host-react-shim\",\n setup(build) {\n build.onResolve({ filter: /^react$/ }, () => ({\n path: \"react-host-shim\",\n namespace: \"suda-shim\",\n }));\n build.onResolve({ filter: /^react\\/jsx-runtime$/ }, () => ({\n path: \"react-jsx-runtime-host-shim\",\n namespace: \"suda-shim\",\n }));\n build.onResolve({ filter: /^react-dom$/ }, () => ({\n path: \"react-dom-host-shim\",\n namespace: \"suda-shim\",\n }));\n build.onResolve({ filter: /^react-dom\\/client$/ }, () => ({\n path: \"react-dom-client-host-shim\",\n namespace: \"suda-shim\",\n }));\n build.onLoad({ filter: /^react-host-shim$/, namespace: \"suda-shim\" }, () => ({\n loader: \"js\",\n contents: `\nconst React = globalThis.__SUDA_REACT__;\nif (!React) {\n throw new Error(\"Missing host React runtime (__SUDA_REACT__).\");\n}\nexport default React;\nexport const {\n Children,\n Component,\n Fragment,\n Profiler,\n PureComponent,\n StrictMode,\n Suspense,\n cloneElement,\n createContext,\n createElement,\n createRef,\n forwardRef,\n isValidElement,\n lazy,\n memo,\n startTransition,\n use,\n useActionState,\n useCallback,\n useContext,\n useDeferredValue,\n useEffect,\n useId,\n useImperativeHandle,\n useInsertionEffect,\n useLayoutEffect,\n useMemo,\n useOptimistic,\n useReducer,\n useRef,\n useState,\n useSyncExternalStore,\n useTransition,\n version,\n} = React;\n`,\n }));\n build.onLoad(\n {\n filter: /^react-jsx-runtime-host-shim$/,\n namespace: \"suda-shim\",\n },\n () => ({\n loader: \"js\",\n contents: `\nconst runtime = globalThis.__SUDA_REACT_JSX_RUNTIME__;\nif (!runtime) {\n throw new Error(\"Missing host React JSX runtime (__SUDA_REACT_JSX_RUNTIME__).\");\n}\nexport const { Fragment, jsx, jsxs, jsxDEV } = runtime;\n`,\n }),\n );\n build.onLoad({ filter: /^react-dom-host-shim$/, namespace: \"suda-shim\" }, () => ({\n loader: \"js\",\n contents: `\nconst ReactDOM = globalThis.__SUDA_REACT_DOM__;\nif (!ReactDOM) {\n throw new Error(\"Missing host React DOM runtime (__SUDA_REACT_DOM__).\");\n}\nexport default ReactDOM;\nexport const {\n createPortal,\n flushSync,\n preconnect,\n prefetchDNS,\n preinit,\n preinitModule,\n preload,\n preloadModule,\n requestFormReset,\n unstable_batchedUpdates,\n useFormState,\n useFormStatus,\n version,\n} = ReactDOM;\n`,\n }));\n build.onLoad({ filter: /^react-dom-client-host-shim$/, namespace: \"suda-shim\" }, () => ({\n loader: \"js\",\n contents: `\nconst ReactDOMClient = globalThis.__SUDA_REACT_DOM_CLIENT__;\nif (!ReactDOMClient) {\n throw new Error(\"Missing host React DOM client runtime (__SUDA_REACT_DOM_CLIENT__).\");\n}\nexport default ReactDOMClient;\nexport const { createRoot, hydrateRoot, version } = ReactDOMClient;\n`,\n }));\n },\n };\n}\n\nfunction safeObjectKeyPart(value: string): string {\n return value.replace(/[^a-zA-Z0-9._-]/g, \"-\");\n}\n\nfunction createThemePrefix(themeKey: string, version: string): string {\n return `themes/${safeObjectKeyPart(themeKey)}/${safeObjectKeyPart(version)}`;\n}\n\nfunction createThemeObjectKey(themeKey: string, version: string, relativePath: string): string {\n const safeRelativePath = relativePath\n .replace(/^\\/+/, \"\")\n .split(\"/\")\n .map(safeObjectKeyPart)\n .filter(Boolean)\n .join(\"/\");\n\n return `${createThemePrefix(themeKey, version)}/${safeRelativePath}`;\n}\n\nimport { login, status, logout, readAuthConfig } from \"./auth\";\n\nfunction resolveThemeRoot(options: ThemeRootOptions): string {\n return path.resolve(options.themeRoot ?? process.cwd());\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await stat(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function readJson<T>(filePath: string): Promise<T> {\n const raw = await readFile(filePath, \"utf8\");\n return JSON.parse(raw) as T;\n}\n\nasync function loadThemeModule(serverEntryPath: string): Promise<ThemeModule> {\n const imported = (await import(`${pathToFileURL(serverEntryPath).href}?t=${Date.now()}`)) as {\n default?: ThemeModule;\n };\n\n if (!imported.default) {\n throw new Error(`${serverEntryPath} must export a default ThemeModule.`);\n }\n\n return imported.default;\n}\n\nfunction assertRecord(value: unknown, label: string): asserts value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new Error(`${label} must be an object.`);\n }\n}\n\nfunction validateThemeModule(module: ThemeModule): void {\n assertRecord(module.manifest, \"manifest\");\n if (!module.manifest.key || !module.manifest.version || !module.manifest.name) {\n throw new Error(\"manifest.key, manifest.version and manifest.name are required.\");\n }\n if (module.manifest.entry !== \"dist/index.js\") {\n throw new Error('manifest.entry must be artifact-local: \"dist/index.js\".');\n }\n if (\n module.manifest.clientEntry !== undefined &&\n module.manifest.clientEntry !== \"dist/runtime.client.js\"\n ) {\n throw new Error(\n 'manifest.clientEntry must be artifact-local: \"dist/runtime.client.js\" when provided.',\n );\n }\n assertRecord(module.pageConfig?.components, \"pageConfig.components\");\n assertRecord(module.layoutConfig, \"layoutConfig\");\n assertRecord(module.defaultLayout, \"defaultLayout\");\n if (!Array.isArray(module.starterPages)) {\n throw new Error(\"starterPages must be an array.\");\n }\n}\n\nasync function validateTheme(root: string): Promise<ValidatedTheme> {\n const packageJsonPath = path.join(root, \"package.json\");\n const serverEntryPath = path.join(root, \"dist\", \"index.js\");\n const clientEntryPath = path.join(root, \"dist\", \"runtime.client.js\");\n const stylesheetPath = path.join(root, \"styles.css\");\n\n if (!(await pathExists(packageJsonPath))) {\n throw new Error(`Missing package.json in ${root}`);\n }\n if (!(await pathExists(serverEntryPath))) {\n throw new Error(`Missing ${serverEntryPath}. Run the theme package build first.`);\n }\n\n const module = await loadThemeModule(serverEntryPath);\n validateThemeModule(module);\n\n const result: ValidatedTheme = {\n root,\n module,\n serverEntryPath,\n clientEntryPath: null,\n stylesheetPath: null,\n };\n if (await pathExists(clientEntryPath)) {\n result.clientEntryPath = clientEntryPath;\n }\n if (await pathExists(stylesheetPath)) {\n result.stylesheetPath = stylesheetPath;\n }\n return result;\n}\n\nasync function runCommand(command: string, args: string[], cwd: string): Promise<void> {\n const { spawn } = await import(\"node:child_process\");\n await new Promise<void>((resolve, reject) => {\n const child = spawn(command, args, { cwd, stdio: \"inherit\" });\n child.on(\"error\", reject);\n child.on(\"exit\", (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`${command} ${args.join(\" \")} exited with ${code ?? \"unknown\"}.`));\n }\n });\n });\n}\n\nasync function runThemePackageBuild(root: string): Promise<void> {\n const packageJson = await readJson<{ scripts?: Record<string, string> }>(\n path.join(root, \"package.json\"),\n );\n const buildScript = packageJson.scripts?.build;\n if (!buildScript || buildScript.includes(\"suda theme\") || buildScript.includes(\"suda-theme\")) {\n return;\n }\n await runCommand(\"pnpm\", [\"--dir\", root, \"run\", \"build\"], process.cwd());\n}\n\nasync function findClientEntry(root: string): Promise<string> {\n const candidates = [\n path.join(root, \"src\", \"runtime.client.tsx\"),\n path.join(root, \"src\", \"runtime.client.ts\"),\n ];\n for (const candidate of candidates) {\n if (await pathExists(candidate)) {\n return candidate;\n }\n }\n throw new Error(`Missing runtime entry. Add src/runtime.client.ts(x) in ${root}.`);\n}\n\nasync function findServerEntry(root: string): Promise<string> {\n const candidates = [path.join(root, \"src\", \"index.tsx\"), path.join(root, \"src\", \"index.ts\")];\n for (const candidate of candidates) {\n if (await pathExists(candidate)) {\n return candidate;\n }\n }\n throw new Error(`Missing server entry. Add src/index.ts(x) in ${root}.`);\n}\n\nasync function buildServerBundle(root: string): Promise<void> {\n const entryPoint = await findServerEntry(root);\n\n await esbuild({\n bundle: true,\n entryPoints: [entryPoint],\n external: [\n \"react\",\n \"react/jsx-runtime\",\n \"react-dom\",\n \"react-dom/*\",\n \"@puckeditor/core\",\n \"@puckeditor/core/*\",\n \"@sudajs/theme-engine\",\n \"@sudajs/theme-engine/*\",\n ],\n format: \"esm\",\n jsx: \"automatic\",\n minify: false,\n outfile: path.join(root, \"dist\", \"index.js\"),\n platform: \"node\",\n sourcemap: false,\n target: \"es2022\",\n treeShaking: true,\n });\n}\n\nasync function buildTheme(root: string, skipThemeBuild: boolean): Promise<ValidatedTheme> {\n if (!skipThemeBuild) {\n await runThemePackageBuild(root);\n }\n\n await buildServerBundle(root);\n\n const entryPoint = await findClientEntry(root);\n if (!(await pathExists(entryPoint))) {\n throw new Error(`Missing client entry: ${entryPoint}`);\n }\n\n await mkdir(path.join(root, \"dist\"), { recursive: true });\n const hostReactShimPlugin = createHostReactShimPlugin();\n await esbuild({\n bundle: true,\n entryPoints: [entryPoint],\n format: \"esm\",\n jsx: \"automatic\",\n minify: true,\n outfile: path.join(root, \"dist\", \"runtime.client.js\"),\n platform: \"browser\",\n plugins: [hostReactShimPlugin],\n sourcemap: false,\n target: \"es2022\",\n treeShaking: true,\n });\n\n const validated = await validateTheme(root);\n await writeThemeArtifacts(validated);\n return validateTheme(root);\n}\n\nasync function watchTheme(root: string, skipThemeBuild: boolean): Promise<void> {\n if (!skipThemeBuild) {\n await runThemePackageBuild(root);\n }\n\n await buildServerBundle(root);\n\n const entryPoint = await findClientEntry(root);\n await mkdir(path.join(root, \"dist\"), { recursive: true });\n const hostReactShimPlugin = createHostReactShimPlugin();\n\n const context = await esbuildContext({\n bundle: true,\n entryPoints: [entryPoint],\n format: \"esm\",\n jsx: \"automatic\",\n minify: false,\n outfile: path.join(root, \"dist\", \"runtime.client.js\"),\n platform: \"browser\",\n plugins: [hostReactShimPlugin],\n sourcemap: true,\n target: \"es2022\",\n treeShaking: true,\n });\n await context.watch();\n console.log(\"watching theme runtime; press Ctrl+C to stop\");\n await new Promise(() => undefined);\n}\n\ninterface PreviewServerHandle {\n url: string;\n port: number;\n close: () => Promise<void>;\n}\n\nfunction pickStarterPage(theme: ValidatedTheme): ThemeStarterPage | null {\n const pages = theme.module.starterPages;\n if (pages.length === 0) {\n return null;\n }\n return pages.find((page) => page.isHome) ?? pages[0] ?? null;\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\nfunction renderStarterPageHtml(theme: ValidatedTheme, page: ThemeStarterPage): string {\n const chrome = extractLayoutChrome(theme.module.defaultLayout);\n const body = renderToStaticMarkup(\n createElement(ThemeRender, {\n theme: theme.module,\n pageData: page.data,\n layoutData: theme.module.defaultLayout,\n }),\n );\n const cssVarStyle = Object.entries(chrome.cssVariables)\n .map(([key, value]) => `${key}: ${value};`)\n .join(\" \");\n const stylesheetTag = theme.stylesheetPath\n ? '<link rel=\"stylesheet\" href=\"/styles.css\" />'\n : \"\";\n const customHead = chrome.customHeadCode ?? \"\";\n const customBody = chrome.customBodyCode ?? \"\";\n return [\n \"<!doctype html>\",\n '<html lang=\"en\">',\n \"<head>\",\n '<meta charset=\"utf-8\" />',\n '<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />',\n `<title>${escapeHtml(`${theme.module.manifest.name} — ${page.title}`)}</title>`,\n stylesheetTag,\n customHead,\n \"</head>\",\n `<body${cssVarStyle ? ` style=\"${cssVarStyle}\"` : \"\"}>`,\n body,\n customBody,\n \"</body>\",\n \"</html>\",\n ].join(\"\");\n}\n\nasync function startPreviewServer(\n theme: ValidatedTheme,\n port: number,\n): Promise<PreviewServerHandle> {\n const { createServer } = await import(\"node:http\");\n const starterPage = pickStarterPage(theme);\n const server = createServer((request, response) => {\n void (async () => {\n const url = new URL(request.url ?? \"/\", `http://localhost:${port}`);\n const pathname = url.pathname;\n\n if (pathname === \"/\" || pathname === \"/index.html\") {\n if (!starterPage) {\n response.writeHead(404, { \"Content-Type\": \"text/plain; charset=utf-8\" });\n response.end(\"No starter page available for preview.\");\n return;\n }\n response.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n response.end(renderStarterPageHtml(theme, starterPage));\n return;\n }\n\n if (pathname === \"/styles.css\" && theme.stylesheetPath) {\n response.writeHead(200, { \"Content-Type\": \"text/css; charset=utf-8\" });\n response.end(await readFile(theme.stylesheetPath));\n return;\n }\n\n const relativePath = pathname.replace(/^\\/+/, \"\");\n const filePath = path.resolve(theme.root, relativePath);\n const normalizedRoot = path.resolve(theme.root) + path.sep;\n if (!filePath.startsWith(normalizedRoot) || !(await pathExists(filePath))) {\n response.writeHead(404, { \"Content-Type\": \"text/plain; charset=utf-8\" });\n response.end(\"Not found\");\n return;\n }\n response.writeHead(200, { \"Content-Type\": contentTypeFor(relativePath) });\n response.end(await readFile(filePath));\n })().catch((error) => {\n response.writeHead(500, { \"Content-Type\": \"text/plain; charset=utf-8\" });\n response.end(error instanceof Error ? error.message : \"Internal error\");\n });\n });\n\n await new Promise<void>((resolve, reject) => {\n const onError = (err: Error): void => {\n reject(err);\n };\n server.once(\"error\", onError);\n server.listen(port, () => {\n server.off(\"error\", onError);\n resolve();\n });\n });\n\n return {\n url: `http://localhost:${port}`,\n port,\n close: () =>\n new Promise<void>((resolve) => {\n server.close(() => {\n resolve();\n });\n }),\n };\n}\n\nasync function previewTheme(root: string, port: number): Promise<void> {\n const theme = await buildTheme(root, false);\n const handle = await startPreviewServer(theme, port);\n console.log(\n `previewing ${theme.module.manifest.key}@${theme.module.manifest.version} at ${handle.url}`,\n );\n await new Promise(() => undefined);\n}\n\ninterface ScreenshotOptions extends ThemeRootOptions {\n output?: string;\n width?: string;\n height?: string;\n port?: string;\n skipBuild?: boolean;\n}\n\ninterface ResolvedScreenshotOptions {\n outputPath: string;\n viewport: { width: number; height: number };\n port: number;\n skipBuild: boolean;\n}\n\nconst DEFAULT_SCREENSHOT_RELATIVE = path.join(\"dist\", \"preview\", \"desktop.png\");\n\nfunction parsePositiveInt(value: string | undefined, fallback: number): number {\n if (!value) {\n return fallback;\n }\n const parsed = Number.parseInt(value, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;\n}\n\nfunction resolveScreenshotOptions(\n root: string,\n options: ScreenshotOptions,\n): ResolvedScreenshotOptions {\n return {\n outputPath: path.resolve(root, options.output ?? DEFAULT_SCREENSHOT_RELATIVE),\n viewport: {\n width: parsePositiveInt(options.width, 1280),\n height: parsePositiveInt(options.height, 800),\n },\n port: parsePositiveInt(options.port, 4178),\n skipBuild: options.skipBuild === true,\n };\n}\n\ninterface PlaywrightModule {\n chromium: {\n launch: (options?: { headless?: boolean }) => Promise<{\n newContext: (options: {\n viewport: { width: number; height: number };\n deviceScaleFactor?: number;\n }) => Promise<{\n newPage: () => Promise<{\n goto: (url: string, options?: { waitUntil?: string }) => Promise<unknown>;\n screenshot: (options: { path: string; fullPage?: boolean }) => Promise<unknown>;\n close: () => Promise<void>;\n }>;\n close: () => Promise<void>;\n }>;\n close: () => Promise<void>;\n }>;\n };\n}\n\nasync function loadPlaywright(): Promise<PlaywrightModule> {\n // Resolve module ids via runtime expressions so the optional dependency is not\n // required at type-check time. Either `playwright` or `@playwright/test` works.\n const candidates = [\"playwright\", \"@playwright/test\"];\n for (const id of candidates) {\n try {\n const mod: unknown = await import(/* @vite-ignore */ id);\n return mod as PlaywrightModule;\n } catch {\n // try next candidate\n }\n }\n throw new Error(\n \"Playwright is required for screenshot capture. Install it as a dev dependency: `pnpm add -D playwright && pnpm exec playwright install chromium`.\",\n );\n}\n\nasync function captureScreenshot(\n theme: ValidatedTheme,\n options: ResolvedScreenshotOptions,\n): Promise<void> {\n const playwright = await loadPlaywright();\n const handle = await startPreviewServer(theme, options.port);\n try {\n await mkdir(path.dirname(options.outputPath), { recursive: true });\n const browser = await playwright.chromium.launch({ headless: true });\n try {\n const context = await browser.newContext({\n viewport: options.viewport,\n deviceScaleFactor: 1,\n });\n const page = await context.newPage();\n await page.goto(handle.url, { waitUntil: \"networkidle\" });\n await page.screenshot({ path: options.outputPath, fullPage: false });\n await context.close();\n } finally {\n await browser.close();\n }\n } finally {\n await handle.close();\n }\n console.log(`captured ${path.relative(theme.root, options.outputPath)}`);\n}\n\nasync function screenshotTheme(root: string, options: ScreenshotOptions): Promise<void> {\n const resolved = resolveScreenshotOptions(root, options);\n const theme = resolved.skipBuild ? await validateTheme(root) : await buildTheme(root, false);\n await captureScreenshot(theme, resolved);\n}\n\nasync function writeThemeArtifacts(theme: ValidatedTheme): Promise<void> {\n const dist = path.join(theme.root, \"dist\");\n await writeFile(\n path.join(dist, \"manifest.json\"),\n `${JSON.stringify(theme.module.manifest, null, 2)}\\n`,\n );\n await writeFile(\n path.join(dist, \"starter-pages.json\"),\n `${JSON.stringify(theme.module.starterPages, null, 2)}\\n`,\n );\n await writeFile(\n path.join(dist, \"default-layout.json\"),\n `${JSON.stringify(theme.module.defaultLayout, null, 2)}\\n`,\n );\n}\n\nfunction contentTypeFor(relativePath: string): string {\n if (relativePath.endsWith(\".css\")) {\n return \"text/css; charset=utf-8\";\n }\n if (relativePath.endsWith(\".js\")) {\n return \"application/javascript; charset=utf-8\";\n }\n if (relativePath.endsWith(\".json\")) {\n return \"application/json; charset=utf-8\";\n }\n if (relativePath.endsWith(\".svg\")) {\n return \"image/svg+xml\";\n }\n if (relativePath.endsWith(\".png\")) {\n return \"image/png\";\n }\n if (relativePath.endsWith(\".jpg\") || relativePath.endsWith(\".jpeg\")) {\n return \"image/jpeg\";\n }\n if (relativePath.endsWith(\".webp\")) {\n return \"image/webp\";\n }\n return \"application/octet-stream\";\n}\n\nasync function collectFiles(root: string, directory: string): Promise<UploadFile[]> {\n const files: UploadFile[] = [];\n if (!(await pathExists(directory))) {\n return files;\n }\n\n const children = await readdir(directory, { withFileTypes: true });\n for (const child of children) {\n const absolutePath = path.join(directory, child.name);\n if (child.isDirectory()) {\n files.push(...(await collectFiles(root, absolutePath)));\n continue;\n }\n const relativePath = path.relative(root, absolutePath).replaceAll(path.sep, \"/\");\n files.push({\n absolutePath,\n relativePath,\n contentType: contentTypeFor(relativePath),\n });\n }\n return files;\n}\n\nasync function collectThemeArtifactFiles(theme: ValidatedTheme): Promise<UploadFile[]> {\n const files = await collectFiles(theme.root, path.join(theme.root, \"dist\"));\n if (theme.stylesheetPath) {\n files.push({\n absolutePath: theme.stylesheetPath,\n relativePath: \"styles.css\",\n contentType: \"text/css; charset=utf-8\",\n });\n }\n return files;\n}\n\nasync function checksum(files: UploadFile[]): Promise<string> {\n const hash = createHash(\"sha256\");\n for (const file of files.sort((a, b) => a.relativePath.localeCompare(b.relativePath))) {\n hash.update(file.relativePath);\n hash.update(await readFile(file.absolutePath));\n }\n return hash.digest(\"hex\");\n}\n\nasync function publishTheme(\n root: string,\n skipBuild: boolean,\n): Promise<void> {\n const theme = skipBuild ? await validateTheme(root) : await buildTheme(root, false);\n const files = await collectThemeArtifactFiles(theme);\n if (!theme.clientEntryPath) {\n throw new Error(\"Missing dist/runtime.client.js. Run `suda theme build` first.\");\n }\n\n const config = await readAuthConfig();\n if (!config) {\n throw new Error(\"Not logged in. Run `suda auth login` to authenticate first.\");\n }\n\n const protocol = config.host.includes(\"localhost\") || config.host.includes(\"127.0.0.1\") ? \"http\" : \"https\";\n const baseUrl = `${protocol}://${config.host}`;\n const { key, version } = theme.module.manifest;\n\n const intentRes = await fetch(`${baseUrl}/api/cli/themes/publish-intent`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${config.sessionToken}`,\n },\n body: JSON.stringify({\n key,\n version,\n files: files.map((f) => ({ relativePath: f.relativePath, contentType: f.contentType })),\n }),\n });\n\n if (!intentRes.ok) {\n const errorData = (await intentRes.json().catch(() => ({}))) as { error?: string };\n throw new Error(`Failed to get publish intent: ${errorData.error || intentRes.statusText}`);\n }\n\n const { urls } = (await intentRes.json()) as { urls: Array<{ relativePath: string; url: string }> };\n\n // Upload to S3 using presigned URLs\n for (const file of files) {\n const urlObj = urls.find((u) => u.relativePath === file.relativePath);\n if (!urlObj) {\n throw new Error(`Missing presigned URL for ${file.relativePath}`);\n }\n\n const body = await readFile(file.absolutePath);\n const putRes = await fetch(urlObj.url, {\n method: \"PUT\",\n headers: {\n \"Content-Type\": file.contentType,\n },\n body,\n });\n\n if (!putRes.ok) {\n throw new Error(`Failed to upload ${file.relativePath}: ${putRes.statusText}`);\n }\n console.log(`uploaded ${createThemeObjectKey(key, version, file.relativePath)}`);\n }\n\n const digest = await checksum(files);\n const bundleArtifactRelative = \"dist/index.js\";\n const bundleArtifactExists = files.some((f) => f.relativePath === bundleArtifactRelative);\n if (!bundleArtifactExists) {\n throw new Error(`Missing ${bundleArtifactRelative} in theme artifacts.`);\n }\n\n const previewArtifactRelative = \"dist/preview/desktop.png\";\n const previewArtifactExists = await pathExists(path.join(theme.root, previewArtifactRelative));\n\n const completeRes = await fetch(`${baseUrl}/api/cli/themes/publish-complete`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${config.sessionToken}`,\n },\n body: JSON.stringify({\n key,\n version,\n checksum: digest,\n manifest: theme.module.manifest,\n bundleArtifactRelative,\n previewArtifactRelative: previewArtifactExists ? previewArtifactRelative : undefined,\n }),\n });\n\n if (!completeRes.ok) {\n const errorData = (await completeRes.json().catch(() => ({}))) as { error?: string };\n throw new Error(`Failed to complete publish: ${errorData.error || completeRes.statusText}`);\n }\n\n console.log(`published ${key}@${version}`);\n}\n\nasync function initTheme(target: string): Promise<void> {\n const key = path\n .basename(target)\n .replace(/[^a-zA-Z0-9._-]/g, \"-\")\n .toLowerCase();\n await mkdir(path.join(target, \"src\"), { recursive: true });\n await writeFile(\n path.join(target, \"package.json\"),\n `${JSON.stringify(\n {\n name: `@suda-themes/${key}`,\n version: \"0.1.0\",\n private: true,\n type: \"module\",\n main: \"./dist/index.js\",\n types: \"./dist/index.d.ts\",\n scripts: {\n build: \"tsc -p tsconfig.json\",\n typecheck: \"tsc -p tsconfig.json --noEmit\",\n },\n dependencies: { \"@sudajs/theme-engine\": \"workspace:*\" },\n peerDependencies: {\n \"@puckeditor/core\": \"^0.21.2\",\n react: \"^19.0.0\",\n \"react-dom\": \"^19.0.0\",\n },\n },\n null,\n 2,\n )}\\n`,\n );\n await writeFile(\n path.join(target, \"tsconfig.json\"),\n '{\\n \"extends\": \"@suda/tsconfig/node.json\",\\n \"compilerOptions\": { \"lib\": [\"DOM\", \"DOM.Iterable\", \"ES2022\"], \"jsx\": \"react-jsx\", \"rootDir\": \"src\", \"outDir\": \"dist\" },\\n \"include\": [\"src\"]\\n}\\n',\n );\n await writeFile(\n path.join(target, \"src\", \"manifest.ts\"),\n `import type { ThemeManifest } from \"@sudajs/theme-engine\";\\n\\nexport const manifest: ThemeManifest = {\\n key: \"${key}\",\\n name: \"${key}\",\\n version: \"0.1.0\",\\n categories: [\"other\"],\\n minEngineVersion: \"0.0.0\",\\n entry: \"dist/index.js\",\\n clientEntry: \"dist/runtime.client.js\",\\n};\\n`,\n );\n await writeFile(\n path.join(target, \"src\", \"sections.tsx\"),\n `import type { ComponentConfig } from \"@puckeditor/core\";\\n\\nexport const Hero: ComponentConfig = {\\n label: \"Hero\",\\n fields: {\\n eyebrow: { type: \"text\", label: \"Eyebrow\" },\\n title: { type: \"text\", label: \"Title\" },\\n description: { type: \"textarea\", label: \"Description\" },\\n },\\n defaultProps: {\\n eyebrow: \"New theme\",\\n title: \"Build with SudaCloud\",\\n description: \"Edit this starter section in the visual editor.\",\\n },\\n render: ({ eyebrow, title, description }) => (\\n <section className=\"${key}-hero\">\\n <p>{eyebrow}</p>\\n <h1>{title}</h1>\\n <div>{description}</div>\\n </section>\\n ),\\n};\\n\\nexport const SECTION_COMPONENTS = { Hero };\\n`,\n );\n await writeFile(\n path.join(target, \"src\", \"layout.tsx\"),\n `import { getPageSlot } from \"@sudajs/theme-engine/runtime\";\\nimport type { ComponentConfig, Config } from \"@puckeditor/core\";\\nimport type { ReactElement, ReactNode } from \"react\";\\n\\ntype PuckExtras = { puck?: { metadata?: Record<string, unknown> } };\\n\\nexport const rootConfig: NonNullable<Config[\"root\"]> = {\\n fields: {\\n siteName: { type: \"text\", label: \"Site name\" },\\n },\\n defaultProps: { siteName: \"${key}\" },\\n render: ({ children, siteName }: { children?: ReactNode; siteName?: string }) => (\\n <div className=\"${key}-root\" data-site-name={siteName}>{children}</div>\\n ),\\n};\\n\\nexport const Header: ComponentConfig = {\\n label: \"Header\",\\n fields: { siteName: { type: \"text\", label: \"Site name\" } },\\n defaultProps: { siteName: \"${key}\" },\\n render: ({ siteName }) => <header className=\"${key}-header\">{siteName}</header>,\\n};\\n\\nexport const PageOutlet: ComponentConfig = {\\n label: \"Page outlet\",\\n fields: {},\\n defaultProps: {},\\n render: (props: PuckExtras): ReactElement => <>{getPageSlot(props.puck?.metadata)}</>,\\n};\\n\\nexport const Footer: ComponentConfig = {\\n label: \"Footer\",\\n fields: { text: { type: \"text\", label: \"Text\" } },\\n defaultProps: { text: \"© ${key}\" },\\n render: ({ text }) => <footer className=\"${key}-footer\">{text}</footer>,\\n};\\n\\nexport const LAYOUT_COMPONENTS = { Header, PageOutlet, Footer };\\n`,\n );\n await writeFile(\n path.join(target, \"src\", \"config.ts\"),\n `import type { Config, Data } from \"@puckeditor/core\";\\n\\nimport { LAYOUT_COMPONENTS, rootConfig } from \"./layout.js\";\\nimport { SECTION_COMPONENTS } from \"./sections.js\";\\n\\nexport const pageConfig: Config = {\\n components: SECTION_COMPONENTS,\\n};\\n\\nexport const layoutConfig: Config = {\\n root: rootConfig,\\n components: LAYOUT_COMPONENTS,\\n};\\n\\nexport const defaultLayout: Data = {\\n root: { props: { siteName: \"${key}\" } },\\n content: [\\n { type: \"Header\", props: { id: \"Header-1\", siteName: \"${key}\" } },\\n { type: \"PageOutlet\", props: { id: \"PageOutlet-1\" } },\\n { type: \"Footer\", props: { id: \"Footer-1\", text: \"© ${key}\" } },\\n ],\\n};\\n`,\n );\n await writeFile(\n path.join(target, \"src\", \"templates.ts\"),\n `import type { ThemeStarterPage } from \"@sudajs/theme-engine\";\\n\\nexport const starterPages: ThemeStarterPage[] = [\\n {\\n slug: \"home\",\\n title: \"Home\",\\n isHome: true,\\n data: {\\n root: { props: {} },\\n content: [\\n {\\n type: \"Hero\",\\n props: {\\n id: \"Hero-1\",\\n eyebrow: \"Starter page\",\\n title: \"Welcome to ${key}\",\\n description: \"This page was generated by suda theme init.\",\\n },\\n },\\n ],\\n },\\n },\\n];\\n`,\n );\n await writeFile(\n path.join(target, \"src\", \"index.tsx\"),\n `import type { ThemeModule } from \"@sudajs/theme-engine\";\\n\\nimport { defaultLayout, layoutConfig, pageConfig } from \"./config.js\";\\nimport { manifest } from \"./manifest.js\";\\nimport { starterPages } from \"./templates.js\";\\n\\nconst theme: ThemeModule = {\\n manifest,\\n pageConfig,\\n layoutConfig,\\n defaultLayout,\\n starterPages,\\n};\\n\\nexport default theme;\\nexport { manifest, pageConfig, layoutConfig, defaultLayout, starterPages };\\n`,\n );\n await writeFile(\n path.join(target, \"src\", \"runtime.client.ts\"),\n '\"use client\";\\n\\nimport theme from \"./index.js\";\\n\\nexport default theme;\\n',\n );\n await writeFile(path.join(target, \"styles.css\"), \"\\n\");\n await writeFile(path.join(target, \".gitignore\"), \"node_modules\\ndist\\n*.tsbuildinfo\\n\");\n await writeFile(\n path.join(target, \"README.md\"),\n `# ${key}\\n\\nA Suda theme scaffolded with \\`suda theme init\\`.\\n\\n## Develop\\n\\n\\`\\`\\`bash\\nsuda theme dev # rebuild the browser runtime on change\\nsuda theme preview # build and preview the home starter page\\n\\`\\`\\`\\n\\n## Publish\\n\\n\\`\\`\\`bash\\nsuda theme build\\nsuda theme screenshot # optional: capture dist/preview/desktop.png\\nsuda theme publish\\n\\`\\`\\`\\n`,\n );\n console.log(`created theme scaffold at ${target}`);\n}\n\nasync function main(): Promise<void> {\n const program = new Command();\n\n program\n .name(\"suda\")\n .description(\"Suda CLI for managing themes, sites, posts and AI tooling.\")\n .version(\"0.0.0\");\n\n const theme = program.command(\"theme\").description(\"Manage Suda theme artifacts.\");\n\n theme\n .command(\"init\")\n .description(\"Scaffold a new theme source directory.\")\n .argument(\"[dir]\", \"Theme directory to create.\", \"./theme\")\n .action(async (dir: string) => {\n await initTheme(path.resolve(dir));\n });\n\n theme\n .command(\"validate\")\n .description(\"Validate a built theme artifact.\")\n .option(\"--theme-root <path>\", \"Theme source/artifact root.\")\n .action(async (options: ThemeRootOptions) => {\n const result = await validateTheme(resolveThemeRoot(options));\n console.log(`valid ${result.module.manifest.key}@${result.module.manifest.version}`);\n if (!result.clientEntryPath) {\n console.warn(\n \"warning: dist/runtime.client.js is missing; editor runtime will not load until `suda theme build` runs.\",\n );\n }\n });\n\n theme\n .command(\"build\")\n .description(\"Build server dist (via package script) and browser runtime.\")\n .option(\"--theme-root <path>\", \"Theme source/artifact root.\")\n .option(\"--skip-theme-build\", \"Skip the theme package build script.\")\n .action(async (options: BuildOptions) => {\n const result = await buildTheme(resolveThemeRoot(options), options.skipThemeBuild === true);\n console.log(`built ${result.module.manifest.key}@${result.module.manifest.version}`);\n });\n\n theme\n .command(\"dev\")\n .description(\"Watch and rebuild the browser runtime.\")\n .option(\"--theme-root <path>\", \"Theme source/artifact root.\")\n .option(\"--skip-theme-build\", \"Skip the theme package build script.\")\n .action(async (options: BuildOptions) => {\n await watchTheme(resolveThemeRoot(options), options.skipThemeBuild === true);\n });\n\n theme\n .command(\"preview\")\n .description(\"Build and serve the local theme artifact files.\")\n .option(\"--theme-root <path>\", \"Theme source/artifact root.\")\n .option(\"--port <port>\", \"Preview server port.\", \"4177\")\n .action(async (options: PreviewOptions) => {\n const port = Number(options.port ?? \"4177\");\n await previewTheme(resolveThemeRoot(options), Number.isFinite(port) ? port : 4177);\n });\n\n theme\n .command(\"screenshot\")\n .description(\n \"Capture a desktop preview screenshot of the home starter page using Playwright.\",\n )\n .option(\"--theme-root <path>\", \"Theme source/artifact root.\")\n .option(\"--output <path>\", \"Output PNG path relative to theme root.\")\n .option(\"--width <px>\", \"Viewport width in pixels.\", \"1280\")\n .option(\"--height <px>\", \"Viewport height in pixels.\", \"800\")\n .option(\"--port <port>\", \"Preview server port used during capture.\", \"4178\")\n .option(\"--skip-build\", \"Skip rebuilding the theme before capturing.\")\n .action(async (options: ScreenshotOptions) => {\n await screenshotTheme(resolveThemeRoot(options), options);\n });\n\n theme\n .command(\"publish\")\n .description(\"Upload artifact to S3 and upsert ThemePackage/ThemeVersion.\")\n .option(\"--theme-root <path>\", \"Theme source/artifact root.\")\n .option(\"--skip-build\", \"Publish existing dist files without rebuilding.\")\n .action(async (options: PublishOptions) => {\n await publishTheme(resolveThemeRoot(options), options.skipBuild === true);\n });\n\n const authCmd = program.command(\"auth\").description(\"Manage Suda authentication.\");\n\n authCmd\n .command(\"login\")\n .description(\"Authenticate Suda CLI with a SudaCloud workspace.\")\n .option(\"--host <host>\", \"The SudaCloud workspace host to authenticate against.\", \"workspace.sudacloud.com\")\n .action(async (options: { host: string }) => {\n await login(options.host);\n });\n\n authCmd\n .command(\"status\")\n .description(\"Check current authentication status.\")\n .action(async () => {\n await status();\n });\n\n authCmd\n .command(\"logout\")\n .description(\"Clear local authentication configuration.\")\n .action(async () => {\n await logout();\n });\n\n program.showHelpAfterError();\n\n const argv = process.argv.filter((arg, index) => index < 2 || arg !== \"--\");\n await program.parseAsync(argv);\n}\n\nmain().catch((error) => {\n console.error(error instanceof Error ? error.message : error);\n process.exitCode = 1;\n});\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sudajs/cli",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"suda": "./bin/suda.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"development": "./src/index.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"bin"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@puckeditor/core": "^0.21.2",
|
|
27
|
+
"commander": "^12.1.0",
|
|
28
|
+
"esbuild": "^0.25.12",
|
|
29
|
+
"open": "^10.1.0",
|
|
30
|
+
"react": "^19.0.0",
|
|
31
|
+
"react-dom": "^19.0.0",
|
|
32
|
+
"@sudajs/theme-engine": "0.0.2"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^22.10.2",
|
|
36
|
+
"@types/react": "^19.0.2",
|
|
37
|
+
"@types/react-dom": "^19.0.2",
|
|
38
|
+
"eslint": "^9.17.0",
|
|
39
|
+
"typescript": "^5.7.2",
|
|
40
|
+
"@suda/eslint-config": "0.0.0",
|
|
41
|
+
"@suda/tsconfig": "0.0.0",
|
|
42
|
+
"@suda/build-config": "0.0.0"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsup",
|
|
46
|
+
"lint": "eslint .",
|
|
47
|
+
"lint:fix": "eslint . --fix",
|
|
48
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
49
|
+
}
|
|
50
|
+
}
|