@onexapis/cli 1.0.4 → 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/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 = class {
22
- constructor() {
23
- this.spinner = null;
24
- }
25
- success(message) {
26
- console.log(chalk4.green("\u2713"), message);
27
- }
28
- error(message) {
29
- console.log(chalk4.red("\u2717"), message);
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
- warning(message) {
32
- console.log(chalk4.yellow("\u26A0"), message);
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
- info(message) {
35
- console.log(chalk4.blue("\u2139"), message);
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
- log(message) {
38
- console.log(message);
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
- startSpinner(message) {
41
- this.spinner = ora(message).start();
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
- stopSpinner(success = true, message) {
44
- if (!this.spinner) return;
45
- if (success) {
46
- this.spinner.succeed(message);
47
- } else {
48
- this.spinner.fail(message);
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
- this.spinner = null;
249
+ } catch (e) {
51
250
  }
52
- updateSpinner(message) {
53
- if (this.spinner) {
54
- this.spinner.text = message;
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
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`);
55
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
- newLine() {
58
- console.log();
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
- header(message) {
61
- console.log();
62
- console.log(chalk4.bold.cyan(message));
63
- console.log(chalk4.cyan("=".repeat(message.length)));
64
- console.log();
461
+ if (!previewEntryPath) {
462
+ throw new Error(
463
+ `Preview app source not found. Searched:
464
+ ${locations.join("\n")}`
465
+ );
65
466
  }
66
- section(message) {
67
- console.log();
68
- console.log(chalk4.bold(message));
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,8 +749,10 @@ function getProjectRoot() {
156
749
  }
157
750
  function getThemesDir() {
158
751
  const root = getProjectRoot();
159
- if (fs.existsSync(path.join(root, "themes"))) return path.join(root, "themes");
160
- if (fs.existsSync(path.join(root, "src/themes"))) return path.join(root, "src/themes");
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");
161
756
  return path.dirname(root);
162
757
  }
163
758
  function getFeaturesDir() {
@@ -215,6 +810,9 @@ async function installDependencies(projectPath, packageManager = "npm") {
215
810
  });
216
811
  }
217
812
 
813
+ // src/commands/init.ts
814
+ init_logger();
815
+
218
816
  // src/utils/validators.ts
219
817
  function validateName(name) {
220
818
  return /^[a-z0-9]+(-[a-z0-9]+)*$/.test(name);
@@ -656,6 +1254,9 @@ export const homePageConfig: PageConfig = {
656
1254
  };
657
1255
  `;
658
1256
  }
1257
+
1258
+ // src/commands/create-section.ts
1259
+ init_logger();
659
1260
  async function createSectionCommand(name, options) {
660
1261
  logger.header("Create New Section");
661
1262
  ensureOneXProject();
@@ -895,6 +1496,9 @@ export { ${data.sectionName}Schema } from "./${data.sectionName}.schema";
895
1496
  ${hasTemplate ? `export { ${data.sectionNamePascal}Default } from "./${data.sectionName}-default";` : ""}
896
1497
  `;
897
1498
  }
1499
+
1500
+ // src/commands/create-block.ts
1501
+ init_logger();
898
1502
  async function createBlockCommand(name, options) {
899
1503
  logger.header("Create New Block");
900
1504
  ensureOneXProject();
@@ -1119,6 +1723,9 @@ export { ${data.blockName}Definition } from "./${data.blockName}.schema";
1119
1723
  export { ${data.blockNamePascal} } from "./${data.blockName}";
1120
1724
  `;
1121
1725
  }
1726
+
1727
+ // src/commands/create-component.ts
1728
+ init_logger();
1122
1729
  async function createComponentCommand(name, options) {
1123
1730
  logger.header("Create New Component");
1124
1731
  ensureOneXProject();
@@ -1323,6 +1930,9 @@ export { ${data.componentName}Definition } from "./${data.componentName}.schema"
1323
1930
  export { ${data.componentNamePascal} } from "./${data.componentName}";
1324
1931
  `;
1325
1932
  }
1933
+
1934
+ // src/commands/list.ts
1935
+ init_logger();
1326
1936
  async function listCommand(options) {
1327
1937
  logger.header("OneX Project Inventory");
1328
1938
  ensureOneXProject();
@@ -1454,6 +2064,9 @@ async function listThemesInfo() {
1454
2064
  }
1455
2065
  logger.newLine();
1456
2066
  }
2067
+
2068
+ // src/commands/validate.ts
2069
+ init_logger();
1457
2070
  async function validateCommand(options) {
1458
2071
  logger.header("Validate Theme");
1459
2072
  ensureOneXProject();
@@ -1466,7 +2079,11 @@ async function validateCommand(options) {
1466
2079
  }
1467
2080
  themeToValidate = options.theme;
1468
2081
  } else {
1469
- const isThemeDir = ["theme.config.ts", "bundle-entry.ts", "manifest.ts"].some((f) => fs.existsSync(path.join(process.cwd(), f)));
2082
+ const isThemeDir = [
2083
+ "theme.config.ts",
2084
+ "bundle-entry.ts",
2085
+ "manifest.ts"
2086
+ ].some((f) => fs.existsSync(path.join(process.cwd(), f)));
1470
2087
  if (isThemeDir) {
1471
2088
  themeToValidate = path.basename(process.cwd());
1472
2089
  logger.info(`Validating current theme: ${themeToValidate}`);
@@ -1479,15 +2096,21 @@ async function validateCommand(options) {
1479
2096
  }
1480
2097
  const themePath = path.join(getThemesDir(), themeToValidate);
1481
2098
  logger.startSpinner("Running validation checks...");
1482
- const manifestPath = path.join(themePath, "manifest.ts");
1483
- if (!fs.existsSync(manifestPath)) {
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) {
1484
2104
  issues.push({
1485
2105
  type: "error",
1486
- file: "manifest.ts",
1487
- message: "Manifest file not found"
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)"
1488
2108
  });
1489
- } else {
1490
- const manifestContent = fs.readFileSync(manifestPath, "utf-8");
2109
+ } else if (foundEntry === "manifest.ts") {
2110
+ const manifestContent = fs.readFileSync(
2111
+ path.join(themePath, foundEntry),
2112
+ "utf-8"
2113
+ );
1491
2114
  if (!manifestContent.includes("export const") && !manifestContent.includes("export default") && !manifestContent.includes("export interface")) {
1492
2115
  issues.push({
1493
2116
  type: "error",
@@ -1623,7 +2246,11 @@ async function validateCommand(options) {
1623
2246
  }
1624
2247
  }
1625
2248
  }
2249
+
2250
+ // src/commands/build.ts
2251
+ init_logger();
1626
2252
  async function buildCommand(options) {
2253
+ var _a;
1627
2254
  logger.header("Build Theme");
1628
2255
  let themePath;
1629
2256
  let themeName;
@@ -1644,7 +2271,11 @@ async function buildCommand(options) {
1644
2271
  process.exit(1);
1645
2272
  }
1646
2273
  } else {
1647
- const isThemeDir = ["theme.config.ts", "bundle-entry.ts", "manifest.ts"].some((f) => fs.existsSync(path.join(process.cwd(), f)));
2274
+ const isThemeDir = [
2275
+ "theme.config.ts",
2276
+ "bundle-entry.ts",
2277
+ "manifest.ts"
2278
+ ].some((f) => fs.existsSync(path.join(process.cwd(), f)));
1648
2279
  if (isThemeDir) {
1649
2280
  themePath = process.cwd();
1650
2281
  themeName = path.basename(themePath);
@@ -1688,11 +2319,20 @@ async function buildCommand(options) {
1688
2319
  process.exit(1);
1689
2320
  }
1690
2321
  logger.stopSpinner(true, "Lint passed");
1691
- const buildArgs = options.watch ? ["build", "--watch"] : ["build"];
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");
1692
2325
  logger.startSpinner(
1693
2326
  options.watch ? "Building (watch mode)..." : "Building..."
1694
2327
  );
1695
- const buildSuccess = await runCommand("pnpm", buildArgs, themePath);
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
+ }
1696
2336
  if (!buildSuccess && !options.watch) {
1697
2337
  logger.stopSpinner(false, "Build failed");
1698
2338
  process.exit(1);
@@ -1733,6 +2373,9 @@ function runCommand(command, args, cwd) {
1733
2373
  });
1734
2374
  });
1735
2375
  }
2376
+
2377
+ // src/commands/package.ts
2378
+ init_logger();
1736
2379
  async function packageCommand(options) {
1737
2380
  logger.header("Package Theme");
1738
2381
  ensureOneXProject();
@@ -1746,7 +2389,11 @@ async function packageCommand(options) {
1746
2389
  process.exit(1);
1747
2390
  }
1748
2391
  } else {
1749
- const isThemeDir = ["theme.config.ts", "bundle-entry.ts", "manifest.ts"].some((f) => fs.existsSync(path.join(process.cwd(), f)));
2392
+ const isThemeDir = [
2393
+ "theme.config.ts",
2394
+ "bundle-entry.ts",
2395
+ "manifest.ts"
2396
+ ].some((f) => fs.existsSync(path.join(process.cwd(), f)));
1750
2397
  if (isThemeDir) {
1751
2398
  themePath = process.cwd();
1752
2399
  themeName = path.basename(themePath);
@@ -1770,10 +2417,9 @@ async function packageCommand(options) {
1770
2417
  logger.newLine();
1771
2418
  const compiledThemePath = path.join(
1772
2419
  process.cwd(),
1773
- "apps",
1774
- "api-server",
1775
- "compiled-themes",
1776
- `${themeName}@${version}`
2420
+ "themes",
2421
+ themeName,
2422
+ "dist"
1777
2423
  );
1778
2424
  if (!options.skipBuild) {
1779
2425
  logger.section("Step 1: Compile Theme");
@@ -1871,6 +2517,9 @@ async function createZipArchive(compiledThemePath, outputPath) {
1871
2517
  archive.finalize();
1872
2518
  });
1873
2519
  }
2520
+
2521
+ // src/commands/deploy.ts
2522
+ init_logger();
1874
2523
  async function deployCommand(options) {
1875
2524
  logger.header("Deploy Theme");
1876
2525
  ensureOneXProject();
@@ -1973,6 +2622,9 @@ async function deployCommand(options) {
1973
2622
  process.exit(1);
1974
2623
  }
1975
2624
  }
2625
+
2626
+ // src/commands/upload.ts
2627
+ init_logger();
1976
2628
  function getS3Client() {
1977
2629
  const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
1978
2630
  if (adapterMode === "vps") {
@@ -2012,21 +2664,12 @@ function getBucketName(env) {
2012
2664
  return environment === "production" ? "onex-themes-prod" : "onex-themes-staging";
2013
2665
  }
2014
2666
  async function findCompiledThemeDir(themeId, version) {
2015
- const searchPaths = [
2016
- path.resolve(process.cwd(), "dist"),
2017
- path.resolve(
2018
- process.cwd(),
2019
- `../../apps/api-server/compiled-themes/${themeId}@${version}`
2020
- ),
2021
- path.resolve(
2022
- process.cwd(),
2023
- `../api-server/compiled-themes/${themeId}@${version}`
2024
- )
2025
- ];
2667
+ const searchPaths = [path.resolve(process.cwd(), "dist")];
2026
2668
  for (const dir of searchPaths) {
2027
2669
  if (await fs.pathExists(dir)) {
2028
- const manifestPath = path.join(dir, "manifest.json");
2029
- if (await fs.pathExists(manifestPath)) {
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) {
2030
2673
  return dir;
2031
2674
  }
2032
2675
  }
@@ -2129,13 +2772,8 @@ async function uploadCommand(options) {
2129
2772
  `Compiled theme not found for ${themeId}@${version}. Run 'onex build' first.`
2130
2773
  )
2131
2774
  );
2132
- logger.info(
2133
- chalk4.gray(
2134
- `Expected locations:
2135
- - ./dist/
2136
- - ../../apps/api-server/compiled-themes/${themeId}@${version}/`
2137
- )
2138
- );
2775
+ logger.info(chalk4.gray(`Expected location:
2776
+ - ./dist/`));
2139
2777
  process.exit(1);
2140
2778
  }
2141
2779
  spinner.succeed(`Found compiled theme at: ${compiledDir}`);
@@ -2242,9 +2880,7 @@ async function uploadCommand(options) {
2242
2880
  );
2243
2881
  console.log(chalk4.cyan(" Bucket: ") + chalk4.white(bucket));
2244
2882
  console.log(
2245
- chalk4.cyan(" Files: ") + chalk4.white(
2246
- `bundle.zip${sourceUploaded ? " + source.zip" : ""}`
2247
- )
2883
+ chalk4.cyan(" Files: ") + chalk4.white(`bundle.zip${sourceUploaded ? " + source.zip" : ""}`)
2248
2884
  );
2249
2885
  console.log(
2250
2886
  chalk4.cyan(" Path: ") + chalk4.gray(`s3://${bucket}/themes/${themeId}/${version}/`)
@@ -2256,6 +2892,9 @@ async function uploadCommand(options) {
2256
2892
  process.exit(1);
2257
2893
  }
2258
2894
  }
2895
+
2896
+ // src/commands/download.ts
2897
+ init_logger();
2259
2898
  function getS3Client2() {
2260
2899
  const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
2261
2900
  if (adapterMode === "vps") {
@@ -2403,11 +3042,7 @@ function showDownloadFailureHelp(themeId, bucket) {
2403
3042
  );
2404
3043
  console.log();
2405
3044
  console.log(chalk4.white("4. Verify theme exists in S3:"));
2406
- console.log(
2407
- chalk4.gray(
2408
- ` aws s3 ls s3://${bucket}/themes/${themeId}/`
2409
- )
2410
- );
3045
+ console.log(chalk4.gray(` aws s3 ls s3://${bucket}/themes/${themeId}/`));
2411
3046
  console.log();
2412
3047
  }
2413
3048
  async function downloadCommand(options) {
@@ -2483,6 +3118,9 @@ async function downloadCommand(options) {
2483
3118
  process.exit(1);
2484
3119
  }
2485
3120
  }
3121
+
3122
+ // src/commands/clone.ts
3123
+ init_logger();
2486
3124
  function getS3Client3() {
2487
3125
  const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
2488
3126
  if (adapterMode === "vps") {
@@ -2586,17 +3224,100 @@ function runInstall(cwd) {
2586
3224
  proc.on("error", () => resolve(false));
2587
3225
  });
2588
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
+ }
2589
3308
  async function cloneCommand(themeName, options) {
2590
3309
  logger.header("Clone Theme Source");
3310
+ let newName = options.name;
3311
+ if (!newName) {
3312
+ newName = await promptThemeName(themeName);
3313
+ }
2591
3314
  const spinner = ora("Initializing clone...").start();
2592
3315
  try {
2593
3316
  const bucket = options.bucket || getBucketName3(options.environment);
2594
- const outputDir = options.output || path.resolve(process.cwd(), themeName);
3317
+ const outputDir = options.output || path.resolve(process.cwd(), newName);
2595
3318
  const s3Client = getS3Client3();
2596
3319
  if (await fs.pathExists(outputDir)) {
2597
- spinner.fail(
2598
- chalk4.red(`Directory already exists: ${outputDir}`)
2599
- );
3320
+ spinner.fail(chalk4.red(`Directory already exists: ${outputDir}`));
2600
3321
  logger.info(
2601
3322
  chalk4.gray(
2602
3323
  "Use -o to specify a different output directory, or remove the existing directory."
@@ -2610,9 +3331,7 @@ async function cloneCommand(themeName, options) {
2610
3331
  version = await resolveLatestVersion2(s3Client, bucket, themeName);
2611
3332
  spinner.succeed(`Resolved latest version: ${chalk4.cyan(version)}`);
2612
3333
  }
2613
- spinner.start(
2614
- `Downloading source.zip for ${themeName}@${version}...`
2615
- );
3334
+ spinner.start(`Downloading source.zip for ${themeName}@${version}...`);
2616
3335
  const s3Key = `themes/${themeName}/${version}/source.zip`;
2617
3336
  let zipBuffer;
2618
3337
  try {
@@ -2630,9 +3349,7 @@ async function cloneCommand(themeName, options) {
2630
3349
  chalk4.yellow("The theme source may not have been uploaded yet.")
2631
3350
  );
2632
3351
  console.log(
2633
- chalk4.gray(
2634
- `Upload source with: onex upload --theme ${themeName}`
2635
- )
3352
+ chalk4.gray(`Upload source with: onex upload --theme ${themeName}`)
2636
3353
  );
2637
3354
  console.log();
2638
3355
  process.exit(1);
@@ -2645,6 +3362,13 @@ async function cloneCommand(themeName, options) {
2645
3362
  zip.extractAllTo(outputDir, true);
2646
3363
  const entries = zip.getEntries().filter((e) => !e.isDirectory);
2647
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
+ );
2648
3372
  if (options.install !== false) {
2649
3373
  const hasPkgJson = await fs.pathExists(
2650
3374
  path.join(outputDir, "package.json")
@@ -2667,15 +3391,14 @@ async function cloneCommand(themeName, options) {
2667
3391
  logger.success(chalk4.green.bold("Theme cloned successfully!"));
2668
3392
  console.log();
2669
3393
  console.log(
2670
- chalk4.cyan(" Theme: ") + chalk4.white(`${themeName}@${version}`)
3394
+ chalk4.cyan(" Source: ") + chalk4.gray(`${themeName}@${version}`)
2671
3395
  );
3396
+ console.log(chalk4.cyan(" Theme: ") + chalk4.white(newName));
2672
3397
  console.log(chalk4.cyan(" Location: ") + chalk4.white(outputDir));
2673
3398
  console.log(chalk4.cyan(" Files: ") + chalk4.white(entries.length));
2674
3399
  console.log();
2675
3400
  console.log(chalk4.cyan("Next steps:"));
2676
- console.log(
2677
- chalk4.gray(` cd ${path.relative(process.cwd(), outputDir)}`)
2678
- );
3401
+ console.log(chalk4.gray(` cd ${path.relative(process.cwd(), outputDir)}`));
2679
3402
  if (options.install === false) {
2680
3403
  console.log(chalk4.gray(" pnpm install"));
2681
3404
  }
@@ -2688,14 +3411,253 @@ async function cloneCommand(themeName, options) {
2688
3411
  }
2689
3412
  }
2690
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
+
2691
3647
  // src/cli.ts
2692
3648
  try {
2693
3649
  const projectRoot = getProjectRoot();
2694
- dotenv.config({ path: path.join(projectRoot, ".env.local"), quiet: true });
3650
+ dotenv.config({
3651
+ path: path.join(projectRoot, ".env.local"),
3652
+ quiet: true
3653
+ });
2695
3654
  dotenv.config({ path: path.join(projectRoot, ".env"), quiet: true });
2696
3655
  } catch (e) {
2697
3656
  }
2698
- dotenv.config({ path: path.join(os.homedir(), ".onex", ".env"), quiet: true });
3657
+ dotenv.config({
3658
+ path: path.join(os.homedir(), ".onex", ".env"),
3659
+ quiet: true
3660
+ });
2699
3661
  var program = new Command();
2700
3662
  program.name("onex").description("CLI tool for OneX theme development").version("0.1.0");
2701
3663
  program.command("init").description("Create a new OneX theme project").argument("[project-name]", "Name of the project").option(
@@ -2717,6 +3679,7 @@ program.command("create:block").alias("cb").description("Create a new block").ar
2717
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);
2718
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);
2719
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);
2720
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);
2721
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);
2722
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(
@@ -2741,7 +3704,7 @@ program.command("clone").description("Clone theme source code from S3").argument
2741
3704
  "-v, --version <version>",
2742
3705
  "Theme version (default: latest)",
2743
3706
  "latest"
2744
- ).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(
2745
3708
  "-e, --environment <env>",
2746
3709
  "Environment (staging|production)",
2747
3710
  "staging"