@onexapis/cli 1.0.4 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -63
- package/dist/cli.js +1150 -105
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +1125 -103
- 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 +1018 -237
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +852 -90
- 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,710 @@ 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
|
+
build2.onResolve({ filter: /^lucide-react/ }, async (args) => {
|
|
515
|
+
var _a;
|
|
516
|
+
if ((_a = args.pluginData) == null ? void 0 : _a.skipStub) return void 0;
|
|
517
|
+
try {
|
|
518
|
+
const result = await build2.resolve(args.path, {
|
|
519
|
+
kind: args.kind,
|
|
520
|
+
resolveDir: args.resolveDir,
|
|
521
|
+
importer: args.importer,
|
|
522
|
+
namespace: "file",
|
|
523
|
+
pluginData: { skipStub: true }
|
|
524
|
+
});
|
|
525
|
+
if (!result.errors.length) return result;
|
|
526
|
+
} catch (e) {
|
|
527
|
+
}
|
|
528
|
+
return { path: args.path, namespace: "lucide-stub" };
|
|
529
|
+
});
|
|
530
|
+
build2.onLoad({ filter: /.*/, namespace: "lucide-stub" }, () => ({
|
|
531
|
+
// Provide all icon names used by @onexapis/core as no-op SVG stub components
|
|
532
|
+
contents: `
|
|
533
|
+
const icon = (props) => null;
|
|
534
|
+
export { icon as Check, icon as ChevronDown, icon as XCircle, icon as AlertTriangle };
|
|
535
|
+
export { icon as CheckCircle, icon as Info, icon as X, icon as XIcon };
|
|
536
|
+
export { icon as CircleIcon, icon as Star, icon as ShoppingCart };
|
|
537
|
+
export { icon as ChevronRight, icon as ChevronLeft, icon as ChevronUp };
|
|
538
|
+
export { icon as Search, icon as Menu, icon as Heart, icon as User };
|
|
539
|
+
export { icon as Trash2, icon as Plus, icon as Minus, icon as Eye, icon as EyeOff };
|
|
540
|
+
export { icon as ArrowRight, icon as ArrowLeft, icon as ExternalLink, icon as Mail };
|
|
541
|
+
export { icon as Phone, icon as MapPin, icon as Calendar, icon as Clock };
|
|
542
|
+
export { icon as Facebook, icon as Twitter, icon as Instagram, icon as Linkedin, icon as Github };
|
|
543
|
+
export default new Proxy({}, { get: (_, name) => name === '__esModule' ? true : icon });
|
|
544
|
+
`.trim(),
|
|
545
|
+
loader: "jsx"
|
|
546
|
+
}));
|
|
547
|
+
build2.onResolve({ filter: /^framer-motion/ }, async (args) => {
|
|
548
|
+
var _a;
|
|
549
|
+
if ((_a = args.pluginData) == null ? void 0 : _a.skipStub) return void 0;
|
|
550
|
+
try {
|
|
551
|
+
const result = await build2.resolve(args.path, {
|
|
552
|
+
kind: args.kind,
|
|
553
|
+
resolveDir: args.resolveDir,
|
|
554
|
+
importer: args.importer,
|
|
555
|
+
namespace: "file",
|
|
556
|
+
pluginData: { skipStub: true }
|
|
557
|
+
});
|
|
558
|
+
if (!result.errors.length) return result;
|
|
559
|
+
} catch (e) {
|
|
560
|
+
}
|
|
561
|
+
return { path: args.path, namespace: "motion-stub" };
|
|
562
|
+
});
|
|
563
|
+
build2.onLoad({ filter: /.*/, namespace: "motion-stub" }, () => ({
|
|
564
|
+
contents: `
|
|
565
|
+
const stub = (props) => props.children || null;
|
|
566
|
+
const handler = { get: (_, name) => name === '__esModule' ? true : stub };
|
|
567
|
+
export const motion = new Proxy({}, handler);
|
|
568
|
+
export const AnimatePresence = stub;
|
|
569
|
+
export default { motion, AnimatePresence };
|
|
570
|
+
`.trim(),
|
|
571
|
+
loader: "jsx"
|
|
572
|
+
}));
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
await esbuild.build({
|
|
576
|
+
entryPoints: [previewEntryPath],
|
|
577
|
+
bundle: true,
|
|
578
|
+
platform: "browser",
|
|
579
|
+
format: "esm",
|
|
580
|
+
outfile: outputPath,
|
|
581
|
+
// Bundle React + core INTO the output (NOT externalized)
|
|
582
|
+
external: [],
|
|
583
|
+
plugins: [serverStubPlugin],
|
|
584
|
+
minify: false,
|
|
585
|
+
sourcemap: true,
|
|
586
|
+
target: "es2020",
|
|
587
|
+
jsx: "automatic",
|
|
588
|
+
jsxImportSource: "react",
|
|
589
|
+
define: {
|
|
590
|
+
"process.env.NODE_ENV": JSON.stringify("development"),
|
|
591
|
+
global: "globalThis"
|
|
592
|
+
},
|
|
593
|
+
loader: { ".tsx": "tsx", ".ts": "ts" },
|
|
594
|
+
// Force CJS resolution to avoid sideEffects:false dropping ESM chunk imports
|
|
595
|
+
conditions: ["require", "default"],
|
|
596
|
+
mainFields: ["main"],
|
|
597
|
+
logOverride: {
|
|
598
|
+
"ignored-bare-import": "silent"
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
return outputPath;
|
|
602
|
+
}
|
|
603
|
+
var PROCESS_SHIM, reactGlobalPlugin;
|
|
604
|
+
var init_compile_theme = __esm({
|
|
605
|
+
"src/utils/compile-theme.ts"() {
|
|
606
|
+
init_logger();
|
|
607
|
+
PROCESS_SHIM = `
|
|
608
|
+
if (typeof process === "undefined") {
|
|
609
|
+
globalThis.process = {
|
|
610
|
+
env: {},
|
|
611
|
+
browser: true,
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
`;
|
|
615
|
+
reactGlobalPlugin = {
|
|
616
|
+
name: "react-global",
|
|
617
|
+
setup(build2) {
|
|
618
|
+
build2.onResolve({ filter: /^react$/ }, () => ({
|
|
619
|
+
path: "react-external",
|
|
620
|
+
namespace: "react-global"
|
|
621
|
+
}));
|
|
622
|
+
build2.onResolve({ filter: /^react-dom$/ }, () => ({
|
|
623
|
+
path: "react-dom-external",
|
|
624
|
+
namespace: "react-global"
|
|
625
|
+
}));
|
|
626
|
+
build2.onResolve({ filter: /^react\/jsx-runtime$/ }, () => ({
|
|
627
|
+
path: "react-jsx-runtime-external",
|
|
628
|
+
namespace: "react-global"
|
|
629
|
+
}));
|
|
630
|
+
build2.onLoad({ filter: /.*/, namespace: "react-global" }, (args) => {
|
|
631
|
+
if (args.path === "react-external") {
|
|
632
|
+
return {
|
|
633
|
+
contents: `
|
|
634
|
+
if (!globalThis.__ONEX_REACT__) {
|
|
635
|
+
throw new Error('[Theme Bundle] React not initialized. Ensure globalThis.__ONEX_REACT__ is set before loading theme.');
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const React = globalThis.__ONEX_REACT__;
|
|
639
|
+
export default React;
|
|
640
|
+
|
|
641
|
+
export const {
|
|
642
|
+
useState,
|
|
643
|
+
useEffect,
|
|
644
|
+
useContext,
|
|
645
|
+
useReducer,
|
|
646
|
+
useCallback,
|
|
647
|
+
useMemo,
|
|
648
|
+
useRef,
|
|
649
|
+
useImperativeHandle,
|
|
650
|
+
useLayoutEffect,
|
|
651
|
+
useDebugValue,
|
|
652
|
+
useDeferredValue,
|
|
653
|
+
useTransition,
|
|
654
|
+
useId,
|
|
655
|
+
useSyncExternalStore,
|
|
656
|
+
useInsertionEffect,
|
|
657
|
+
createContext,
|
|
658
|
+
forwardRef,
|
|
659
|
+
lazy,
|
|
660
|
+
memo,
|
|
661
|
+
startTransition,
|
|
662
|
+
createElement,
|
|
663
|
+
cloneElement,
|
|
664
|
+
isValidElement,
|
|
665
|
+
Children,
|
|
666
|
+
Fragment,
|
|
667
|
+
Profiler,
|
|
668
|
+
StrictMode,
|
|
669
|
+
Suspense,
|
|
670
|
+
Component,
|
|
671
|
+
PureComponent,
|
|
672
|
+
useActionState,
|
|
673
|
+
use,
|
|
674
|
+
} = React;
|
|
675
|
+
`.trim(),
|
|
676
|
+
loader: "js"
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
if (args.path === "react-dom-external") {
|
|
680
|
+
return {
|
|
681
|
+
contents: `
|
|
682
|
+
if (!globalThis.__ONEX_REACT_DOM__) {
|
|
683
|
+
throw new Error('[Theme Bundle] ReactDOM not initialized. Ensure globalThis.__ONEX_REACT_DOM__ is set before loading theme.');
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const ReactDOM = globalThis.__ONEX_REACT_DOM__;
|
|
687
|
+
export default ReactDOM;
|
|
688
|
+
|
|
689
|
+
export const {
|
|
690
|
+
createRoot,
|
|
691
|
+
hydrateRoot,
|
|
692
|
+
flushSync,
|
|
693
|
+
createPortal,
|
|
694
|
+
findDOMNode,
|
|
695
|
+
render,
|
|
696
|
+
hydrate,
|
|
697
|
+
unmountComponentAtNode,
|
|
698
|
+
} = ReactDOM;
|
|
699
|
+
`.trim(),
|
|
700
|
+
loader: "js"
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
if (args.path === "react-jsx-runtime-external") {
|
|
704
|
+
return {
|
|
705
|
+
contents: `
|
|
706
|
+
if (!globalThis.__ONEX_JSX_RUNTIME__) {
|
|
707
|
+
throw new Error('[Theme Bundle] React JSX runtime not initialized. Ensure globalThis.__ONEX_JSX_RUNTIME__ is set before loading theme.');
|
|
708
|
+
}
|
|
709
|
+
const _jsxRuntime = globalThis.__ONEX_JSX_RUNTIME__;
|
|
710
|
+
export const jsx = _jsxRuntime.jsx;
|
|
711
|
+
export const jsxs = _jsxRuntime.jsxs;
|
|
712
|
+
export const Fragment = _jsxRuntime.Fragment;
|
|
713
|
+
`.trim(),
|
|
714
|
+
loader: "js"
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
return null;
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
};
|
|
69
721
|
}
|
|
70
|
-
};
|
|
71
|
-
var logger = new Logger();
|
|
722
|
+
});
|
|
72
723
|
|
|
73
724
|
// src/utils/file-helpers.ts
|
|
725
|
+
init_logger();
|
|
74
726
|
async function renderTemplate(templatePath, data) {
|
|
75
727
|
const template = await fs.readFile(templatePath, "utf-8");
|
|
76
728
|
return ejs.render(template, data);
|
|
@@ -156,8 +808,10 @@ function getProjectRoot() {
|
|
|
156
808
|
}
|
|
157
809
|
function getThemesDir() {
|
|
158
810
|
const root = getProjectRoot();
|
|
159
|
-
if (fs.existsSync(path.join(root, "themes")))
|
|
160
|
-
|
|
811
|
+
if (fs.existsSync(path.join(root, "themes")))
|
|
812
|
+
return path.join(root, "themes");
|
|
813
|
+
if (fs.existsSync(path.join(root, "src/themes")))
|
|
814
|
+
return path.join(root, "src/themes");
|
|
161
815
|
return path.dirname(root);
|
|
162
816
|
}
|
|
163
817
|
function getFeaturesDir() {
|
|
@@ -215,6 +869,9 @@ async function installDependencies(projectPath, packageManager = "npm") {
|
|
|
215
869
|
});
|
|
216
870
|
}
|
|
217
871
|
|
|
872
|
+
// src/commands/init.ts
|
|
873
|
+
init_logger();
|
|
874
|
+
|
|
218
875
|
// src/utils/validators.ts
|
|
219
876
|
function validateName(name) {
|
|
220
877
|
return /^[a-z0-9]+(-[a-z0-9]+)*$/.test(name);
|
|
@@ -656,6 +1313,9 @@ export const homePageConfig: PageConfig = {
|
|
|
656
1313
|
};
|
|
657
1314
|
`;
|
|
658
1315
|
}
|
|
1316
|
+
|
|
1317
|
+
// src/commands/create-section.ts
|
|
1318
|
+
init_logger();
|
|
659
1319
|
async function createSectionCommand(name, options) {
|
|
660
1320
|
logger.header("Create New Section");
|
|
661
1321
|
ensureOneXProject();
|
|
@@ -895,6 +1555,9 @@ export { ${data.sectionName}Schema } from "./${data.sectionName}.schema";
|
|
|
895
1555
|
${hasTemplate ? `export { ${data.sectionNamePascal}Default } from "./${data.sectionName}-default";` : ""}
|
|
896
1556
|
`;
|
|
897
1557
|
}
|
|
1558
|
+
|
|
1559
|
+
// src/commands/create-block.ts
|
|
1560
|
+
init_logger();
|
|
898
1561
|
async function createBlockCommand(name, options) {
|
|
899
1562
|
logger.header("Create New Block");
|
|
900
1563
|
ensureOneXProject();
|
|
@@ -1119,6 +1782,9 @@ export { ${data.blockName}Definition } from "./${data.blockName}.schema";
|
|
|
1119
1782
|
export { ${data.blockNamePascal} } from "./${data.blockName}";
|
|
1120
1783
|
`;
|
|
1121
1784
|
}
|
|
1785
|
+
|
|
1786
|
+
// src/commands/create-component.ts
|
|
1787
|
+
init_logger();
|
|
1122
1788
|
async function createComponentCommand(name, options) {
|
|
1123
1789
|
logger.header("Create New Component");
|
|
1124
1790
|
ensureOneXProject();
|
|
@@ -1323,6 +1989,9 @@ export { ${data.componentName}Definition } from "./${data.componentName}.schema"
|
|
|
1323
1989
|
export { ${data.componentNamePascal} } from "./${data.componentName}";
|
|
1324
1990
|
`;
|
|
1325
1991
|
}
|
|
1992
|
+
|
|
1993
|
+
// src/commands/list.ts
|
|
1994
|
+
init_logger();
|
|
1326
1995
|
async function listCommand(options) {
|
|
1327
1996
|
logger.header("OneX Project Inventory");
|
|
1328
1997
|
ensureOneXProject();
|
|
@@ -1454,6 +2123,9 @@ async function listThemesInfo() {
|
|
|
1454
2123
|
}
|
|
1455
2124
|
logger.newLine();
|
|
1456
2125
|
}
|
|
2126
|
+
|
|
2127
|
+
// src/commands/validate.ts
|
|
2128
|
+
init_logger();
|
|
1457
2129
|
async function validateCommand(options) {
|
|
1458
2130
|
logger.header("Validate Theme");
|
|
1459
2131
|
ensureOneXProject();
|
|
@@ -1466,7 +2138,11 @@ async function validateCommand(options) {
|
|
|
1466
2138
|
}
|
|
1467
2139
|
themeToValidate = options.theme;
|
|
1468
2140
|
} else {
|
|
1469
|
-
const isThemeDir = [
|
|
2141
|
+
const isThemeDir = [
|
|
2142
|
+
"theme.config.ts",
|
|
2143
|
+
"bundle-entry.ts",
|
|
2144
|
+
"manifest.ts"
|
|
2145
|
+
].some((f) => fs.existsSync(path.join(process.cwd(), f)));
|
|
1470
2146
|
if (isThemeDir) {
|
|
1471
2147
|
themeToValidate = path.basename(process.cwd());
|
|
1472
2148
|
logger.info(`Validating current theme: ${themeToValidate}`);
|
|
@@ -1479,15 +2155,21 @@ async function validateCommand(options) {
|
|
|
1479
2155
|
}
|
|
1480
2156
|
const themePath = path.join(getThemesDir(), themeToValidate);
|
|
1481
2157
|
logger.startSpinner("Running validation checks...");
|
|
1482
|
-
const
|
|
1483
|
-
|
|
2158
|
+
const entryFiles = ["manifest.ts", "theme.config.ts", "bundle-entry.ts"];
|
|
2159
|
+
const foundEntry = entryFiles.find(
|
|
2160
|
+
(f) => fs.existsSync(path.join(themePath, f))
|
|
2161
|
+
);
|
|
2162
|
+
if (!foundEntry) {
|
|
1484
2163
|
issues.push({
|
|
1485
2164
|
type: "error",
|
|
1486
|
-
file: "manifest.ts",
|
|
1487
|
-
message: "
|
|
2165
|
+
file: "manifest.ts / theme.config.ts / bundle-entry.ts",
|
|
2166
|
+
message: "No theme entry file found (need at least one of: manifest.ts, theme.config.ts, bundle-entry.ts)"
|
|
1488
2167
|
});
|
|
1489
|
-
} else {
|
|
1490
|
-
const manifestContent = fs.readFileSync(
|
|
2168
|
+
} else if (foundEntry === "manifest.ts") {
|
|
2169
|
+
const manifestContent = fs.readFileSync(
|
|
2170
|
+
path.join(themePath, foundEntry),
|
|
2171
|
+
"utf-8"
|
|
2172
|
+
);
|
|
1491
2173
|
if (!manifestContent.includes("export const") && !manifestContent.includes("export default") && !manifestContent.includes("export interface")) {
|
|
1492
2174
|
issues.push({
|
|
1493
2175
|
type: "error",
|
|
@@ -1623,7 +2305,11 @@ async function validateCommand(options) {
|
|
|
1623
2305
|
}
|
|
1624
2306
|
}
|
|
1625
2307
|
}
|
|
2308
|
+
|
|
2309
|
+
// src/commands/build.ts
|
|
2310
|
+
init_logger();
|
|
1626
2311
|
async function buildCommand(options) {
|
|
2312
|
+
var _a;
|
|
1627
2313
|
logger.header("Build Theme");
|
|
1628
2314
|
let themePath;
|
|
1629
2315
|
let themeName;
|
|
@@ -1644,7 +2330,11 @@ async function buildCommand(options) {
|
|
|
1644
2330
|
process.exit(1);
|
|
1645
2331
|
}
|
|
1646
2332
|
} else {
|
|
1647
|
-
const isThemeDir = [
|
|
2333
|
+
const isThemeDir = [
|
|
2334
|
+
"theme.config.ts",
|
|
2335
|
+
"bundle-entry.ts",
|
|
2336
|
+
"manifest.ts"
|
|
2337
|
+
].some((f) => fs.existsSync(path.join(process.cwd(), f)));
|
|
1648
2338
|
if (isThemeDir) {
|
|
1649
2339
|
themePath = process.cwd();
|
|
1650
2340
|
themeName = path.basename(themePath);
|
|
@@ -1688,11 +2378,20 @@ async function buildCommand(options) {
|
|
|
1688
2378
|
process.exit(1);
|
|
1689
2379
|
}
|
|
1690
2380
|
logger.stopSpinner(true, "Lint passed");
|
|
1691
|
-
const
|
|
2381
|
+
const pkgJson = fs.readJsonSync(packageJsonPath);
|
|
2382
|
+
const buildScript = ((_a = pkgJson.scripts) == null ? void 0 : _a.build) || "";
|
|
2383
|
+
const isRecursive = buildScript.includes("onex build") || buildScript.includes("onex-cli build");
|
|
1692
2384
|
logger.startSpinner(
|
|
1693
2385
|
options.watch ? "Building (watch mode)..." : "Building..."
|
|
1694
2386
|
);
|
|
1695
|
-
|
|
2387
|
+
let buildSuccess;
|
|
2388
|
+
if (isRecursive) {
|
|
2389
|
+
const { compileStandaloneTheme: compileStandaloneTheme2 } = await Promise.resolve().then(() => (init_compile_theme(), compile_theme_exports));
|
|
2390
|
+
buildSuccess = await compileStandaloneTheme2(themePath, themeName);
|
|
2391
|
+
} else {
|
|
2392
|
+
const buildArgs = options.watch ? ["build", "--watch"] : ["build"];
|
|
2393
|
+
buildSuccess = await runCommand("pnpm", buildArgs, themePath);
|
|
2394
|
+
}
|
|
1696
2395
|
if (!buildSuccess && !options.watch) {
|
|
1697
2396
|
logger.stopSpinner(false, "Build failed");
|
|
1698
2397
|
process.exit(1);
|
|
@@ -1733,6 +2432,9 @@ function runCommand(command, args, cwd) {
|
|
|
1733
2432
|
});
|
|
1734
2433
|
});
|
|
1735
2434
|
}
|
|
2435
|
+
|
|
2436
|
+
// src/commands/package.ts
|
|
2437
|
+
init_logger();
|
|
1736
2438
|
async function packageCommand(options) {
|
|
1737
2439
|
logger.header("Package Theme");
|
|
1738
2440
|
ensureOneXProject();
|
|
@@ -1746,7 +2448,11 @@ async function packageCommand(options) {
|
|
|
1746
2448
|
process.exit(1);
|
|
1747
2449
|
}
|
|
1748
2450
|
} else {
|
|
1749
|
-
const isThemeDir = [
|
|
2451
|
+
const isThemeDir = [
|
|
2452
|
+
"theme.config.ts",
|
|
2453
|
+
"bundle-entry.ts",
|
|
2454
|
+
"manifest.ts"
|
|
2455
|
+
].some((f) => fs.existsSync(path.join(process.cwd(), f)));
|
|
1750
2456
|
if (isThemeDir) {
|
|
1751
2457
|
themePath = process.cwd();
|
|
1752
2458
|
themeName = path.basename(themePath);
|
|
@@ -1770,10 +2476,9 @@ async function packageCommand(options) {
|
|
|
1770
2476
|
logger.newLine();
|
|
1771
2477
|
const compiledThemePath = path.join(
|
|
1772
2478
|
process.cwd(),
|
|
1773
|
-
"
|
|
1774
|
-
|
|
1775
|
-
"
|
|
1776
|
-
`${themeName}@${version}`
|
|
2479
|
+
"themes",
|
|
2480
|
+
themeName,
|
|
2481
|
+
"dist"
|
|
1777
2482
|
);
|
|
1778
2483
|
if (!options.skipBuild) {
|
|
1779
2484
|
logger.section("Step 1: Compile Theme");
|
|
@@ -1871,6 +2576,9 @@ async function createZipArchive(compiledThemePath, outputPath) {
|
|
|
1871
2576
|
archive.finalize();
|
|
1872
2577
|
});
|
|
1873
2578
|
}
|
|
2579
|
+
|
|
2580
|
+
// src/commands/deploy.ts
|
|
2581
|
+
init_logger();
|
|
1874
2582
|
async function deployCommand(options) {
|
|
1875
2583
|
logger.header("Deploy Theme");
|
|
1876
2584
|
ensureOneXProject();
|
|
@@ -1973,6 +2681,9 @@ async function deployCommand(options) {
|
|
|
1973
2681
|
process.exit(1);
|
|
1974
2682
|
}
|
|
1975
2683
|
}
|
|
2684
|
+
|
|
2685
|
+
// src/commands/upload.ts
|
|
2686
|
+
init_logger();
|
|
1976
2687
|
function getS3Client() {
|
|
1977
2688
|
const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
|
|
1978
2689
|
if (adapterMode === "vps") {
|
|
@@ -2012,21 +2723,12 @@ function getBucketName(env) {
|
|
|
2012
2723
|
return environment === "production" ? "onex-themes-prod" : "onex-themes-staging";
|
|
2013
2724
|
}
|
|
2014
2725
|
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
|
-
];
|
|
2726
|
+
const searchPaths = [path.resolve(process.cwd(), "dist")];
|
|
2026
2727
|
for (const dir of searchPaths) {
|
|
2027
2728
|
if (await fs.pathExists(dir)) {
|
|
2028
|
-
const
|
|
2029
|
-
|
|
2729
|
+
const hasManifest = await fs.pathExists(path.join(dir, "manifest.json"));
|
|
2730
|
+
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"));
|
|
2731
|
+
if (hasManifest || hasThemeEntry) {
|
|
2030
2732
|
return dir;
|
|
2031
2733
|
}
|
|
2032
2734
|
}
|
|
@@ -2129,13 +2831,8 @@ async function uploadCommand(options) {
|
|
|
2129
2831
|
`Compiled theme not found for ${themeId}@${version}. Run 'onex build' first.`
|
|
2130
2832
|
)
|
|
2131
2833
|
);
|
|
2132
|
-
logger.info(
|
|
2133
|
-
|
|
2134
|
-
`Expected locations:
|
|
2135
|
-
- ./dist/
|
|
2136
|
-
- ../../apps/api-server/compiled-themes/${themeId}@${version}/`
|
|
2137
|
-
)
|
|
2138
|
-
);
|
|
2834
|
+
logger.info(chalk4.gray(`Expected location:
|
|
2835
|
+
- ./dist/`));
|
|
2139
2836
|
process.exit(1);
|
|
2140
2837
|
}
|
|
2141
2838
|
spinner.succeed(`Found compiled theme at: ${compiledDir}`);
|
|
@@ -2242,9 +2939,7 @@ async function uploadCommand(options) {
|
|
|
2242
2939
|
);
|
|
2243
2940
|
console.log(chalk4.cyan(" Bucket: ") + chalk4.white(bucket));
|
|
2244
2941
|
console.log(
|
|
2245
|
-
chalk4.cyan(" Files: ") + chalk4.white(
|
|
2246
|
-
`bundle.zip${sourceUploaded ? " + source.zip" : ""}`
|
|
2247
|
-
)
|
|
2942
|
+
chalk4.cyan(" Files: ") + chalk4.white(`bundle.zip${sourceUploaded ? " + source.zip" : ""}`)
|
|
2248
2943
|
);
|
|
2249
2944
|
console.log(
|
|
2250
2945
|
chalk4.cyan(" Path: ") + chalk4.gray(`s3://${bucket}/themes/${themeId}/${version}/`)
|
|
@@ -2256,6 +2951,9 @@ async function uploadCommand(options) {
|
|
|
2256
2951
|
process.exit(1);
|
|
2257
2952
|
}
|
|
2258
2953
|
}
|
|
2954
|
+
|
|
2955
|
+
// src/commands/download.ts
|
|
2956
|
+
init_logger();
|
|
2259
2957
|
function getS3Client2() {
|
|
2260
2958
|
const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
|
|
2261
2959
|
if (adapterMode === "vps") {
|
|
@@ -2403,11 +3101,7 @@ function showDownloadFailureHelp(themeId, bucket) {
|
|
|
2403
3101
|
);
|
|
2404
3102
|
console.log();
|
|
2405
3103
|
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
|
-
);
|
|
3104
|
+
console.log(chalk4.gray(` aws s3 ls s3://${bucket}/themes/${themeId}/`));
|
|
2411
3105
|
console.log();
|
|
2412
3106
|
}
|
|
2413
3107
|
async function downloadCommand(options) {
|
|
@@ -2483,6 +3177,9 @@ async function downloadCommand(options) {
|
|
|
2483
3177
|
process.exit(1);
|
|
2484
3178
|
}
|
|
2485
3179
|
}
|
|
3180
|
+
|
|
3181
|
+
// src/commands/clone.ts
|
|
3182
|
+
init_logger();
|
|
2486
3183
|
function getS3Client3() {
|
|
2487
3184
|
const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
|
|
2488
3185
|
if (adapterMode === "vps") {
|
|
@@ -2586,17 +3283,100 @@ function runInstall(cwd) {
|
|
|
2586
3283
|
proc.on("error", () => resolve(false));
|
|
2587
3284
|
});
|
|
2588
3285
|
}
|
|
3286
|
+
async function promptThemeName(originalName) {
|
|
3287
|
+
const { default: inquirer5 } = await import('inquirer');
|
|
3288
|
+
const { themeName } = await inquirer5.prompt([
|
|
3289
|
+
{
|
|
3290
|
+
type: "input",
|
|
3291
|
+
name: "themeName",
|
|
3292
|
+
message: "New theme name (kebab-case):",
|
|
3293
|
+
default: `my-${originalName}`,
|
|
3294
|
+
validate: (input) => {
|
|
3295
|
+
if (!/^[a-z][a-z0-9-]*$/.test(input)) {
|
|
3296
|
+
return "Theme name must be kebab-case (lowercase letters, numbers, hyphens)";
|
|
3297
|
+
}
|
|
3298
|
+
if (input === originalName) {
|
|
3299
|
+
return `Name must differ from the original theme "${originalName}"`;
|
|
3300
|
+
}
|
|
3301
|
+
return true;
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
]);
|
|
3305
|
+
return themeName;
|
|
3306
|
+
}
|
|
3307
|
+
async function renameTheme(themeDir, oldName, newName) {
|
|
3308
|
+
const oldPrefix = `${oldName}-`;
|
|
3309
|
+
const newPrefix = `${newName}-`;
|
|
3310
|
+
const newDisplayName = newName.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
3311
|
+
const pkgPath = path.join(themeDir, "package.json");
|
|
3312
|
+
if (await fs.pathExists(pkgPath)) {
|
|
3313
|
+
const pkg = await fs.readJson(pkgPath);
|
|
3314
|
+
pkg.name = `@onex-themes/${newName}`;
|
|
3315
|
+
if (pkg.description) {
|
|
3316
|
+
pkg.description = pkg.description.replace(
|
|
3317
|
+
new RegExp(oldName, "gi"),
|
|
3318
|
+
newDisplayName
|
|
3319
|
+
);
|
|
3320
|
+
}
|
|
3321
|
+
pkg.version = "1.0.0";
|
|
3322
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
3323
|
+
}
|
|
3324
|
+
const configPath = path.join(themeDir, "theme.config.ts");
|
|
3325
|
+
if (await fs.pathExists(configPath)) {
|
|
3326
|
+
let content = await fs.readFile(configPath, "utf-8");
|
|
3327
|
+
content = content.replace(/id:\s*"[^"]*"/, `id: "${newName}"`);
|
|
3328
|
+
content = content.replace(
|
|
3329
|
+
/name:\s*"[^"]*Theme"/,
|
|
3330
|
+
`name: "${newDisplayName} Theme"`
|
|
3331
|
+
);
|
|
3332
|
+
await fs.writeFile(configPath, content);
|
|
3333
|
+
}
|
|
3334
|
+
const layoutPath = path.join(themeDir, "theme.layout.ts");
|
|
3335
|
+
if (await fs.pathExists(layoutPath)) {
|
|
3336
|
+
let content = await fs.readFile(layoutPath, "utf-8");
|
|
3337
|
+
content = content.replace(/id:\s*"[^"]*"/, `id: "${newName}"`);
|
|
3338
|
+
content = content.replace(
|
|
3339
|
+
/name:\s*"[^"]*Theme"/,
|
|
3340
|
+
`name: "${newDisplayName} Theme"`
|
|
3341
|
+
);
|
|
3342
|
+
await fs.writeFile(layoutPath, content);
|
|
3343
|
+
}
|
|
3344
|
+
const oldDisplayName = oldName.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
3345
|
+
const tsFiles = await glob("**/*.ts", { cwd: themeDir, nodir: true });
|
|
3346
|
+
for (const file of tsFiles) {
|
|
3347
|
+
const filePath = path.join(themeDir, file);
|
|
3348
|
+
let content = await fs.readFile(filePath, "utf-8");
|
|
3349
|
+
const original = content;
|
|
3350
|
+
content = content.replace(
|
|
3351
|
+
new RegExp(`"${oldPrefix}`, "g"),
|
|
3352
|
+
`"${newPrefix}`
|
|
3353
|
+
);
|
|
3354
|
+
content = content.replace(
|
|
3355
|
+
new RegExp(`themeId:\\s*"${oldName}"`, "g"),
|
|
3356
|
+
`themeId: "${newName}"`
|
|
3357
|
+
);
|
|
3358
|
+
content = content.replace(
|
|
3359
|
+
new RegExp(`${oldDisplayName} Theme`, "g"),
|
|
3360
|
+
`${newDisplayName} Theme`
|
|
3361
|
+
);
|
|
3362
|
+
if (content !== original) {
|
|
3363
|
+
await fs.writeFile(filePath, content);
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
2589
3367
|
async function cloneCommand(themeName, options) {
|
|
2590
3368
|
logger.header("Clone Theme Source");
|
|
3369
|
+
let newName = options.name;
|
|
3370
|
+
if (!newName) {
|
|
3371
|
+
newName = await promptThemeName(themeName);
|
|
3372
|
+
}
|
|
2591
3373
|
const spinner = ora("Initializing clone...").start();
|
|
2592
3374
|
try {
|
|
2593
3375
|
const bucket = options.bucket || getBucketName3(options.environment);
|
|
2594
|
-
const outputDir = options.output || path.resolve(process.cwd(),
|
|
3376
|
+
const outputDir = options.output || path.resolve(process.cwd(), newName);
|
|
2595
3377
|
const s3Client = getS3Client3();
|
|
2596
3378
|
if (await fs.pathExists(outputDir)) {
|
|
2597
|
-
spinner.fail(
|
|
2598
|
-
chalk4.red(`Directory already exists: ${outputDir}`)
|
|
2599
|
-
);
|
|
3379
|
+
spinner.fail(chalk4.red(`Directory already exists: ${outputDir}`));
|
|
2600
3380
|
logger.info(
|
|
2601
3381
|
chalk4.gray(
|
|
2602
3382
|
"Use -o to specify a different output directory, or remove the existing directory."
|
|
@@ -2610,9 +3390,7 @@ async function cloneCommand(themeName, options) {
|
|
|
2610
3390
|
version = await resolveLatestVersion2(s3Client, bucket, themeName);
|
|
2611
3391
|
spinner.succeed(`Resolved latest version: ${chalk4.cyan(version)}`);
|
|
2612
3392
|
}
|
|
2613
|
-
spinner.start(
|
|
2614
|
-
`Downloading source.zip for ${themeName}@${version}...`
|
|
2615
|
-
);
|
|
3393
|
+
spinner.start(`Downloading source.zip for ${themeName}@${version}...`);
|
|
2616
3394
|
const s3Key = `themes/${themeName}/${version}/source.zip`;
|
|
2617
3395
|
let zipBuffer;
|
|
2618
3396
|
try {
|
|
@@ -2630,9 +3408,7 @@ async function cloneCommand(themeName, options) {
|
|
|
2630
3408
|
chalk4.yellow("The theme source may not have been uploaded yet.")
|
|
2631
3409
|
);
|
|
2632
3410
|
console.log(
|
|
2633
|
-
chalk4.gray(
|
|
2634
|
-
`Upload source with: onex upload --theme ${themeName}`
|
|
2635
|
-
)
|
|
3411
|
+
chalk4.gray(`Upload source with: onex upload --theme ${themeName}`)
|
|
2636
3412
|
);
|
|
2637
3413
|
console.log();
|
|
2638
3414
|
process.exit(1);
|
|
@@ -2645,6 +3421,13 @@ async function cloneCommand(themeName, options) {
|
|
|
2645
3421
|
zip.extractAllTo(outputDir, true);
|
|
2646
3422
|
const entries = zip.getEntries().filter((e) => !e.isDirectory);
|
|
2647
3423
|
spinner.succeed(`Extracted ${entries.length} files`);
|
|
3424
|
+
spinner.start(
|
|
3425
|
+
`Renaming theme: ${chalk4.gray(themeName)} \u2192 ${chalk4.cyan(newName)}...`
|
|
3426
|
+
);
|
|
3427
|
+
await renameTheme(outputDir, themeName, newName);
|
|
3428
|
+
spinner.succeed(
|
|
3429
|
+
`Renamed theme: ${chalk4.gray(themeName)} \u2192 ${chalk4.cyan(newName)}`
|
|
3430
|
+
);
|
|
2648
3431
|
if (options.install !== false) {
|
|
2649
3432
|
const hasPkgJson = await fs.pathExists(
|
|
2650
3433
|
path.join(outputDir, "package.json")
|
|
@@ -2667,15 +3450,14 @@ async function cloneCommand(themeName, options) {
|
|
|
2667
3450
|
logger.success(chalk4.green.bold("Theme cloned successfully!"));
|
|
2668
3451
|
console.log();
|
|
2669
3452
|
console.log(
|
|
2670
|
-
chalk4.cyan("
|
|
3453
|
+
chalk4.cyan(" Source: ") + chalk4.gray(`${themeName}@${version}`)
|
|
2671
3454
|
);
|
|
3455
|
+
console.log(chalk4.cyan(" Theme: ") + chalk4.white(newName));
|
|
2672
3456
|
console.log(chalk4.cyan(" Location: ") + chalk4.white(outputDir));
|
|
2673
3457
|
console.log(chalk4.cyan(" Files: ") + chalk4.white(entries.length));
|
|
2674
3458
|
console.log();
|
|
2675
3459
|
console.log(chalk4.cyan("Next steps:"));
|
|
2676
|
-
console.log(
|
|
2677
|
-
chalk4.gray(` cd ${path.relative(process.cwd(), outputDir)}`)
|
|
2678
|
-
);
|
|
3460
|
+
console.log(chalk4.gray(` cd ${path.relative(process.cwd(), outputDir)}`));
|
|
2679
3461
|
if (options.install === false) {
|
|
2680
3462
|
console.log(chalk4.gray(" pnpm install"));
|
|
2681
3463
|
}
|
|
@@ -2688,14 +3470,253 @@ async function cloneCommand(themeName, options) {
|
|
|
2688
3470
|
}
|
|
2689
3471
|
}
|
|
2690
3472
|
|
|
3473
|
+
// src/commands/dev.ts
|
|
3474
|
+
init_logger();
|
|
3475
|
+
init_compile_theme();
|
|
3476
|
+
var MIME_TYPES = {
|
|
3477
|
+
".js": "application/javascript",
|
|
3478
|
+
".mjs": "application/javascript",
|
|
3479
|
+
".css": "text/css",
|
|
3480
|
+
".json": "application/json",
|
|
3481
|
+
".html": "text/html",
|
|
3482
|
+
".svg": "image/svg+xml",
|
|
3483
|
+
".png": "image/png",
|
|
3484
|
+
".jpg": "image/jpeg",
|
|
3485
|
+
".jpeg": "image/jpeg",
|
|
3486
|
+
".webp": "image/webp",
|
|
3487
|
+
".gif": "image/gif",
|
|
3488
|
+
".map": "application/json"
|
|
3489
|
+
};
|
|
3490
|
+
function createDevServer(options) {
|
|
3491
|
+
const clients = /* @__PURE__ */ new Set();
|
|
3492
|
+
const server = http.createServer((req, res) => {
|
|
3493
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
3494
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
|
|
3495
|
+
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
|
|
3496
|
+
if (req.method === "OPTIONS") {
|
|
3497
|
+
res.writeHead(200);
|
|
3498
|
+
res.end();
|
|
3499
|
+
return;
|
|
3500
|
+
}
|
|
3501
|
+
const url = new URL(req.url || "/", `http://localhost:${options.port}`);
|
|
3502
|
+
const pathname = url.pathname;
|
|
3503
|
+
if (pathname === "/" || pathname === "/index.html") {
|
|
3504
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
3505
|
+
res.end(generatePreviewHTML(options.themeName));
|
|
3506
|
+
return;
|
|
3507
|
+
}
|
|
3508
|
+
if (pathname === "/preview-runtime.js") {
|
|
3509
|
+
serveFile(res, options.previewRuntimePath);
|
|
3510
|
+
return;
|
|
3511
|
+
}
|
|
3512
|
+
const filePath = path.join(options.distDir, pathname);
|
|
3513
|
+
if (!filePath.startsWith(options.distDir)) {
|
|
3514
|
+
res.writeHead(403);
|
|
3515
|
+
res.end("Forbidden");
|
|
3516
|
+
return;
|
|
3517
|
+
}
|
|
3518
|
+
serveFile(res, filePath);
|
|
3519
|
+
});
|
|
3520
|
+
const wss = new WebSocketServer({ server });
|
|
3521
|
+
wss.on("connection", (ws) => {
|
|
3522
|
+
clients.add(ws);
|
|
3523
|
+
ws.on("close", () => clients.delete(ws));
|
|
3524
|
+
});
|
|
3525
|
+
server.listen(options.port);
|
|
3526
|
+
return {
|
|
3527
|
+
broadcast(message) {
|
|
3528
|
+
const data = JSON.stringify(message);
|
|
3529
|
+
for (const client of clients) {
|
|
3530
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
3531
|
+
client.send(data);
|
|
3532
|
+
}
|
|
3533
|
+
}
|
|
3534
|
+
},
|
|
3535
|
+
close() {
|
|
3536
|
+
wss.close();
|
|
3537
|
+
server.close();
|
|
3538
|
+
}
|
|
3539
|
+
};
|
|
3540
|
+
}
|
|
3541
|
+
function serveFile(res, filePath) {
|
|
3542
|
+
try {
|
|
3543
|
+
if (!fs2.existsSync(filePath)) {
|
|
3544
|
+
res.writeHead(404);
|
|
3545
|
+
res.end("Not Found");
|
|
3546
|
+
return;
|
|
3547
|
+
}
|
|
3548
|
+
const ext = path.extname(filePath);
|
|
3549
|
+
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
3550
|
+
const content = fs2.readFileSync(filePath);
|
|
3551
|
+
res.writeHead(200, { "Content-Type": contentType });
|
|
3552
|
+
res.end(content);
|
|
3553
|
+
} catch (e) {
|
|
3554
|
+
res.writeHead(500);
|
|
3555
|
+
res.end("Internal Server Error");
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
function generatePreviewHTML(themeName, port) {
|
|
3559
|
+
return `<!DOCTYPE html>
|
|
3560
|
+
<html lang="en">
|
|
3561
|
+
<head>
|
|
3562
|
+
<meta charset="UTF-8">
|
|
3563
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3564
|
+
<title>OneX Dev \u2014 ${themeName}</title>
|
|
3565
|
+
<!-- Tailwind CSS Play CDN \u2014 JIT compilation in browser for dev preview -->
|
|
3566
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
3567
|
+
<style>
|
|
3568
|
+
#onex-dev-toolbar {
|
|
3569
|
+
position: fixed; top: 0; left: 0; right: 0; z-index: 9999;
|
|
3570
|
+
height: 40px; background: #1a1a2e; color: #e0e0e0;
|
|
3571
|
+
display: flex; align-items: center; padding: 0 16px; gap: 16px;
|
|
3572
|
+
font-family: system-ui; font-size: 13px;
|
|
3573
|
+
border-bottom: 2px solid #16213e;
|
|
3574
|
+
}
|
|
3575
|
+
#onex-dev-toolbar .status { width: 8px; height: 8px; border-radius: 50%; }
|
|
3576
|
+
#onex-dev-toolbar .status.connected { background: #00ff88; }
|
|
3577
|
+
#onex-dev-toolbar .status.disconnected { background: #ff4444; }
|
|
3578
|
+
#onex-dev-toolbar .status.rebuilding { background: #ffaa00; animation: pulse 0.5s infinite; }
|
|
3579
|
+
@keyframes pulse { 50% { opacity: 0.5; } }
|
|
3580
|
+
#onex-preview-root { margin-top: 40px; }
|
|
3581
|
+
</style>
|
|
3582
|
+
</head>
|
|
3583
|
+
<body>
|
|
3584
|
+
<div id="onex-dev-toolbar">
|
|
3585
|
+
<span style="font-weight:600;">OneX Dev</span>
|
|
3586
|
+
<span style="color:#888;">|</span>
|
|
3587
|
+
<span>${themeName}</span>
|
|
3588
|
+
<span style="color:#888;">|</span>
|
|
3589
|
+
<span id="page-indicator">Home</span>
|
|
3590
|
+
<span style="color:#888;">|</span>
|
|
3591
|
+
<span class="status connected" id="ws-status"></span>
|
|
3592
|
+
<span id="ws-label">Connected</span>
|
|
3593
|
+
</div>
|
|
3594
|
+
<div id="onex-preview-root"></div>
|
|
3595
|
+
<script type="module" src="/preview-runtime.js"></script>
|
|
3596
|
+
</body>
|
|
3597
|
+
</html>`;
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3600
|
+
// src/commands/dev.ts
|
|
3601
|
+
async function devCommand(options) {
|
|
3602
|
+
logger.header("OneX Dev Server");
|
|
3603
|
+
let themePath;
|
|
3604
|
+
let themeName;
|
|
3605
|
+
if (options.theme) {
|
|
3606
|
+
themeName = options.theme;
|
|
3607
|
+
try {
|
|
3608
|
+
const workspaceThemePath = path.join(getThemesDir(), themeName);
|
|
3609
|
+
if (fs.existsSync(workspaceThemePath)) {
|
|
3610
|
+
themePath = workspaceThemePath;
|
|
3611
|
+
} else {
|
|
3612
|
+
themePath = path.join(process.cwd(), themeName);
|
|
3613
|
+
}
|
|
3614
|
+
} catch (e) {
|
|
3615
|
+
themePath = path.join(process.cwd(), themeName);
|
|
3616
|
+
}
|
|
3617
|
+
if (!fs.existsSync(themePath)) {
|
|
3618
|
+
logger.error(`Theme "${themeName}" not found.`);
|
|
3619
|
+
process.exit(1);
|
|
3620
|
+
}
|
|
3621
|
+
} else {
|
|
3622
|
+
const isThemeDir = [
|
|
3623
|
+
"theme.config.ts",
|
|
3624
|
+
"bundle-entry.ts",
|
|
3625
|
+
"manifest.ts"
|
|
3626
|
+
].some((f) => fs.existsSync(path.join(process.cwd(), f)));
|
|
3627
|
+
if (isThemeDir) {
|
|
3628
|
+
themePath = process.cwd();
|
|
3629
|
+
themeName = path.basename(themePath);
|
|
3630
|
+
} else {
|
|
3631
|
+
logger.error(
|
|
3632
|
+
"Not in a theme directory and no --theme specified. Run from theme root or use --theme flag."
|
|
3633
|
+
);
|
|
3634
|
+
process.exit(1);
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
logger.startSpinner("Compiling preview runtime...");
|
|
3638
|
+
const previewRuntimePath = await compilePreviewRuntime(themePath);
|
|
3639
|
+
logger.stopSpinner(true, "Preview runtime compiled");
|
|
3640
|
+
logger.startSpinner("Compiling theme...");
|
|
3641
|
+
const { context: context2, outputDir } = await compileStandaloneThemeDev(
|
|
3642
|
+
themePath,
|
|
3643
|
+
themeName
|
|
3644
|
+
);
|
|
3645
|
+
logger.stopSpinner(true, "Theme compiled");
|
|
3646
|
+
const port = Number(options.port) || 3456;
|
|
3647
|
+
const server = createDevServer({
|
|
3648
|
+
port,
|
|
3649
|
+
distDir: outputDir,
|
|
3650
|
+
previewRuntimePath,
|
|
3651
|
+
themeName
|
|
3652
|
+
});
|
|
3653
|
+
const watcher = chokidar.watch(
|
|
3654
|
+
[
|
|
3655
|
+
"sections",
|
|
3656
|
+
"blocks",
|
|
3657
|
+
"components",
|
|
3658
|
+
"pages",
|
|
3659
|
+
"theme.config.ts",
|
|
3660
|
+
"theme.layout.ts",
|
|
3661
|
+
"bundle-entry.ts",
|
|
3662
|
+
"sections-registry.ts"
|
|
3663
|
+
],
|
|
3664
|
+
{ cwd: themePath, ignoreInitial: true }
|
|
3665
|
+
);
|
|
3666
|
+
let debounceTimer;
|
|
3667
|
+
watcher.on("all", (_event, filePath) => {
|
|
3668
|
+
clearTimeout(debounceTimer);
|
|
3669
|
+
debounceTimer = setTimeout(async () => {
|
|
3670
|
+
logger.info(`File changed: ${filePath}`);
|
|
3671
|
+
try {
|
|
3672
|
+
await context2.rebuild();
|
|
3673
|
+
await generateManifest2(themeName, themePath, outputDir);
|
|
3674
|
+
server.broadcast({ type: "reload", timestamp: Date.now() });
|
|
3675
|
+
logger.success("Rebuilt successfully");
|
|
3676
|
+
} catch (error) {
|
|
3677
|
+
server.broadcast({ type: "error", message: String(error) });
|
|
3678
|
+
logger.error(`Rebuild failed: ${error}`);
|
|
3679
|
+
}
|
|
3680
|
+
}, 150);
|
|
3681
|
+
});
|
|
3682
|
+
logger.newLine();
|
|
3683
|
+
logger.success(`Theme compiled: ${themeName}`);
|
|
3684
|
+
logger.info(`Preview: http://localhost:${port}`);
|
|
3685
|
+
logger.info("Watching for changes...");
|
|
3686
|
+
logger.newLine();
|
|
3687
|
+
if (options.open !== false) {
|
|
3688
|
+
const open = await import('open');
|
|
3689
|
+
open.default(`http://localhost:${port}`);
|
|
3690
|
+
}
|
|
3691
|
+
process.on("SIGINT", async () => {
|
|
3692
|
+
logger.newLine();
|
|
3693
|
+
logger.info("Shutting down...");
|
|
3694
|
+
watcher.close();
|
|
3695
|
+
await context2.dispose();
|
|
3696
|
+
server.close();
|
|
3697
|
+
const shimPath = path.join(outputDir, ".process-shim.js");
|
|
3698
|
+
try {
|
|
3699
|
+
await fs7.unlink(shimPath);
|
|
3700
|
+
} catch (e) {
|
|
3701
|
+
}
|
|
3702
|
+
process.exit(0);
|
|
3703
|
+
});
|
|
3704
|
+
}
|
|
3705
|
+
|
|
2691
3706
|
// src/cli.ts
|
|
2692
3707
|
try {
|
|
2693
3708
|
const projectRoot = getProjectRoot();
|
|
2694
|
-
dotenv.config({
|
|
3709
|
+
dotenv.config({
|
|
3710
|
+
path: path.join(projectRoot, ".env.local"),
|
|
3711
|
+
quiet: true
|
|
3712
|
+
});
|
|
2695
3713
|
dotenv.config({ path: path.join(projectRoot, ".env"), quiet: true });
|
|
2696
3714
|
} catch (e) {
|
|
2697
3715
|
}
|
|
2698
|
-
dotenv.config({
|
|
3716
|
+
dotenv.config({
|
|
3717
|
+
path: path.join(os.homedir(), ".onex", ".env"),
|
|
3718
|
+
quiet: true
|
|
3719
|
+
});
|
|
2699
3720
|
var program = new Command();
|
|
2700
3721
|
program.name("onex").description("CLI tool for OneX theme development").version("0.1.0");
|
|
2701
3722
|
program.command("init").description("Create a new OneX theme project").argument("[project-name]", "Name of the project").option(
|
|
@@ -2717,6 +3738,7 @@ program.command("create:block").alias("cb").description("Create a new block").ar
|
|
|
2717
3738
|
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
3739
|
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
3740
|
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);
|
|
3741
|
+
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
3742
|
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
3743
|
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
3744
|
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 +3763,7 @@ program.command("clone").description("Clone theme source code from S3").argument
|
|
|
2741
3763
|
"-v, --version <version>",
|
|
2742
3764
|
"Theme version (default: latest)",
|
|
2743
3765
|
"latest"
|
|
2744
|
-
).option("-o, --output <dir>", "Output directory").option("-b, --bucket <name>", "S3 bucket name").option(
|
|
3766
|
+
).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
3767
|
"-e, --environment <env>",
|
|
2746
3768
|
"Environment (staging|production)",
|
|
2747
3769
|
"staging"
|