@onexapis/cli 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -63
- package/dist/cli.js +1117 -114
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +1092 -112
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +978 -242
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +812 -95
- package/dist/index.mjs.map +1 -1
- package/dist/preview/preview-app.tsx +368 -0
- package/package.json +7 -2
package/dist/cli.mjs
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import chalk4 from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import * as esbuild from 'esbuild';
|
|
2
5
|
import path from 'path';
|
|
6
|
+
import fs7 from 'fs/promises';
|
|
7
|
+
import crypto from 'crypto';
|
|
8
|
+
import { glob } from 'glob';
|
|
3
9
|
import os from 'os';
|
|
4
10
|
import dotenv from 'dotenv';
|
|
5
11
|
import fs from 'fs-extra';
|
|
6
12
|
import ejs from 'ejs';
|
|
7
13
|
import { execSync, spawn } from 'child_process';
|
|
8
|
-
import chalk4 from 'chalk';
|
|
9
|
-
import ora from 'ora';
|
|
10
14
|
import { Command } from 'commander';
|
|
11
15
|
import fs2 from 'fs';
|
|
12
16
|
import inquirer from 'inquirer';
|
|
@@ -15,62 +19,651 @@ import FormData from 'form-data';
|
|
|
15
19
|
import fetch from 'node-fetch';
|
|
16
20
|
import { PutObjectCommand, GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
|
17
21
|
import AdmZip from 'adm-zip';
|
|
22
|
+
import chokidar from 'chokidar';
|
|
23
|
+
import http from 'http';
|
|
24
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
18
25
|
|
|
26
|
+
var __defProp = Object.defineProperty;
|
|
27
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
19
28
|
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
|
|
29
|
+
var __esm = (fn, res) => function __init() {
|
|
30
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
31
|
+
};
|
|
32
|
+
var __export = (target, all) => {
|
|
33
|
+
for (var name in all)
|
|
34
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
35
|
+
};
|
|
20
36
|
var __forAwait = (obj, it, method) => (it = obj[__knownSymbol("asyncIterator")]) ? it.call(obj) : (obj = obj[__knownSymbol("iterator")](), it = {}, method = (key, fn) => (fn = obj[key]) && (it[key] = (arg) => new Promise((yes, no, done) => (arg = fn.call(obj, arg), done = arg.done, Promise.resolve(arg.value).then((value) => yes({ value, done }), no)))), method("next"), method("return"), it);
|
|
21
|
-
var Logger
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
var Logger, logger;
|
|
38
|
+
var init_logger = __esm({
|
|
39
|
+
"src/utils/logger.ts"() {
|
|
40
|
+
Logger = class {
|
|
41
|
+
constructor() {
|
|
42
|
+
this.spinner = null;
|
|
43
|
+
}
|
|
44
|
+
success(message) {
|
|
45
|
+
console.log(chalk4.green("\u2713"), message);
|
|
46
|
+
}
|
|
47
|
+
error(message) {
|
|
48
|
+
console.log(chalk4.red("\u2717"), message);
|
|
49
|
+
}
|
|
50
|
+
warning(message) {
|
|
51
|
+
console.log(chalk4.yellow("\u26A0"), message);
|
|
52
|
+
}
|
|
53
|
+
info(message) {
|
|
54
|
+
console.log(chalk4.blue("\u2139"), message);
|
|
55
|
+
}
|
|
56
|
+
log(message) {
|
|
57
|
+
console.log(message);
|
|
58
|
+
}
|
|
59
|
+
startSpinner(message) {
|
|
60
|
+
this.spinner = ora(message).start();
|
|
61
|
+
}
|
|
62
|
+
stopSpinner(success = true, message) {
|
|
63
|
+
if (!this.spinner) return;
|
|
64
|
+
if (success) {
|
|
65
|
+
this.spinner.succeed(message);
|
|
66
|
+
} else {
|
|
67
|
+
this.spinner.fail(message);
|
|
68
|
+
}
|
|
69
|
+
this.spinner = null;
|
|
70
|
+
}
|
|
71
|
+
updateSpinner(message) {
|
|
72
|
+
if (this.spinner) {
|
|
73
|
+
this.spinner.text = message;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
newLine() {
|
|
77
|
+
console.log();
|
|
78
|
+
}
|
|
79
|
+
header(message) {
|
|
80
|
+
console.log();
|
|
81
|
+
console.log(chalk4.bold.cyan(message));
|
|
82
|
+
console.log(chalk4.cyan("=".repeat(message.length)));
|
|
83
|
+
console.log();
|
|
84
|
+
}
|
|
85
|
+
section(message) {
|
|
86
|
+
console.log();
|
|
87
|
+
console.log(chalk4.bold(message));
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
logger = new Logger();
|
|
30
91
|
}
|
|
31
|
-
|
|
32
|
-
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// src/utils/compile-theme.ts
|
|
95
|
+
var compile_theme_exports = {};
|
|
96
|
+
__export(compile_theme_exports, {
|
|
97
|
+
compilePreviewRuntime: () => compilePreviewRuntime,
|
|
98
|
+
compileStandaloneTheme: () => compileStandaloneTheme,
|
|
99
|
+
compileStandaloneThemeDev: () => compileStandaloneThemeDev,
|
|
100
|
+
generateManifest: () => generateManifest2
|
|
101
|
+
});
|
|
102
|
+
async function resolveNodeModulesFile(startDir, relativePath) {
|
|
103
|
+
let dir = startDir;
|
|
104
|
+
while (true) {
|
|
105
|
+
const candidate = path.join(dir, "node_modules", relativePath);
|
|
106
|
+
try {
|
|
107
|
+
await fs7.access(candidate);
|
|
108
|
+
return candidate;
|
|
109
|
+
} catch (e) {
|
|
110
|
+
const parent = path.dirname(dir);
|
|
111
|
+
if (parent === dir) break;
|
|
112
|
+
dir = parent;
|
|
113
|
+
}
|
|
33
114
|
}
|
|
34
|
-
|
|
35
|
-
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
function createCoreGlobalPlugin(themePath) {
|
|
118
|
+
const exportsBySubpath = {};
|
|
119
|
+
return {
|
|
120
|
+
name: "core-global",
|
|
121
|
+
setup(build2) {
|
|
122
|
+
build2.onResolve({ filter: /^@onexapis\/core(\/.*)?$/ }, (args) => ({
|
|
123
|
+
path: args.path,
|
|
124
|
+
namespace: "core-global"
|
|
125
|
+
}));
|
|
126
|
+
build2.onLoad({ filter: /.*/, namespace: "core-global" }, async (args) => {
|
|
127
|
+
const match = args.path.match(/^@onexapis\/core(\/(.+))?$/);
|
|
128
|
+
const subpath = (match == null ? void 0 : match[2]) || "";
|
|
129
|
+
const moduleAccess = subpath ? `['${subpath}']` : "";
|
|
130
|
+
let namedExports = [];
|
|
131
|
+
const cacheKey = subpath || "__root__";
|
|
132
|
+
if (exportsBySubpath[cacheKey]) {
|
|
133
|
+
namedExports = exportsBySubpath[cacheKey];
|
|
134
|
+
} else {
|
|
135
|
+
const distFileName = subpath ? `${subpath}.mjs` : "index.mjs";
|
|
136
|
+
const distPath = await resolveNodeModulesFile(
|
|
137
|
+
themePath,
|
|
138
|
+
path.join("@onexapis", "core", "dist", distFileName)
|
|
139
|
+
);
|
|
140
|
+
try {
|
|
141
|
+
if (!distPath) throw new Error("not found");
|
|
142
|
+
const distContent = await fs7.readFile(distPath, "utf-8");
|
|
143
|
+
const exportMatches = distContent.matchAll(/export\s*\{([^}]+)\}/g);
|
|
144
|
+
for (const m of exportMatches) {
|
|
145
|
+
const names = m[1].split(",").map((n) => {
|
|
146
|
+
const parts = n.trim().split(/\s+as\s+/);
|
|
147
|
+
return (parts[1] || parts[0]).trim();
|
|
148
|
+
}).filter((n) => n.length > 0);
|
|
149
|
+
namedExports.push(...names);
|
|
150
|
+
}
|
|
151
|
+
namedExports = [...new Set(namedExports)];
|
|
152
|
+
} catch (e) {
|
|
153
|
+
}
|
|
154
|
+
exportsBySubpath[cacheKey] = namedExports;
|
|
155
|
+
}
|
|
156
|
+
const namedExportLines = namedExports.length > 0 ? `
|
|
157
|
+
export const {
|
|
158
|
+
${namedExports.join(",\n ")}
|
|
159
|
+
} = _module;
|
|
160
|
+
` : "";
|
|
161
|
+
return {
|
|
162
|
+
contents: `
|
|
163
|
+
if (!globalThis.__ONEX_CORE__) {
|
|
164
|
+
throw new Error('[Theme Bundle] @onexapis/core not initialized. Ensure globalThis.__ONEX_CORE__ is set before loading theme.');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const _module = globalThis.__ONEX_CORE__${moduleAccess};
|
|
168
|
+
if (!_module) {
|
|
169
|
+
const subpath = ${subpath ? `'${subpath}'` : "null"};
|
|
170
|
+
const modulePath = subpath ? '/' + subpath : '';
|
|
171
|
+
const moduleKey = subpath ? '["' + subpath + '"]' : '';
|
|
172
|
+
throw new Error('[Theme Bundle] @onexapis/core' + modulePath + ' not available in globalThis.__ONEX_CORE__' + moduleKey);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export default _module;
|
|
176
|
+
${namedExportLines}
|
|
177
|
+
`.trim(),
|
|
178
|
+
loader: "js"
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async function contentHashEntry(outputDir) {
|
|
185
|
+
const entryPath = path.join(outputDir, "bundle-entry.js");
|
|
186
|
+
const mapPath = path.join(outputDir, "bundle-entry.js.map");
|
|
187
|
+
const oldFiles = await glob("bundle-entry-*.js*", { cwd: outputDir });
|
|
188
|
+
for (const f of oldFiles) {
|
|
189
|
+
await fs7.unlink(path.join(outputDir, f));
|
|
36
190
|
}
|
|
37
|
-
|
|
38
|
-
|
|
191
|
+
let entryContent;
|
|
192
|
+
try {
|
|
193
|
+
entryContent = await fs7.readFile(entryPath, "utf-8");
|
|
194
|
+
} catch (e) {
|
|
195
|
+
const indexPath = path.join(outputDir, "index.js");
|
|
196
|
+
try {
|
|
197
|
+
entryContent = await fs7.readFile(indexPath, "utf-8");
|
|
198
|
+
} catch (e2) {
|
|
199
|
+
logger.warning("No entry file found in output, skipping content hash");
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const hash2 = crypto.createHash("sha256").update(entryContent).digest("hex").slice(0, 8);
|
|
203
|
+
const hashedName2 = `bundle-entry-${hash2}.js`;
|
|
204
|
+
const indexMapPath = path.join(outputDir, "index.js.map");
|
|
205
|
+
const hashedMapName2 = `bundle-entry-${hash2}.js.map`;
|
|
206
|
+
entryContent = entryContent.replace(
|
|
207
|
+
/\/\/# sourceMappingURL=index\.js\.map/,
|
|
208
|
+
`//# sourceMappingURL=${hashedMapName2}`
|
|
209
|
+
);
|
|
210
|
+
await fs7.writeFile(path.join(outputDir, hashedName2), entryContent);
|
|
211
|
+
await fs7.unlink(indexPath);
|
|
212
|
+
try {
|
|
213
|
+
await fs7.access(indexMapPath);
|
|
214
|
+
await fs7.rename(indexMapPath, path.join(outputDir, hashedMapName2));
|
|
215
|
+
} catch (e2) {
|
|
216
|
+
}
|
|
217
|
+
logger.info(`Entry hashed: ${hashedName2}`);
|
|
218
|
+
return;
|
|
39
219
|
}
|
|
40
|
-
|
|
41
|
-
|
|
220
|
+
const hash = crypto.createHash("sha256").update(entryContent).digest("hex").slice(0, 8);
|
|
221
|
+
const hashedName = `bundle-entry-${hash}.js`;
|
|
222
|
+
const hashedMapName = `bundle-entry-${hash}.js.map`;
|
|
223
|
+
entryContent = entryContent.replace(
|
|
224
|
+
/\/\/# sourceMappingURL=bundle-entry\.js\.map/,
|
|
225
|
+
`//# sourceMappingURL=${hashedMapName}`
|
|
226
|
+
);
|
|
227
|
+
await fs7.writeFile(path.join(outputDir, hashedName), entryContent);
|
|
228
|
+
await fs7.unlink(entryPath);
|
|
229
|
+
try {
|
|
230
|
+
await fs7.access(mapPath);
|
|
231
|
+
await fs7.rename(mapPath, path.join(outputDir, hashedMapName));
|
|
232
|
+
} catch (e) {
|
|
42
233
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
234
|
+
logger.info(`Entry hashed: ${hashedName}`);
|
|
235
|
+
}
|
|
236
|
+
async function generateManifest2(themeName, themePath, outputDir) {
|
|
237
|
+
let version = "1.0.0";
|
|
238
|
+
let themeId = themeName;
|
|
239
|
+
try {
|
|
240
|
+
const pkgContent = await fs7.readFile(
|
|
241
|
+
path.join(themePath, "package.json"),
|
|
242
|
+
"utf-8"
|
|
243
|
+
);
|
|
244
|
+
const pkg = JSON.parse(pkgContent);
|
|
245
|
+
version = pkg.version || version;
|
|
246
|
+
if (pkg.name) {
|
|
247
|
+
themeId = pkg.name.replace(/^@onex-themes\//, "");
|
|
49
248
|
}
|
|
50
|
-
|
|
249
|
+
} catch (e) {
|
|
51
250
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
251
|
+
const [sectionFiles, blockFiles, schemaFiles] = await Promise.all([
|
|
252
|
+
glob("sections/**/index.ts", { cwd: themePath }),
|
|
253
|
+
glob("blocks/**/index.ts", { cwd: themePath }),
|
|
254
|
+
glob("**/*.schema.ts", { cwd: themePath })
|
|
255
|
+
]);
|
|
256
|
+
let hasThemeConfig = false;
|
|
257
|
+
try {
|
|
258
|
+
await fs7.access(path.join(themePath, "theme.config.ts"));
|
|
259
|
+
hasThemeConfig = true;
|
|
260
|
+
} catch (e) {
|
|
261
|
+
}
|
|
262
|
+
const allFiles = await glob("**/*", { cwd: outputDir, nodir: true });
|
|
263
|
+
const jsFiles = allFiles.filter((f) => f.endsWith(".js"));
|
|
264
|
+
const cssFiles = allFiles.filter((f) => f.endsWith(".css"));
|
|
265
|
+
const entryFile = jsFiles.find((f) => f.includes("bundle-entry")) || "bundle-entry.js";
|
|
266
|
+
const manifest = {
|
|
267
|
+
themeId,
|
|
268
|
+
version,
|
|
269
|
+
name: themeId.charAt(0).toUpperCase() + themeId.slice(1),
|
|
270
|
+
compiledAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
271
|
+
format: "esm",
|
|
272
|
+
platform: "browser",
|
|
273
|
+
target: "es2020",
|
|
274
|
+
counts: {
|
|
275
|
+
sections: sectionFiles.length,
|
|
276
|
+
blocks: blockFiles.length,
|
|
277
|
+
schemas: schemaFiles.length
|
|
278
|
+
},
|
|
279
|
+
output: {
|
|
280
|
+
entry: entryFile,
|
|
281
|
+
chunks: jsFiles.filter((f) => f !== entryFile && !f.endsWith(".map")),
|
|
282
|
+
assets: allFiles.filter(
|
|
283
|
+
(f) => [".png", ".jpg", ".jpeg", ".svg", ".gif", ".webp"].some(
|
|
284
|
+
(ext) => f.endsWith(ext)
|
|
285
|
+
)
|
|
286
|
+
),
|
|
287
|
+
stylesheets: cssFiles
|
|
288
|
+
},
|
|
289
|
+
external: ["react", "react-dom", "@onexapis/core"],
|
|
290
|
+
source: {
|
|
291
|
+
sections: sectionFiles,
|
|
292
|
+
blocks: blockFiles,
|
|
293
|
+
schemas: schemaFiles,
|
|
294
|
+
hasThemeConfig
|
|
55
295
|
}
|
|
296
|
+
};
|
|
297
|
+
await fs7.writeFile(
|
|
298
|
+
path.join(outputDir, "manifest.json"),
|
|
299
|
+
JSON.stringify(manifest, null, 2)
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
async function compileStandaloneTheme(themePath, themeName) {
|
|
303
|
+
const outputDir = path.join(themePath, "dist");
|
|
304
|
+
const bundleEntry = path.join(themePath, "bundle-entry.ts");
|
|
305
|
+
const indexEntry = path.join(themePath, "index.ts");
|
|
306
|
+
let entryPoint = indexEntry;
|
|
307
|
+
try {
|
|
308
|
+
await fs7.access(bundleEntry);
|
|
309
|
+
entryPoint = bundleEntry;
|
|
310
|
+
} catch (e) {
|
|
311
|
+
}
|
|
312
|
+
const shimPath = path.join(outputDir, ".process-shim.js");
|
|
313
|
+
await fs7.mkdir(outputDir, { recursive: true });
|
|
314
|
+
await fs7.writeFile(shimPath, PROCESS_SHIM);
|
|
315
|
+
const buildOptions = {
|
|
316
|
+
entryPoints: [entryPoint],
|
|
317
|
+
bundle: true,
|
|
318
|
+
platform: "browser",
|
|
319
|
+
format: "esm",
|
|
320
|
+
outdir: outputDir,
|
|
321
|
+
splitting: false,
|
|
322
|
+
chunkNames: "[name]-[hash]",
|
|
323
|
+
banner: {
|
|
324
|
+
js: '"use client";'
|
|
325
|
+
},
|
|
326
|
+
plugins: [reactGlobalPlugin, createCoreGlobalPlugin(themePath)],
|
|
327
|
+
external: [],
|
|
328
|
+
alias: {
|
|
329
|
+
events: "events/",
|
|
330
|
+
buffer: "buffer/"
|
|
331
|
+
},
|
|
332
|
+
inject: [shimPath],
|
|
333
|
+
define: {
|
|
334
|
+
"process.env.NODE_ENV": JSON.stringify("production"),
|
|
335
|
+
global: "globalThis"
|
|
336
|
+
},
|
|
337
|
+
minify: true,
|
|
338
|
+
sourcemap: true,
|
|
339
|
+
logLevel: "warning",
|
|
340
|
+
target: "es2020",
|
|
341
|
+
jsx: "automatic",
|
|
342
|
+
jsxImportSource: "react",
|
|
343
|
+
loader: {
|
|
344
|
+
".tsx": "tsx",
|
|
345
|
+
".ts": "ts",
|
|
346
|
+
".jpg": "file",
|
|
347
|
+
".jpeg": "file",
|
|
348
|
+
".png": "file",
|
|
349
|
+
".gif": "file",
|
|
350
|
+
".svg": "file",
|
|
351
|
+
".webp": "file"
|
|
352
|
+
},
|
|
353
|
+
assetNames: "assets/[name]-[hash]",
|
|
354
|
+
publicPath: "./",
|
|
355
|
+
metafile: true
|
|
356
|
+
};
|
|
357
|
+
try {
|
|
358
|
+
const result = await esbuild.build(buildOptions);
|
|
359
|
+
try {
|
|
360
|
+
await fs7.unlink(shimPath);
|
|
361
|
+
} catch (e) {
|
|
362
|
+
}
|
|
363
|
+
await contentHashEntry(outputDir);
|
|
364
|
+
await generateManifest2(themeName, themePath, outputDir);
|
|
365
|
+
if (result.metafile) {
|
|
366
|
+
const outputs = result.metafile.outputs;
|
|
367
|
+
let totalSize = 0;
|
|
368
|
+
for (const output of Object.values(outputs)) {
|
|
369
|
+
totalSize += output.bytes;
|
|
370
|
+
}
|
|
371
|
+
const totalKB = (totalSize / 1024).toFixed(2);
|
|
372
|
+
logger.info(`Bundle size: ${totalKB} KB`);
|
|
373
|
+
}
|
|
374
|
+
return true;
|
|
375
|
+
} catch (error) {
|
|
376
|
+
try {
|
|
377
|
+
await fs7.unlink(shimPath);
|
|
378
|
+
} catch (e) {
|
|
379
|
+
}
|
|
380
|
+
logger.error(`esbuild compilation failed: ${error}`);
|
|
381
|
+
return false;
|
|
56
382
|
}
|
|
57
|
-
|
|
58
|
-
|
|
383
|
+
}
|
|
384
|
+
async function compileStandaloneThemeDev(themePath, themeName) {
|
|
385
|
+
const outputDir = path.join(themePath, "dist");
|
|
386
|
+
const bundleEntry = path.join(themePath, "bundle-entry.ts");
|
|
387
|
+
const indexEntry = path.join(themePath, "index.ts");
|
|
388
|
+
let entryPoint = indexEntry;
|
|
389
|
+
try {
|
|
390
|
+
await fs7.access(bundleEntry);
|
|
391
|
+
entryPoint = bundleEntry;
|
|
392
|
+
} catch (e) {
|
|
393
|
+
}
|
|
394
|
+
const shimPath = path.join(outputDir, ".process-shim.js");
|
|
395
|
+
await fs7.mkdir(outputDir, { recursive: true });
|
|
396
|
+
await fs7.writeFile(shimPath, PROCESS_SHIM);
|
|
397
|
+
const buildOptions = {
|
|
398
|
+
entryPoints: [entryPoint],
|
|
399
|
+
bundle: true,
|
|
400
|
+
platform: "browser",
|
|
401
|
+
format: "esm",
|
|
402
|
+
outdir: outputDir,
|
|
403
|
+
splitting: false,
|
|
404
|
+
banner: {
|
|
405
|
+
js: '"use client";'
|
|
406
|
+
},
|
|
407
|
+
plugins: [reactGlobalPlugin, createCoreGlobalPlugin(themePath)],
|
|
408
|
+
external: [],
|
|
409
|
+
alias: {
|
|
410
|
+
events: "events/",
|
|
411
|
+
buffer: "buffer/"
|
|
412
|
+
},
|
|
413
|
+
inject: [shimPath],
|
|
414
|
+
define: {
|
|
415
|
+
"process.env.NODE_ENV": JSON.stringify("development"),
|
|
416
|
+
global: "globalThis"
|
|
417
|
+
},
|
|
418
|
+
minify: false,
|
|
419
|
+
sourcemap: true,
|
|
420
|
+
logLevel: "warning",
|
|
421
|
+
target: "es2020",
|
|
422
|
+
jsx: "automatic",
|
|
423
|
+
jsxImportSource: "react",
|
|
424
|
+
loader: {
|
|
425
|
+
".tsx": "tsx",
|
|
426
|
+
".ts": "ts",
|
|
427
|
+
".jpg": "file",
|
|
428
|
+
".jpeg": "file",
|
|
429
|
+
".png": "file",
|
|
430
|
+
".gif": "file",
|
|
431
|
+
".svg": "file",
|
|
432
|
+
".webp": "file"
|
|
433
|
+
},
|
|
434
|
+
assetNames: "assets/[name]-[hash]",
|
|
435
|
+
publicPath: "./",
|
|
436
|
+
metafile: true
|
|
437
|
+
};
|
|
438
|
+
const context2 = await esbuild.context(buildOptions);
|
|
439
|
+
await context2.rebuild();
|
|
440
|
+
await generateManifest2(themeName, themePath, outputDir);
|
|
441
|
+
return { context: context2, outputDir };
|
|
442
|
+
}
|
|
443
|
+
async function compilePreviewRuntime(themePath) {
|
|
444
|
+
const outputDir = path.join(themePath, "dist");
|
|
445
|
+
await fs7.mkdir(outputDir, { recursive: true });
|
|
446
|
+
const outputPath = path.join(outputDir, "preview-runtime.js");
|
|
447
|
+
const locations = [
|
|
448
|
+
path.join(__dirname, "..", "preview", "preview-app.tsx"),
|
|
449
|
+
path.join(__dirname, "preview", "preview-app.tsx"),
|
|
450
|
+
path.join(__dirname, "..", "..", "src", "preview", "preview-app.tsx")
|
|
451
|
+
];
|
|
452
|
+
let previewEntryPath = null;
|
|
453
|
+
for (const loc of locations) {
|
|
454
|
+
try {
|
|
455
|
+
await fs7.access(loc);
|
|
456
|
+
previewEntryPath = loc;
|
|
457
|
+
break;
|
|
458
|
+
} catch (e) {
|
|
459
|
+
}
|
|
59
460
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
461
|
+
if (!previewEntryPath) {
|
|
462
|
+
throw new Error(
|
|
463
|
+
`Preview app source not found. Searched:
|
|
464
|
+
${locations.join("\n")}`
|
|
465
|
+
);
|
|
65
466
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
467
|
+
const serverStubPlugin = {
|
|
468
|
+
name: "server-stub",
|
|
469
|
+
setup(build2) {
|
|
470
|
+
build2.onResolve({ filter: /^server-only$/ }, () => ({
|
|
471
|
+
path: "server-only",
|
|
472
|
+
namespace: "server-stub"
|
|
473
|
+
}));
|
|
474
|
+
build2.onLoad({ filter: /.*/, namespace: "server-stub" }, () => ({
|
|
475
|
+
contents: "// server-only stub for browser",
|
|
476
|
+
loader: "js"
|
|
477
|
+
}));
|
|
478
|
+
const nodeBuiltins = [
|
|
479
|
+
"fs",
|
|
480
|
+
"fs/promises",
|
|
481
|
+
"path",
|
|
482
|
+
"os",
|
|
483
|
+
"crypto",
|
|
484
|
+
"stream",
|
|
485
|
+
"url",
|
|
486
|
+
"http",
|
|
487
|
+
"https",
|
|
488
|
+
"net",
|
|
489
|
+
"tls",
|
|
490
|
+
"child_process",
|
|
491
|
+
"util",
|
|
492
|
+
"events",
|
|
493
|
+
"buffer",
|
|
494
|
+
"querystring",
|
|
495
|
+
"zlib"
|
|
496
|
+
];
|
|
497
|
+
for (const mod of nodeBuiltins) {
|
|
498
|
+
build2.onResolve({ filter: new RegExp(`^${mod.replace("/", "\\/")}$`) }, () => ({
|
|
499
|
+
path: mod,
|
|
500
|
+
namespace: "node-stub"
|
|
501
|
+
}));
|
|
502
|
+
}
|
|
503
|
+
build2.onLoad({ filter: /.*/, namespace: "node-stub" }, (args) => {
|
|
504
|
+
const stubs = {
|
|
505
|
+
events: "export class EventEmitter { on(){return this} off(){return this} emit(){return false} addListener(){return this} removeListener(){return this} } export default { EventEmitter };",
|
|
506
|
+
path: "export function join(){return ''} export function resolve(){return ''} export function dirname(){return ''} export function basename(){return ''} export function extname(){return ''} export default {};",
|
|
507
|
+
fs: "export const promises = {}; export function readFileSync(){return ''} export function existsSync(){return false} export default {};"
|
|
508
|
+
};
|
|
509
|
+
return {
|
|
510
|
+
contents: stubs[args.path] || "export default {};",
|
|
511
|
+
loader: "js"
|
|
512
|
+
};
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
await esbuild.build({
|
|
517
|
+
entryPoints: [previewEntryPath],
|
|
518
|
+
bundle: true,
|
|
519
|
+
platform: "browser",
|
|
520
|
+
format: "esm",
|
|
521
|
+
outfile: outputPath,
|
|
522
|
+
// Bundle React + core INTO the output (NOT externalized)
|
|
523
|
+
external: [],
|
|
524
|
+
plugins: [serverStubPlugin],
|
|
525
|
+
minify: false,
|
|
526
|
+
sourcemap: true,
|
|
527
|
+
target: "es2020",
|
|
528
|
+
jsx: "automatic",
|
|
529
|
+
jsxImportSource: "react",
|
|
530
|
+
define: {
|
|
531
|
+
"process.env.NODE_ENV": JSON.stringify("development"),
|
|
532
|
+
global: "globalThis"
|
|
533
|
+
},
|
|
534
|
+
loader: { ".tsx": "tsx", ".ts": "ts" },
|
|
535
|
+
// Force CJS resolution to avoid sideEffects:false dropping ESM chunk imports
|
|
536
|
+
conditions: ["require", "default"],
|
|
537
|
+
mainFields: ["main"],
|
|
538
|
+
logOverride: {
|
|
539
|
+
"ignored-bare-import": "silent"
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
return outputPath;
|
|
543
|
+
}
|
|
544
|
+
var PROCESS_SHIM, reactGlobalPlugin;
|
|
545
|
+
var init_compile_theme = __esm({
|
|
546
|
+
"src/utils/compile-theme.ts"() {
|
|
547
|
+
init_logger();
|
|
548
|
+
PROCESS_SHIM = `
|
|
549
|
+
if (typeof process === "undefined") {
|
|
550
|
+
globalThis.process = {
|
|
551
|
+
env: {},
|
|
552
|
+
browser: true,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
`;
|
|
556
|
+
reactGlobalPlugin = {
|
|
557
|
+
name: "react-global",
|
|
558
|
+
setup(build2) {
|
|
559
|
+
build2.onResolve({ filter: /^react$/ }, () => ({
|
|
560
|
+
path: "react-external",
|
|
561
|
+
namespace: "react-global"
|
|
562
|
+
}));
|
|
563
|
+
build2.onResolve({ filter: /^react-dom$/ }, () => ({
|
|
564
|
+
path: "react-dom-external",
|
|
565
|
+
namespace: "react-global"
|
|
566
|
+
}));
|
|
567
|
+
build2.onResolve({ filter: /^react\/jsx-runtime$/ }, () => ({
|
|
568
|
+
path: "react-jsx-runtime-external",
|
|
569
|
+
namespace: "react-global"
|
|
570
|
+
}));
|
|
571
|
+
build2.onLoad({ filter: /.*/, namespace: "react-global" }, (args) => {
|
|
572
|
+
if (args.path === "react-external") {
|
|
573
|
+
return {
|
|
574
|
+
contents: `
|
|
575
|
+
if (!globalThis.__ONEX_REACT__) {
|
|
576
|
+
throw new Error('[Theme Bundle] React not initialized. Ensure globalThis.__ONEX_REACT__ is set before loading theme.');
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const React = globalThis.__ONEX_REACT__;
|
|
580
|
+
export default React;
|
|
581
|
+
|
|
582
|
+
export const {
|
|
583
|
+
useState,
|
|
584
|
+
useEffect,
|
|
585
|
+
useContext,
|
|
586
|
+
useReducer,
|
|
587
|
+
useCallback,
|
|
588
|
+
useMemo,
|
|
589
|
+
useRef,
|
|
590
|
+
useImperativeHandle,
|
|
591
|
+
useLayoutEffect,
|
|
592
|
+
useDebugValue,
|
|
593
|
+
useDeferredValue,
|
|
594
|
+
useTransition,
|
|
595
|
+
useId,
|
|
596
|
+
useSyncExternalStore,
|
|
597
|
+
useInsertionEffect,
|
|
598
|
+
createContext,
|
|
599
|
+
forwardRef,
|
|
600
|
+
lazy,
|
|
601
|
+
memo,
|
|
602
|
+
startTransition,
|
|
603
|
+
createElement,
|
|
604
|
+
cloneElement,
|
|
605
|
+
isValidElement,
|
|
606
|
+
Children,
|
|
607
|
+
Fragment,
|
|
608
|
+
Profiler,
|
|
609
|
+
StrictMode,
|
|
610
|
+
Suspense,
|
|
611
|
+
Component,
|
|
612
|
+
PureComponent,
|
|
613
|
+
useActionState,
|
|
614
|
+
use,
|
|
615
|
+
} = React;
|
|
616
|
+
`.trim(),
|
|
617
|
+
loader: "js"
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
if (args.path === "react-dom-external") {
|
|
621
|
+
return {
|
|
622
|
+
contents: `
|
|
623
|
+
if (!globalThis.__ONEX_REACT_DOM__) {
|
|
624
|
+
throw new Error('[Theme Bundle] ReactDOM not initialized. Ensure globalThis.__ONEX_REACT_DOM__ is set before loading theme.');
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const ReactDOM = globalThis.__ONEX_REACT_DOM__;
|
|
628
|
+
export default ReactDOM;
|
|
629
|
+
|
|
630
|
+
export const {
|
|
631
|
+
createRoot,
|
|
632
|
+
hydrateRoot,
|
|
633
|
+
flushSync,
|
|
634
|
+
createPortal,
|
|
635
|
+
findDOMNode,
|
|
636
|
+
render,
|
|
637
|
+
hydrate,
|
|
638
|
+
unmountComponentAtNode,
|
|
639
|
+
} = ReactDOM;
|
|
640
|
+
`.trim(),
|
|
641
|
+
loader: "js"
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
if (args.path === "react-jsx-runtime-external") {
|
|
645
|
+
return {
|
|
646
|
+
contents: `
|
|
647
|
+
if (!globalThis.__ONEX_JSX_RUNTIME__) {
|
|
648
|
+
throw new Error('[Theme Bundle] React JSX runtime not initialized. Ensure globalThis.__ONEX_JSX_RUNTIME__ is set before loading theme.');
|
|
649
|
+
}
|
|
650
|
+
const _jsxRuntime = globalThis.__ONEX_JSX_RUNTIME__;
|
|
651
|
+
export const jsx = _jsxRuntime.jsx;
|
|
652
|
+
export const jsxs = _jsxRuntime.jsxs;
|
|
653
|
+
export const Fragment = _jsxRuntime.Fragment;
|
|
654
|
+
`.trim(),
|
|
655
|
+
loader: "js"
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
return null;
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
};
|
|
69
662
|
}
|
|
70
|
-
};
|
|
71
|
-
var logger = new Logger();
|
|
663
|
+
});
|
|
72
664
|
|
|
73
665
|
// src/utils/file-helpers.ts
|
|
666
|
+
init_logger();
|
|
74
667
|
async function renderTemplate(templatePath, data) {
|
|
75
668
|
const template = await fs.readFile(templatePath, "utf-8");
|
|
76
669
|
return ejs.render(template, data);
|
|
@@ -156,18 +749,18 @@ function getProjectRoot() {
|
|
|
156
749
|
}
|
|
157
750
|
function getThemesDir() {
|
|
158
751
|
const root = getProjectRoot();
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return path.
|
|
752
|
+
if (fs.existsSync(path.join(root, "themes")))
|
|
753
|
+
return path.join(root, "themes");
|
|
754
|
+
if (fs.existsSync(path.join(root, "src/themes")))
|
|
755
|
+
return path.join(root, "src/themes");
|
|
756
|
+
return path.dirname(root);
|
|
164
757
|
}
|
|
165
758
|
function getFeaturesDir() {
|
|
166
759
|
return path.join(getProjectRoot(), "src/features");
|
|
167
760
|
}
|
|
168
761
|
function isOneXProject() {
|
|
169
762
|
const root = getProjectRoot();
|
|
170
|
-
return fs.existsSync(path.join(root, "themes")) || fs.existsSync(path.join(root, "src/themes"));
|
|
763
|
+
return fs.existsSync(path.join(root, "themes")) || fs.existsSync(path.join(root, "src/themes")) || fs.existsSync(path.join(root, "theme.config.ts")) || fs.existsSync(path.join(root, "bundle-entry.ts"));
|
|
171
764
|
}
|
|
172
765
|
function ensureOneXProject() {
|
|
173
766
|
if (!isOneXProject()) {
|
|
@@ -217,6 +810,9 @@ async function installDependencies(projectPath, packageManager = "npm") {
|
|
|
217
810
|
});
|
|
218
811
|
}
|
|
219
812
|
|
|
813
|
+
// src/commands/init.ts
|
|
814
|
+
init_logger();
|
|
815
|
+
|
|
220
816
|
// src/utils/validators.ts
|
|
221
817
|
function validateName(name) {
|
|
222
818
|
return /^[a-z0-9]+(-[a-z0-9]+)*$/.test(name);
|
|
@@ -658,9 +1254,20 @@ export const homePageConfig: PageConfig = {
|
|
|
658
1254
|
};
|
|
659
1255
|
`;
|
|
660
1256
|
}
|
|
1257
|
+
|
|
1258
|
+
// src/commands/create-section.ts
|
|
1259
|
+
init_logger();
|
|
661
1260
|
async function createSectionCommand(name, options) {
|
|
662
1261
|
logger.header("Create New Section");
|
|
663
1262
|
ensureOneXProject();
|
|
1263
|
+
if (!options.theme) {
|
|
1264
|
+
const isStandaloneTheme = ["theme.config.ts", "bundle-entry.ts"].some(
|
|
1265
|
+
(f) => fs.existsSync(path.join(process.cwd(), f))
|
|
1266
|
+
);
|
|
1267
|
+
if (isStandaloneTheme) {
|
|
1268
|
+
options.theme = path.basename(process.cwd());
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
664
1271
|
const sectionName = toKebabCase(name);
|
|
665
1272
|
if (!validateName(sectionName)) {
|
|
666
1273
|
logger.error(
|
|
@@ -889,9 +1496,20 @@ export { ${data.sectionName}Schema } from "./${data.sectionName}.schema";
|
|
|
889
1496
|
${hasTemplate ? `export { ${data.sectionNamePascal}Default } from "./${data.sectionName}-default";` : ""}
|
|
890
1497
|
`;
|
|
891
1498
|
}
|
|
1499
|
+
|
|
1500
|
+
// src/commands/create-block.ts
|
|
1501
|
+
init_logger();
|
|
892
1502
|
async function createBlockCommand(name, options) {
|
|
893
1503
|
logger.header("Create New Block");
|
|
894
1504
|
ensureOneXProject();
|
|
1505
|
+
if (!options.theme) {
|
|
1506
|
+
const isStandaloneTheme = ["theme.config.ts", "bundle-entry.ts"].some(
|
|
1507
|
+
(f) => fs.existsSync(path.join(process.cwd(), f))
|
|
1508
|
+
);
|
|
1509
|
+
if (isStandaloneTheme) {
|
|
1510
|
+
options.theme = path.basename(process.cwd());
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
895
1513
|
const blockName = toKebabCase(name);
|
|
896
1514
|
if (!validateName(blockName)) {
|
|
897
1515
|
logger.error(
|
|
@@ -1105,6 +1723,9 @@ export { ${data.blockName}Definition } from "./${data.blockName}.schema";
|
|
|
1105
1723
|
export { ${data.blockNamePascal} } from "./${data.blockName}";
|
|
1106
1724
|
`;
|
|
1107
1725
|
}
|
|
1726
|
+
|
|
1727
|
+
// src/commands/create-component.ts
|
|
1728
|
+
init_logger();
|
|
1108
1729
|
async function createComponentCommand(name, options) {
|
|
1109
1730
|
logger.header("Create New Component");
|
|
1110
1731
|
ensureOneXProject();
|
|
@@ -1309,6 +1930,9 @@ export { ${data.componentName}Definition } from "./${data.componentName}.schema"
|
|
|
1309
1930
|
export { ${data.componentNamePascal} } from "./${data.componentName}";
|
|
1310
1931
|
`;
|
|
1311
1932
|
}
|
|
1933
|
+
|
|
1934
|
+
// src/commands/list.ts
|
|
1935
|
+
init_logger();
|
|
1312
1936
|
async function listCommand(options) {
|
|
1313
1937
|
logger.header("OneX Project Inventory");
|
|
1314
1938
|
ensureOneXProject();
|
|
@@ -1440,6 +2064,9 @@ async function listThemesInfo() {
|
|
|
1440
2064
|
}
|
|
1441
2065
|
logger.newLine();
|
|
1442
2066
|
}
|
|
2067
|
+
|
|
2068
|
+
// src/commands/validate.ts
|
|
2069
|
+
init_logger();
|
|
1443
2070
|
async function validateCommand(options) {
|
|
1444
2071
|
logger.header("Validate Theme");
|
|
1445
2072
|
ensureOneXProject();
|
|
@@ -1452,8 +2079,12 @@ async function validateCommand(options) {
|
|
|
1452
2079
|
}
|
|
1453
2080
|
themeToValidate = options.theme;
|
|
1454
2081
|
} else {
|
|
1455
|
-
const
|
|
1456
|
-
|
|
2082
|
+
const isThemeDir = [
|
|
2083
|
+
"theme.config.ts",
|
|
2084
|
+
"bundle-entry.ts",
|
|
2085
|
+
"manifest.ts"
|
|
2086
|
+
].some((f) => fs.existsSync(path.join(process.cwd(), f)));
|
|
2087
|
+
if (isThemeDir) {
|
|
1457
2088
|
themeToValidate = path.basename(process.cwd());
|
|
1458
2089
|
logger.info(`Validating current theme: ${themeToValidate}`);
|
|
1459
2090
|
} else {
|
|
@@ -1465,15 +2096,21 @@ async function validateCommand(options) {
|
|
|
1465
2096
|
}
|
|
1466
2097
|
const themePath = path.join(getThemesDir(), themeToValidate);
|
|
1467
2098
|
logger.startSpinner("Running validation checks...");
|
|
1468
|
-
const
|
|
1469
|
-
|
|
2099
|
+
const entryFiles = ["manifest.ts", "theme.config.ts", "bundle-entry.ts"];
|
|
2100
|
+
const foundEntry = entryFiles.find(
|
|
2101
|
+
(f) => fs.existsSync(path.join(themePath, f))
|
|
2102
|
+
);
|
|
2103
|
+
if (!foundEntry) {
|
|
1470
2104
|
issues.push({
|
|
1471
2105
|
type: "error",
|
|
1472
|
-
file: "manifest.ts",
|
|
1473
|
-
message: "
|
|
2106
|
+
file: "manifest.ts / theme.config.ts / bundle-entry.ts",
|
|
2107
|
+
message: "No theme entry file found (need at least one of: manifest.ts, theme.config.ts, bundle-entry.ts)"
|
|
1474
2108
|
});
|
|
1475
|
-
} else {
|
|
1476
|
-
const manifestContent = fs.readFileSync(
|
|
2109
|
+
} else if (foundEntry === "manifest.ts") {
|
|
2110
|
+
const manifestContent = fs.readFileSync(
|
|
2111
|
+
path.join(themePath, foundEntry),
|
|
2112
|
+
"utf-8"
|
|
2113
|
+
);
|
|
1477
2114
|
if (!manifestContent.includes("export const") && !manifestContent.includes("export default") && !manifestContent.includes("export interface")) {
|
|
1478
2115
|
issues.push({
|
|
1479
2116
|
type: "error",
|
|
@@ -1609,7 +2246,11 @@ async function validateCommand(options) {
|
|
|
1609
2246
|
}
|
|
1610
2247
|
}
|
|
1611
2248
|
}
|
|
2249
|
+
|
|
2250
|
+
// src/commands/build.ts
|
|
2251
|
+
init_logger();
|
|
1612
2252
|
async function buildCommand(options) {
|
|
2253
|
+
var _a;
|
|
1613
2254
|
logger.header("Build Theme");
|
|
1614
2255
|
let themePath;
|
|
1615
2256
|
let themeName;
|
|
@@ -1630,8 +2271,12 @@ async function buildCommand(options) {
|
|
|
1630
2271
|
process.exit(1);
|
|
1631
2272
|
}
|
|
1632
2273
|
} else {
|
|
1633
|
-
const
|
|
1634
|
-
|
|
2274
|
+
const isThemeDir = [
|
|
2275
|
+
"theme.config.ts",
|
|
2276
|
+
"bundle-entry.ts",
|
|
2277
|
+
"manifest.ts"
|
|
2278
|
+
].some((f) => fs.existsSync(path.join(process.cwd(), f)));
|
|
2279
|
+
if (isThemeDir) {
|
|
1635
2280
|
themePath = process.cwd();
|
|
1636
2281
|
themeName = path.basename(themePath);
|
|
1637
2282
|
logger.info(`Building current theme: ${themeName}`);
|
|
@@ -1674,11 +2319,20 @@ async function buildCommand(options) {
|
|
|
1674
2319
|
process.exit(1);
|
|
1675
2320
|
}
|
|
1676
2321
|
logger.stopSpinner(true, "Lint passed");
|
|
1677
|
-
const
|
|
2322
|
+
const pkgJson = fs.readJsonSync(packageJsonPath);
|
|
2323
|
+
const buildScript = ((_a = pkgJson.scripts) == null ? void 0 : _a.build) || "";
|
|
2324
|
+
const isRecursive = buildScript.includes("onex build") || buildScript.includes("onex-cli build");
|
|
1678
2325
|
logger.startSpinner(
|
|
1679
2326
|
options.watch ? "Building (watch mode)..." : "Building..."
|
|
1680
2327
|
);
|
|
1681
|
-
|
|
2328
|
+
let buildSuccess;
|
|
2329
|
+
if (isRecursive) {
|
|
2330
|
+
const { compileStandaloneTheme: compileStandaloneTheme2 } = await Promise.resolve().then(() => (init_compile_theme(), compile_theme_exports));
|
|
2331
|
+
buildSuccess = await compileStandaloneTheme2(themePath, themeName);
|
|
2332
|
+
} else {
|
|
2333
|
+
const buildArgs = options.watch ? ["build", "--watch"] : ["build"];
|
|
2334
|
+
buildSuccess = await runCommand("pnpm", buildArgs, themePath);
|
|
2335
|
+
}
|
|
1682
2336
|
if (!buildSuccess && !options.watch) {
|
|
1683
2337
|
logger.stopSpinner(false, "Build failed");
|
|
1684
2338
|
process.exit(1);
|
|
@@ -1719,6 +2373,9 @@ function runCommand(command, args, cwd) {
|
|
|
1719
2373
|
});
|
|
1720
2374
|
});
|
|
1721
2375
|
}
|
|
2376
|
+
|
|
2377
|
+
// src/commands/package.ts
|
|
2378
|
+
init_logger();
|
|
1722
2379
|
async function packageCommand(options) {
|
|
1723
2380
|
logger.header("Package Theme");
|
|
1724
2381
|
ensureOneXProject();
|
|
@@ -1732,8 +2389,12 @@ async function packageCommand(options) {
|
|
|
1732
2389
|
process.exit(1);
|
|
1733
2390
|
}
|
|
1734
2391
|
} else {
|
|
1735
|
-
const
|
|
1736
|
-
|
|
2392
|
+
const isThemeDir = [
|
|
2393
|
+
"theme.config.ts",
|
|
2394
|
+
"bundle-entry.ts",
|
|
2395
|
+
"manifest.ts"
|
|
2396
|
+
].some((f) => fs.existsSync(path.join(process.cwd(), f)));
|
|
2397
|
+
if (isThemeDir) {
|
|
1737
2398
|
themePath = process.cwd();
|
|
1738
2399
|
themeName = path.basename(themePath);
|
|
1739
2400
|
logger.info(`Packaging current theme: ${themeName}`);
|
|
@@ -1756,10 +2417,9 @@ async function packageCommand(options) {
|
|
|
1756
2417
|
logger.newLine();
|
|
1757
2418
|
const compiledThemePath = path.join(
|
|
1758
2419
|
process.cwd(),
|
|
1759
|
-
"
|
|
1760
|
-
|
|
1761
|
-
"
|
|
1762
|
-
`${themeName}@${version}`
|
|
2420
|
+
"themes",
|
|
2421
|
+
themeName,
|
|
2422
|
+
"dist"
|
|
1763
2423
|
);
|
|
1764
2424
|
if (!options.skipBuild) {
|
|
1765
2425
|
logger.section("Step 1: Compile Theme");
|
|
@@ -1857,6 +2517,9 @@ async function createZipArchive(compiledThemePath, outputPath) {
|
|
|
1857
2517
|
archive.finalize();
|
|
1858
2518
|
});
|
|
1859
2519
|
}
|
|
2520
|
+
|
|
2521
|
+
// src/commands/deploy.ts
|
|
2522
|
+
init_logger();
|
|
1860
2523
|
async function deployCommand(options) {
|
|
1861
2524
|
logger.header("Deploy Theme");
|
|
1862
2525
|
ensureOneXProject();
|
|
@@ -1959,6 +2622,9 @@ async function deployCommand(options) {
|
|
|
1959
2622
|
process.exit(1);
|
|
1960
2623
|
}
|
|
1961
2624
|
}
|
|
2625
|
+
|
|
2626
|
+
// src/commands/upload.ts
|
|
2627
|
+
init_logger();
|
|
1962
2628
|
function getS3Client() {
|
|
1963
2629
|
const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
|
|
1964
2630
|
if (adapterMode === "vps") {
|
|
@@ -1998,21 +2664,12 @@ function getBucketName(env) {
|
|
|
1998
2664
|
return environment === "production" ? "onex-themes-prod" : "onex-themes-staging";
|
|
1999
2665
|
}
|
|
2000
2666
|
async function findCompiledThemeDir(themeId, version) {
|
|
2001
|
-
const searchPaths = [
|
|
2002
|
-
path.resolve(process.cwd(), "dist"),
|
|
2003
|
-
path.resolve(
|
|
2004
|
-
process.cwd(),
|
|
2005
|
-
`../../apps/api-server/compiled-themes/${themeId}@${version}`
|
|
2006
|
-
),
|
|
2007
|
-
path.resolve(
|
|
2008
|
-
process.cwd(),
|
|
2009
|
-
`../api-server/compiled-themes/${themeId}@${version}`
|
|
2010
|
-
)
|
|
2011
|
-
];
|
|
2667
|
+
const searchPaths = [path.resolve(process.cwd(), "dist")];
|
|
2012
2668
|
for (const dir of searchPaths) {
|
|
2013
2669
|
if (await fs.pathExists(dir)) {
|
|
2014
|
-
const
|
|
2015
|
-
|
|
2670
|
+
const hasManifest = await fs.pathExists(path.join(dir, "manifest.json"));
|
|
2671
|
+
const hasThemeEntry = await fs.pathExists(path.join(dir, "bundle-entry.js")) || await fs.pathExists(path.join(dir, "theme.config.js")) || await fs.pathExists(path.join(dir, "index.js"));
|
|
2672
|
+
if (hasManifest || hasThemeEntry) {
|
|
2016
2673
|
return dir;
|
|
2017
2674
|
}
|
|
2018
2675
|
}
|
|
@@ -2115,13 +2772,8 @@ async function uploadCommand(options) {
|
|
|
2115
2772
|
`Compiled theme not found for ${themeId}@${version}. Run 'onex build' first.`
|
|
2116
2773
|
)
|
|
2117
2774
|
);
|
|
2118
|
-
logger.info(
|
|
2119
|
-
|
|
2120
|
-
`Expected locations:
|
|
2121
|
-
- ./dist/
|
|
2122
|
-
- ../../apps/api-server/compiled-themes/${themeId}@${version}/`
|
|
2123
|
-
)
|
|
2124
|
-
);
|
|
2775
|
+
logger.info(chalk4.gray(`Expected location:
|
|
2776
|
+
- ./dist/`));
|
|
2125
2777
|
process.exit(1);
|
|
2126
2778
|
}
|
|
2127
2779
|
spinner.succeed(`Found compiled theme at: ${compiledDir}`);
|
|
@@ -2228,9 +2880,7 @@ async function uploadCommand(options) {
|
|
|
2228
2880
|
);
|
|
2229
2881
|
console.log(chalk4.cyan(" Bucket: ") + chalk4.white(bucket));
|
|
2230
2882
|
console.log(
|
|
2231
|
-
chalk4.cyan(" Files: ") + chalk4.white(
|
|
2232
|
-
`bundle.zip${sourceUploaded ? " + source.zip" : ""}`
|
|
2233
|
-
)
|
|
2883
|
+
chalk4.cyan(" Files: ") + chalk4.white(`bundle.zip${sourceUploaded ? " + source.zip" : ""}`)
|
|
2234
2884
|
);
|
|
2235
2885
|
console.log(
|
|
2236
2886
|
chalk4.cyan(" Path: ") + chalk4.gray(`s3://${bucket}/themes/${themeId}/${version}/`)
|
|
@@ -2242,6 +2892,9 @@ async function uploadCommand(options) {
|
|
|
2242
2892
|
process.exit(1);
|
|
2243
2893
|
}
|
|
2244
2894
|
}
|
|
2895
|
+
|
|
2896
|
+
// src/commands/download.ts
|
|
2897
|
+
init_logger();
|
|
2245
2898
|
function getS3Client2() {
|
|
2246
2899
|
const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
|
|
2247
2900
|
if (adapterMode === "vps") {
|
|
@@ -2389,11 +3042,7 @@ function showDownloadFailureHelp(themeId, bucket) {
|
|
|
2389
3042
|
);
|
|
2390
3043
|
console.log();
|
|
2391
3044
|
console.log(chalk4.white("4. Verify theme exists in S3:"));
|
|
2392
|
-
console.log(
|
|
2393
|
-
chalk4.gray(
|
|
2394
|
-
` aws s3 ls s3://${bucket}/themes/${themeId}/`
|
|
2395
|
-
)
|
|
2396
|
-
);
|
|
3045
|
+
console.log(chalk4.gray(` aws s3 ls s3://${bucket}/themes/${themeId}/`));
|
|
2397
3046
|
console.log();
|
|
2398
3047
|
}
|
|
2399
3048
|
async function downloadCommand(options) {
|
|
@@ -2469,6 +3118,9 @@ async function downloadCommand(options) {
|
|
|
2469
3118
|
process.exit(1);
|
|
2470
3119
|
}
|
|
2471
3120
|
}
|
|
3121
|
+
|
|
3122
|
+
// src/commands/clone.ts
|
|
3123
|
+
init_logger();
|
|
2472
3124
|
function getS3Client3() {
|
|
2473
3125
|
const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
|
|
2474
3126
|
if (adapterMode === "vps") {
|
|
@@ -2572,17 +3224,100 @@ function runInstall(cwd) {
|
|
|
2572
3224
|
proc.on("error", () => resolve(false));
|
|
2573
3225
|
});
|
|
2574
3226
|
}
|
|
3227
|
+
async function promptThemeName(originalName) {
|
|
3228
|
+
const { default: inquirer5 } = await import('inquirer');
|
|
3229
|
+
const { themeName } = await inquirer5.prompt([
|
|
3230
|
+
{
|
|
3231
|
+
type: "input",
|
|
3232
|
+
name: "themeName",
|
|
3233
|
+
message: "New theme name (kebab-case):",
|
|
3234
|
+
default: `my-${originalName}`,
|
|
3235
|
+
validate: (input) => {
|
|
3236
|
+
if (!/^[a-z][a-z0-9-]*$/.test(input)) {
|
|
3237
|
+
return "Theme name must be kebab-case (lowercase letters, numbers, hyphens)";
|
|
3238
|
+
}
|
|
3239
|
+
if (input === originalName) {
|
|
3240
|
+
return `Name must differ from the original theme "${originalName}"`;
|
|
3241
|
+
}
|
|
3242
|
+
return true;
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
]);
|
|
3246
|
+
return themeName;
|
|
3247
|
+
}
|
|
3248
|
+
async function renameTheme(themeDir, oldName, newName) {
|
|
3249
|
+
const oldPrefix = `${oldName}-`;
|
|
3250
|
+
const newPrefix = `${newName}-`;
|
|
3251
|
+
const newDisplayName = newName.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
3252
|
+
const pkgPath = path.join(themeDir, "package.json");
|
|
3253
|
+
if (await fs.pathExists(pkgPath)) {
|
|
3254
|
+
const pkg = await fs.readJson(pkgPath);
|
|
3255
|
+
pkg.name = `@onex-themes/${newName}`;
|
|
3256
|
+
if (pkg.description) {
|
|
3257
|
+
pkg.description = pkg.description.replace(
|
|
3258
|
+
new RegExp(oldName, "gi"),
|
|
3259
|
+
newDisplayName
|
|
3260
|
+
);
|
|
3261
|
+
}
|
|
3262
|
+
pkg.version = "1.0.0";
|
|
3263
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
3264
|
+
}
|
|
3265
|
+
const configPath = path.join(themeDir, "theme.config.ts");
|
|
3266
|
+
if (await fs.pathExists(configPath)) {
|
|
3267
|
+
let content = await fs.readFile(configPath, "utf-8");
|
|
3268
|
+
content = content.replace(/id:\s*"[^"]*"/, `id: "${newName}"`);
|
|
3269
|
+
content = content.replace(
|
|
3270
|
+
/name:\s*"[^"]*Theme"/,
|
|
3271
|
+
`name: "${newDisplayName} Theme"`
|
|
3272
|
+
);
|
|
3273
|
+
await fs.writeFile(configPath, content);
|
|
3274
|
+
}
|
|
3275
|
+
const layoutPath = path.join(themeDir, "theme.layout.ts");
|
|
3276
|
+
if (await fs.pathExists(layoutPath)) {
|
|
3277
|
+
let content = await fs.readFile(layoutPath, "utf-8");
|
|
3278
|
+
content = content.replace(/id:\s*"[^"]*"/, `id: "${newName}"`);
|
|
3279
|
+
content = content.replace(
|
|
3280
|
+
/name:\s*"[^"]*Theme"/,
|
|
3281
|
+
`name: "${newDisplayName} Theme"`
|
|
3282
|
+
);
|
|
3283
|
+
await fs.writeFile(layoutPath, content);
|
|
3284
|
+
}
|
|
3285
|
+
const oldDisplayName = oldName.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
3286
|
+
const tsFiles = await glob("**/*.ts", { cwd: themeDir, nodir: true });
|
|
3287
|
+
for (const file of tsFiles) {
|
|
3288
|
+
const filePath = path.join(themeDir, file);
|
|
3289
|
+
let content = await fs.readFile(filePath, "utf-8");
|
|
3290
|
+
const original = content;
|
|
3291
|
+
content = content.replace(
|
|
3292
|
+
new RegExp(`"${oldPrefix}`, "g"),
|
|
3293
|
+
`"${newPrefix}`
|
|
3294
|
+
);
|
|
3295
|
+
content = content.replace(
|
|
3296
|
+
new RegExp(`themeId:\\s*"${oldName}"`, "g"),
|
|
3297
|
+
`themeId: "${newName}"`
|
|
3298
|
+
);
|
|
3299
|
+
content = content.replace(
|
|
3300
|
+
new RegExp(`${oldDisplayName} Theme`, "g"),
|
|
3301
|
+
`${newDisplayName} Theme`
|
|
3302
|
+
);
|
|
3303
|
+
if (content !== original) {
|
|
3304
|
+
await fs.writeFile(filePath, content);
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
2575
3308
|
async function cloneCommand(themeName, options) {
|
|
2576
3309
|
logger.header("Clone Theme Source");
|
|
3310
|
+
let newName = options.name;
|
|
3311
|
+
if (!newName) {
|
|
3312
|
+
newName = await promptThemeName(themeName);
|
|
3313
|
+
}
|
|
2577
3314
|
const spinner = ora("Initializing clone...").start();
|
|
2578
3315
|
try {
|
|
2579
3316
|
const bucket = options.bucket || getBucketName3(options.environment);
|
|
2580
|
-
const outputDir = options.output || path.resolve(process.cwd(),
|
|
3317
|
+
const outputDir = options.output || path.resolve(process.cwd(), newName);
|
|
2581
3318
|
const s3Client = getS3Client3();
|
|
2582
3319
|
if (await fs.pathExists(outputDir)) {
|
|
2583
|
-
spinner.fail(
|
|
2584
|
-
chalk4.red(`Directory already exists: ${outputDir}`)
|
|
2585
|
-
);
|
|
3320
|
+
spinner.fail(chalk4.red(`Directory already exists: ${outputDir}`));
|
|
2586
3321
|
logger.info(
|
|
2587
3322
|
chalk4.gray(
|
|
2588
3323
|
"Use -o to specify a different output directory, or remove the existing directory."
|
|
@@ -2596,9 +3331,7 @@ async function cloneCommand(themeName, options) {
|
|
|
2596
3331
|
version = await resolveLatestVersion2(s3Client, bucket, themeName);
|
|
2597
3332
|
spinner.succeed(`Resolved latest version: ${chalk4.cyan(version)}`);
|
|
2598
3333
|
}
|
|
2599
|
-
spinner.start(
|
|
2600
|
-
`Downloading source.zip for ${themeName}@${version}...`
|
|
2601
|
-
);
|
|
3334
|
+
spinner.start(`Downloading source.zip for ${themeName}@${version}...`);
|
|
2602
3335
|
const s3Key = `themes/${themeName}/${version}/source.zip`;
|
|
2603
3336
|
let zipBuffer;
|
|
2604
3337
|
try {
|
|
@@ -2616,9 +3349,7 @@ async function cloneCommand(themeName, options) {
|
|
|
2616
3349
|
chalk4.yellow("The theme source may not have been uploaded yet.")
|
|
2617
3350
|
);
|
|
2618
3351
|
console.log(
|
|
2619
|
-
chalk4.gray(
|
|
2620
|
-
`Upload source with: onex upload --theme ${themeName}`
|
|
2621
|
-
)
|
|
3352
|
+
chalk4.gray(`Upload source with: onex upload --theme ${themeName}`)
|
|
2622
3353
|
);
|
|
2623
3354
|
console.log();
|
|
2624
3355
|
process.exit(1);
|
|
@@ -2631,6 +3362,13 @@ async function cloneCommand(themeName, options) {
|
|
|
2631
3362
|
zip.extractAllTo(outputDir, true);
|
|
2632
3363
|
const entries = zip.getEntries().filter((e) => !e.isDirectory);
|
|
2633
3364
|
spinner.succeed(`Extracted ${entries.length} files`);
|
|
3365
|
+
spinner.start(
|
|
3366
|
+
`Renaming theme: ${chalk4.gray(themeName)} \u2192 ${chalk4.cyan(newName)}...`
|
|
3367
|
+
);
|
|
3368
|
+
await renameTheme(outputDir, themeName, newName);
|
|
3369
|
+
spinner.succeed(
|
|
3370
|
+
`Renamed theme: ${chalk4.gray(themeName)} \u2192 ${chalk4.cyan(newName)}`
|
|
3371
|
+
);
|
|
2634
3372
|
if (options.install !== false) {
|
|
2635
3373
|
const hasPkgJson = await fs.pathExists(
|
|
2636
3374
|
path.join(outputDir, "package.json")
|
|
@@ -2653,15 +3391,14 @@ async function cloneCommand(themeName, options) {
|
|
|
2653
3391
|
logger.success(chalk4.green.bold("Theme cloned successfully!"));
|
|
2654
3392
|
console.log();
|
|
2655
3393
|
console.log(
|
|
2656
|
-
chalk4.cyan("
|
|
3394
|
+
chalk4.cyan(" Source: ") + chalk4.gray(`${themeName}@${version}`)
|
|
2657
3395
|
);
|
|
3396
|
+
console.log(chalk4.cyan(" Theme: ") + chalk4.white(newName));
|
|
2658
3397
|
console.log(chalk4.cyan(" Location: ") + chalk4.white(outputDir));
|
|
2659
3398
|
console.log(chalk4.cyan(" Files: ") + chalk4.white(entries.length));
|
|
2660
3399
|
console.log();
|
|
2661
3400
|
console.log(chalk4.cyan("Next steps:"));
|
|
2662
|
-
console.log(
|
|
2663
|
-
chalk4.gray(` cd ${path.relative(process.cwd(), outputDir)}`)
|
|
2664
|
-
);
|
|
3401
|
+
console.log(chalk4.gray(` cd ${path.relative(process.cwd(), outputDir)}`));
|
|
2665
3402
|
if (options.install === false) {
|
|
2666
3403
|
console.log(chalk4.gray(" pnpm install"));
|
|
2667
3404
|
}
|
|
@@ -2674,11 +3411,253 @@ async function cloneCommand(themeName, options) {
|
|
|
2674
3411
|
}
|
|
2675
3412
|
}
|
|
2676
3413
|
|
|
3414
|
+
// src/commands/dev.ts
|
|
3415
|
+
init_logger();
|
|
3416
|
+
init_compile_theme();
|
|
3417
|
+
var MIME_TYPES = {
|
|
3418
|
+
".js": "application/javascript",
|
|
3419
|
+
".mjs": "application/javascript",
|
|
3420
|
+
".css": "text/css",
|
|
3421
|
+
".json": "application/json",
|
|
3422
|
+
".html": "text/html",
|
|
3423
|
+
".svg": "image/svg+xml",
|
|
3424
|
+
".png": "image/png",
|
|
3425
|
+
".jpg": "image/jpeg",
|
|
3426
|
+
".jpeg": "image/jpeg",
|
|
3427
|
+
".webp": "image/webp",
|
|
3428
|
+
".gif": "image/gif",
|
|
3429
|
+
".map": "application/json"
|
|
3430
|
+
};
|
|
3431
|
+
function createDevServer(options) {
|
|
3432
|
+
const clients = /* @__PURE__ */ new Set();
|
|
3433
|
+
const server = http.createServer((req, res) => {
|
|
3434
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
3435
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
|
|
3436
|
+
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
|
|
3437
|
+
if (req.method === "OPTIONS") {
|
|
3438
|
+
res.writeHead(200);
|
|
3439
|
+
res.end();
|
|
3440
|
+
return;
|
|
3441
|
+
}
|
|
3442
|
+
const url = new URL(req.url || "/", `http://localhost:${options.port}`);
|
|
3443
|
+
const pathname = url.pathname;
|
|
3444
|
+
if (pathname === "/" || pathname === "/index.html") {
|
|
3445
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
3446
|
+
res.end(generatePreviewHTML(options.themeName));
|
|
3447
|
+
return;
|
|
3448
|
+
}
|
|
3449
|
+
if (pathname === "/preview-runtime.js") {
|
|
3450
|
+
serveFile(res, options.previewRuntimePath);
|
|
3451
|
+
return;
|
|
3452
|
+
}
|
|
3453
|
+
const filePath = path.join(options.distDir, pathname);
|
|
3454
|
+
if (!filePath.startsWith(options.distDir)) {
|
|
3455
|
+
res.writeHead(403);
|
|
3456
|
+
res.end("Forbidden");
|
|
3457
|
+
return;
|
|
3458
|
+
}
|
|
3459
|
+
serveFile(res, filePath);
|
|
3460
|
+
});
|
|
3461
|
+
const wss = new WebSocketServer({ server });
|
|
3462
|
+
wss.on("connection", (ws) => {
|
|
3463
|
+
clients.add(ws);
|
|
3464
|
+
ws.on("close", () => clients.delete(ws));
|
|
3465
|
+
});
|
|
3466
|
+
server.listen(options.port);
|
|
3467
|
+
return {
|
|
3468
|
+
broadcast(message) {
|
|
3469
|
+
const data = JSON.stringify(message);
|
|
3470
|
+
for (const client of clients) {
|
|
3471
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
3472
|
+
client.send(data);
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
},
|
|
3476
|
+
close() {
|
|
3477
|
+
wss.close();
|
|
3478
|
+
server.close();
|
|
3479
|
+
}
|
|
3480
|
+
};
|
|
3481
|
+
}
|
|
3482
|
+
function serveFile(res, filePath) {
|
|
3483
|
+
try {
|
|
3484
|
+
if (!fs2.existsSync(filePath)) {
|
|
3485
|
+
res.writeHead(404);
|
|
3486
|
+
res.end("Not Found");
|
|
3487
|
+
return;
|
|
3488
|
+
}
|
|
3489
|
+
const ext = path.extname(filePath);
|
|
3490
|
+
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
3491
|
+
const content = fs2.readFileSync(filePath);
|
|
3492
|
+
res.writeHead(200, { "Content-Type": contentType });
|
|
3493
|
+
res.end(content);
|
|
3494
|
+
} catch (e) {
|
|
3495
|
+
res.writeHead(500);
|
|
3496
|
+
res.end("Internal Server Error");
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
function generatePreviewHTML(themeName, port) {
|
|
3500
|
+
return `<!DOCTYPE html>
|
|
3501
|
+
<html lang="en">
|
|
3502
|
+
<head>
|
|
3503
|
+
<meta charset="UTF-8">
|
|
3504
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3505
|
+
<title>OneX Dev \u2014 ${themeName}</title>
|
|
3506
|
+
<!-- Tailwind CSS Play CDN \u2014 JIT compilation in browser for dev preview -->
|
|
3507
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
3508
|
+
<style>
|
|
3509
|
+
#onex-dev-toolbar {
|
|
3510
|
+
position: fixed; top: 0; left: 0; right: 0; z-index: 9999;
|
|
3511
|
+
height: 40px; background: #1a1a2e; color: #e0e0e0;
|
|
3512
|
+
display: flex; align-items: center; padding: 0 16px; gap: 16px;
|
|
3513
|
+
font-family: system-ui; font-size: 13px;
|
|
3514
|
+
border-bottom: 2px solid #16213e;
|
|
3515
|
+
}
|
|
3516
|
+
#onex-dev-toolbar .status { width: 8px; height: 8px; border-radius: 50%; }
|
|
3517
|
+
#onex-dev-toolbar .status.connected { background: #00ff88; }
|
|
3518
|
+
#onex-dev-toolbar .status.disconnected { background: #ff4444; }
|
|
3519
|
+
#onex-dev-toolbar .status.rebuilding { background: #ffaa00; animation: pulse 0.5s infinite; }
|
|
3520
|
+
@keyframes pulse { 50% { opacity: 0.5; } }
|
|
3521
|
+
#onex-preview-root { margin-top: 40px; }
|
|
3522
|
+
</style>
|
|
3523
|
+
</head>
|
|
3524
|
+
<body>
|
|
3525
|
+
<div id="onex-dev-toolbar">
|
|
3526
|
+
<span style="font-weight:600;">OneX Dev</span>
|
|
3527
|
+
<span style="color:#888;">|</span>
|
|
3528
|
+
<span>${themeName}</span>
|
|
3529
|
+
<span style="color:#888;">|</span>
|
|
3530
|
+
<span id="page-indicator">Home</span>
|
|
3531
|
+
<span style="color:#888;">|</span>
|
|
3532
|
+
<span class="status connected" id="ws-status"></span>
|
|
3533
|
+
<span id="ws-label">Connected</span>
|
|
3534
|
+
</div>
|
|
3535
|
+
<div id="onex-preview-root"></div>
|
|
3536
|
+
<script type="module" src="/preview-runtime.js"></script>
|
|
3537
|
+
</body>
|
|
3538
|
+
</html>`;
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
// src/commands/dev.ts
|
|
3542
|
+
async function devCommand(options) {
|
|
3543
|
+
logger.header("OneX Dev Server");
|
|
3544
|
+
let themePath;
|
|
3545
|
+
let themeName;
|
|
3546
|
+
if (options.theme) {
|
|
3547
|
+
themeName = options.theme;
|
|
3548
|
+
try {
|
|
3549
|
+
const workspaceThemePath = path.join(getThemesDir(), themeName);
|
|
3550
|
+
if (fs.existsSync(workspaceThemePath)) {
|
|
3551
|
+
themePath = workspaceThemePath;
|
|
3552
|
+
} else {
|
|
3553
|
+
themePath = path.join(process.cwd(), themeName);
|
|
3554
|
+
}
|
|
3555
|
+
} catch (e) {
|
|
3556
|
+
themePath = path.join(process.cwd(), themeName);
|
|
3557
|
+
}
|
|
3558
|
+
if (!fs.existsSync(themePath)) {
|
|
3559
|
+
logger.error(`Theme "${themeName}" not found.`);
|
|
3560
|
+
process.exit(1);
|
|
3561
|
+
}
|
|
3562
|
+
} else {
|
|
3563
|
+
const isThemeDir = [
|
|
3564
|
+
"theme.config.ts",
|
|
3565
|
+
"bundle-entry.ts",
|
|
3566
|
+
"manifest.ts"
|
|
3567
|
+
].some((f) => fs.existsSync(path.join(process.cwd(), f)));
|
|
3568
|
+
if (isThemeDir) {
|
|
3569
|
+
themePath = process.cwd();
|
|
3570
|
+
themeName = path.basename(themePath);
|
|
3571
|
+
} else {
|
|
3572
|
+
logger.error(
|
|
3573
|
+
"Not in a theme directory and no --theme specified. Run from theme root or use --theme flag."
|
|
3574
|
+
);
|
|
3575
|
+
process.exit(1);
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
logger.startSpinner("Compiling preview runtime...");
|
|
3579
|
+
const previewRuntimePath = await compilePreviewRuntime(themePath);
|
|
3580
|
+
logger.stopSpinner(true, "Preview runtime compiled");
|
|
3581
|
+
logger.startSpinner("Compiling theme...");
|
|
3582
|
+
const { context: context2, outputDir } = await compileStandaloneThemeDev(
|
|
3583
|
+
themePath,
|
|
3584
|
+
themeName
|
|
3585
|
+
);
|
|
3586
|
+
logger.stopSpinner(true, "Theme compiled");
|
|
3587
|
+
const port = Number(options.port) || 3456;
|
|
3588
|
+
const server = createDevServer({
|
|
3589
|
+
port,
|
|
3590
|
+
distDir: outputDir,
|
|
3591
|
+
previewRuntimePath,
|
|
3592
|
+
themeName
|
|
3593
|
+
});
|
|
3594
|
+
const watcher = chokidar.watch(
|
|
3595
|
+
[
|
|
3596
|
+
"sections",
|
|
3597
|
+
"blocks",
|
|
3598
|
+
"components",
|
|
3599
|
+
"pages",
|
|
3600
|
+
"theme.config.ts",
|
|
3601
|
+
"theme.layout.ts",
|
|
3602
|
+
"bundle-entry.ts",
|
|
3603
|
+
"sections-registry.ts"
|
|
3604
|
+
],
|
|
3605
|
+
{ cwd: themePath, ignoreInitial: true }
|
|
3606
|
+
);
|
|
3607
|
+
let debounceTimer;
|
|
3608
|
+
watcher.on("all", (_event, filePath) => {
|
|
3609
|
+
clearTimeout(debounceTimer);
|
|
3610
|
+
debounceTimer = setTimeout(async () => {
|
|
3611
|
+
logger.info(`File changed: ${filePath}`);
|
|
3612
|
+
try {
|
|
3613
|
+
await context2.rebuild();
|
|
3614
|
+
await generateManifest2(themeName, themePath, outputDir);
|
|
3615
|
+
server.broadcast({ type: "reload", timestamp: Date.now() });
|
|
3616
|
+
logger.success("Rebuilt successfully");
|
|
3617
|
+
} catch (error) {
|
|
3618
|
+
server.broadcast({ type: "error", message: String(error) });
|
|
3619
|
+
logger.error(`Rebuild failed: ${error}`);
|
|
3620
|
+
}
|
|
3621
|
+
}, 150);
|
|
3622
|
+
});
|
|
3623
|
+
logger.newLine();
|
|
3624
|
+
logger.success(`Theme compiled: ${themeName}`);
|
|
3625
|
+
logger.info(`Preview: http://localhost:${port}`);
|
|
3626
|
+
logger.info("Watching for changes...");
|
|
3627
|
+
logger.newLine();
|
|
3628
|
+
if (options.open !== false) {
|
|
3629
|
+
const open = await import('open');
|
|
3630
|
+
open.default(`http://localhost:${port}`);
|
|
3631
|
+
}
|
|
3632
|
+
process.on("SIGINT", async () => {
|
|
3633
|
+
logger.newLine();
|
|
3634
|
+
logger.info("Shutting down...");
|
|
3635
|
+
watcher.close();
|
|
3636
|
+
await context2.dispose();
|
|
3637
|
+
server.close();
|
|
3638
|
+
const shimPath = path.join(outputDir, ".process-shim.js");
|
|
3639
|
+
try {
|
|
3640
|
+
await fs7.unlink(shimPath);
|
|
3641
|
+
} catch (e) {
|
|
3642
|
+
}
|
|
3643
|
+
process.exit(0);
|
|
3644
|
+
});
|
|
3645
|
+
}
|
|
3646
|
+
|
|
2677
3647
|
// src/cli.ts
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
dotenv.config({
|
|
2681
|
-
|
|
3648
|
+
try {
|
|
3649
|
+
const projectRoot = getProjectRoot();
|
|
3650
|
+
dotenv.config({
|
|
3651
|
+
path: path.join(projectRoot, ".env.local"),
|
|
3652
|
+
quiet: true
|
|
3653
|
+
});
|
|
3654
|
+
dotenv.config({ path: path.join(projectRoot, ".env"), quiet: true });
|
|
3655
|
+
} catch (e) {
|
|
3656
|
+
}
|
|
3657
|
+
dotenv.config({
|
|
3658
|
+
path: path.join(os.homedir(), ".onex", ".env"),
|
|
3659
|
+
quiet: true
|
|
3660
|
+
});
|
|
2682
3661
|
var program = new Command();
|
|
2683
3662
|
program.name("onex").description("CLI tool for OneX theme development").version("0.1.0");
|
|
2684
3663
|
program.command("init").description("Create a new OneX theme project").argument("[project-name]", "Name of the project").option(
|
|
@@ -2700,6 +3679,7 @@ program.command("create:block").alias("cb").description("Create a new block").ar
|
|
|
2700
3679
|
program.command("create:component").alias("cc").description("Create a new component").argument("<name>", "Name of the component (e.g., button, badge)").option("-t, --type <type>", "Component type (ui, layout, form)").action(createComponentCommand);
|
|
2701
3680
|
program.command("list").description("List available themes, sections, blocks, and components").option("-s, --sections", "List sections only").option("-b, --blocks", "List blocks only").option("-c, --components", "List components only").option("-t, --theme <theme>", "Filter by theme").action(listCommand);
|
|
2702
3681
|
program.command("validate").description("Validate theme structure and files").option("-t, --theme <theme>", "Theme to validate").option("-f, --fix", "Auto-fix issues if possible (not implemented yet)").action(validateCommand);
|
|
3682
|
+
program.command("dev").description("Start dev server with live preview and hot reload").option("-t, --theme <theme>", "Theme to develop").option("-p, --port <port>", "Dev server port", "3456").option("--no-open", "Don't open browser automatically").action(devCommand);
|
|
2703
3683
|
program.command("build").description("Build theme for production").option("-t, --theme <theme>", "Theme to build").option("-p, --production", "Production build with optimizations").option("-w, --watch", "Watch mode for development").action(buildCommand);
|
|
2704
3684
|
program.command("package").description("Compile and package theme as distributable zip file").option("-t, --theme <theme>", "Theme to package").option("-o, --output <dir>", "Output directory for package").option("-n, --name <name>", "Custom package name").option("-m, --minify", "Minify compiled output").option("--skip-build", "Skip compilation step (use existing compiled theme)").action(packageCommand);
|
|
2705
3685
|
program.command("deploy").description("Upload theme package to API server").option("-t, --theme <theme>", "Theme to deploy (finds latest package)").option("-p, --package <file>", "Specific package file to upload").option("--api-url <url>", "API server URL (default: http://localhost:3001)").option("-k, --api-key <key>", "API key for authentication").option(
|
|
@@ -2724,7 +3704,7 @@ program.command("clone").description("Clone theme source code from S3").argument
|
|
|
2724
3704
|
"-v, --version <version>",
|
|
2725
3705
|
"Theme version (default: latest)",
|
|
2726
3706
|
"latest"
|
|
2727
|
-
).option("-o, --output <dir>", "Output directory").option("-b, --bucket <name>", "S3 bucket name").option(
|
|
3707
|
+
).option("-n, --name <name>", "New theme name (skips interactive prompt)").option("-o, --output <dir>", "Output directory").option("-b, --bucket <name>", "S3 bucket name").option(
|
|
2728
3708
|
"-e, --environment <env>",
|
|
2729
3709
|
"Environment (staging|production)",
|
|
2730
3710
|
"staging"
|