@onexapis/cli 1.0.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.
Files changed (33) hide show
  1. package/README.md +332 -0
  2. package/bin/onex.js +4 -0
  3. package/dist/cli.d.mts +1 -0
  4. package/dist/cli.d.ts +1 -0
  5. package/dist/cli.js +2507 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/cli.mjs +2492 -0
  8. package/dist/cli.mjs.map +1 -0
  9. package/dist/index.d.mts +167 -0
  10. package/dist/index.d.ts +167 -0
  11. package/dist/index.js +2104 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/index.mjs +2063 -0
  14. package/dist/index.mjs.map +1 -0
  15. package/package.json +80 -0
  16. package/templates/default/README.md.ejs +129 -0
  17. package/templates/default/esbuild.config.js +81 -0
  18. package/templates/default/package.json.ejs +30 -0
  19. package/templates/default/src/config.ts.ejs +98 -0
  20. package/templates/default/src/index.ts.ejs +11 -0
  21. package/templates/default/src/layout.ts +23 -0
  22. package/templates/default/src/manifest.ts.ejs +47 -0
  23. package/templates/default/src/pages/home.ts.ejs +37 -0
  24. package/templates/default/src/sections/footer/footer-default.tsx +28 -0
  25. package/templates/default/src/sections/footer/footer.schema.ts +45 -0
  26. package/templates/default/src/sections/footer/index.ts +2 -0
  27. package/templates/default/src/sections/header/header-default.tsx +61 -0
  28. package/templates/default/src/sections/header/header.schema.ts +46 -0
  29. package/templates/default/src/sections/header/index.ts +2 -0
  30. package/templates/default/src/sections/hero/hero-default.tsx +52 -0
  31. package/templates/default/src/sections/hero/hero.schema.ts +52 -0
  32. package/templates/default/src/sections/hero/index.ts +2 -0
  33. package/templates/default/tsconfig.json +18 -0
package/dist/cli.js ADDED
@@ -0,0 +1,2507 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ var commander = require('commander');
5
+ var chalk4 = require('chalk');
6
+ var path = require('path');
7
+ var fs2 = require('fs');
8
+ var child_process = require('child_process');
9
+ var inquirer = require('inquirer');
10
+ var ora = require('ora');
11
+ var fs = require('fs-extra');
12
+ var ejs = require('ejs');
13
+ var archiver = require('archiver');
14
+ var FormData = require('form-data');
15
+ var fetch = require('node-fetch');
16
+ var clientS3 = require('@aws-sdk/client-s3');
17
+ var glob = require('glob');
18
+
19
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
20
+
21
+ var chalk4__default = /*#__PURE__*/_interopDefault(chalk4);
22
+ var path__default = /*#__PURE__*/_interopDefault(path);
23
+ var fs2__default = /*#__PURE__*/_interopDefault(fs2);
24
+ var inquirer__default = /*#__PURE__*/_interopDefault(inquirer);
25
+ var ora__default = /*#__PURE__*/_interopDefault(ora);
26
+ var fs__default = /*#__PURE__*/_interopDefault(fs);
27
+ var ejs__default = /*#__PURE__*/_interopDefault(ejs);
28
+ var archiver__default = /*#__PURE__*/_interopDefault(archiver);
29
+ var FormData__default = /*#__PURE__*/_interopDefault(FormData);
30
+ var fetch__default = /*#__PURE__*/_interopDefault(fetch);
31
+
32
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
33
+ 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);
34
+ var Logger = class {
35
+ constructor() {
36
+ this.spinner = null;
37
+ }
38
+ success(message) {
39
+ console.log(chalk4__default.default.green("\u2713"), message);
40
+ }
41
+ error(message) {
42
+ console.log(chalk4__default.default.red("\u2717"), message);
43
+ }
44
+ warning(message) {
45
+ console.log(chalk4__default.default.yellow("\u26A0"), message);
46
+ }
47
+ info(message) {
48
+ console.log(chalk4__default.default.blue("\u2139"), message);
49
+ }
50
+ log(message) {
51
+ console.log(message);
52
+ }
53
+ startSpinner(message) {
54
+ this.spinner = ora__default.default(message).start();
55
+ }
56
+ stopSpinner(success = true, message) {
57
+ if (!this.spinner) return;
58
+ if (success) {
59
+ this.spinner.succeed(message);
60
+ } else {
61
+ this.spinner.fail(message);
62
+ }
63
+ this.spinner = null;
64
+ }
65
+ updateSpinner(message) {
66
+ if (this.spinner) {
67
+ this.spinner.text = message;
68
+ }
69
+ }
70
+ newLine() {
71
+ console.log();
72
+ }
73
+ header(message) {
74
+ console.log();
75
+ console.log(chalk4__default.default.bold.cyan(message));
76
+ console.log(chalk4__default.default.cyan("=".repeat(message.length)));
77
+ console.log();
78
+ }
79
+ section(message) {
80
+ console.log();
81
+ console.log(chalk4__default.default.bold(message));
82
+ }
83
+ };
84
+ var logger = new Logger();
85
+
86
+ // src/utils/validators.ts
87
+ function validateName(name) {
88
+ return /^[a-z0-9]+(-[a-z0-9]+)*$/.test(name);
89
+ }
90
+ function toKebabCase(str) {
91
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
92
+ }
93
+ function toPascalCase(str) {
94
+ return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
95
+ }
96
+ function validateThemeName(name) {
97
+ return /^[a-z][a-z0-9-]*$/.test(name);
98
+ }
99
+ function getValidCategories() {
100
+ return [
101
+ "headers",
102
+ "heroes",
103
+ "content",
104
+ "features",
105
+ "testimonials",
106
+ "galleries",
107
+ "cta",
108
+ "footers",
109
+ "ecommerce",
110
+ "blog",
111
+ "contact"
112
+ ];
113
+ }
114
+ async function renderTemplate(templatePath, data) {
115
+ const template = await fs__default.default.readFile(templatePath, "utf-8");
116
+ return ejs__default.default.render(template, data);
117
+ }
118
+ async function writeFile(filePath, content) {
119
+ await fs__default.default.ensureDir(path__default.default.dirname(filePath));
120
+ await fs__default.default.writeFile(filePath, content, "utf-8");
121
+ }
122
+ function getTemplatesDir() {
123
+ const locations = [
124
+ path__default.default.join(__dirname, "../../templates"),
125
+ // Development
126
+ path__default.default.join(__dirname, "../templates"),
127
+ // Production (dist/)
128
+ path__default.default.join(process.cwd(), "templates"),
129
+ // Fallback
130
+ path__default.default.join(process.cwd(), "packages/cli/templates")
131
+ // Monorepo
132
+ ];
133
+ for (const location of locations) {
134
+ if (fs__default.default.existsSync(location)) {
135
+ return location;
136
+ }
137
+ }
138
+ throw new Error("Templates directory not found");
139
+ }
140
+ async function copyTemplate(templateName, targetDir, data) {
141
+ const templatesDir = getTemplatesDir();
142
+ const templateDir = path__default.default.join(templatesDir, templateName);
143
+ if (!fs__default.default.existsSync(templateDir)) {
144
+ throw new Error(
145
+ `Template "${templateName}" not found at ${templateDir}. Available templates: ${fs__default.default.readdirSync(templatesDir).join(", ")}`
146
+ );
147
+ }
148
+ await fs__default.default.ensureDir(targetDir);
149
+ const files = await fs__default.default.readdir(templateDir);
150
+ for (const file of files) {
151
+ const templatePath = path__default.default.join(templateDir, file);
152
+ const targetPath = path__default.default.join(targetDir, file);
153
+ const stat = await fs__default.default.stat(templatePath);
154
+ if (stat.isDirectory()) {
155
+ await copyTemplateDir(templatePath, targetPath, data);
156
+ } else if (file.endsWith(".ejs")) {
157
+ const content = await renderTemplate(templatePath, data);
158
+ const outputPath = targetPath.replace(/\.ejs$/, "");
159
+ await writeFile(outputPath, content);
160
+ } else {
161
+ await fs__default.default.copy(templatePath, targetPath);
162
+ }
163
+ }
164
+ }
165
+ async function copyTemplateDir(templateDir, targetDir, data) {
166
+ await fs__default.default.ensureDir(targetDir);
167
+ const files = await fs__default.default.readdir(templateDir);
168
+ for (const file of files) {
169
+ const templatePath = path__default.default.join(templateDir, file);
170
+ const targetPath = path__default.default.join(targetDir, file);
171
+ const stat = await fs__default.default.stat(templatePath);
172
+ if (stat.isDirectory()) {
173
+ await copyTemplateDir(templatePath, targetPath, data);
174
+ } else if (file.endsWith(".ejs")) {
175
+ const content = await renderTemplate(templatePath, data);
176
+ const outputPath = targetPath.replace(/\.ejs$/, "");
177
+ await writeFile(outputPath, content);
178
+ } else {
179
+ await fs__default.default.copy(templatePath, targetPath);
180
+ }
181
+ }
182
+ }
183
+ function getProjectRoot() {
184
+ let currentDir = process.cwd();
185
+ while (currentDir !== path__default.default.parse(currentDir).root) {
186
+ const packageJsonPath = path__default.default.join(currentDir, "package.json");
187
+ if (fs__default.default.existsSync(packageJsonPath)) {
188
+ const packageJson = fs__default.default.readJsonSync(packageJsonPath);
189
+ if (packageJson.workspaces || fs__default.default.existsSync(path__default.default.join(currentDir, "src/themes")) || fs__default.default.existsSync(path__default.default.join(currentDir, "themes"))) {
190
+ return currentDir;
191
+ }
192
+ }
193
+ currentDir = path__default.default.dirname(currentDir);
194
+ }
195
+ return process.cwd();
196
+ }
197
+ function getThemesDir() {
198
+ try {
199
+ return path__default.default.join(getProjectRoot(), "src/themes");
200
+ } catch (e) {
201
+ return path__default.default.join(getProjectRoot(), "themes");
202
+ }
203
+ }
204
+ function getFeaturesDir() {
205
+ return path__default.default.join(getProjectRoot(), "src/features");
206
+ }
207
+ function isOneXProject() {
208
+ const root = getProjectRoot();
209
+ return fs__default.default.existsSync(path__default.default.join(root, "src/themes")) && fs__default.default.existsSync(path__default.default.join(root, "src/lib/registry"));
210
+ }
211
+ function ensureOneXProject() {
212
+ if (!isOneXProject()) {
213
+ logger.error(
214
+ "Not in a OneX project. Please run this command from a OneX project root."
215
+ );
216
+ process.exit(1);
217
+ }
218
+ }
219
+ function listThemes() {
220
+ const themesDir = getThemesDir();
221
+ if (!fs__default.default.existsSync(themesDir)) {
222
+ return [];
223
+ }
224
+ return fs__default.default.readdirSync(themesDir).filter((name) => {
225
+ const themePath = path__default.default.join(themesDir, name);
226
+ return fs__default.default.statSync(themePath).isDirectory() && fs__default.default.existsSync(path__default.default.join(themePath, "manifest.ts"));
227
+ });
228
+ }
229
+ function themeExists(themeName) {
230
+ const themePath = path__default.default.join(getThemesDir(), themeName);
231
+ return fs__default.default.existsSync(themePath) && fs__default.default.existsSync(path__default.default.join(themePath, "manifest.ts"));
232
+ }
233
+ function detectPackageManager() {
234
+ const userAgent = process.env.npm_config_user_agent || "";
235
+ if (userAgent.includes("pnpm")) return "pnpm";
236
+ if (userAgent.includes("yarn")) return "yarn";
237
+ if (userAgent.includes("bun")) return "bun";
238
+ const cwd = process.cwd();
239
+ if (fs__default.default.existsSync(path__default.default.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
240
+ if (fs__default.default.existsSync(path__default.default.join(cwd, "yarn.lock"))) return "yarn";
241
+ if (fs__default.default.existsSync(path__default.default.join(cwd, "bun.lockb"))) return "bun";
242
+ return "npm";
243
+ }
244
+ async function installDependencies(projectPath, packageManager = "npm") {
245
+ return new Promise((resolve, reject) => {
246
+ try {
247
+ const installCmd = packageManager === "yarn" ? "yarn" : `${packageManager} install`;
248
+ child_process.execSync(installCmd, {
249
+ cwd: projectPath,
250
+ stdio: "inherit"
251
+ });
252
+ resolve();
253
+ } catch (error) {
254
+ reject(error);
255
+ }
256
+ });
257
+ }
258
+
259
+ // src/commands/init.ts
260
+ async function initCommand(projectName, options = {}) {
261
+ logger.header("Create New OneX Theme Project");
262
+ let name;
263
+ if (!projectName) {
264
+ const { inputName } = await inquirer__default.default.prompt([
265
+ {
266
+ type: "input",
267
+ name: "inputName",
268
+ message: "Project name (kebab-case):",
269
+ validate: (input) => {
270
+ if (!input) return "Project name is required";
271
+ const kebabName = toKebabCase(input);
272
+ if (!validateThemeName(kebabName)) {
273
+ return "Invalid project name. Use lowercase letters, numbers, and hyphens only.";
274
+ }
275
+ if (fs2__default.default.existsSync(path__default.default.join(process.cwd(), kebabName))) {
276
+ return `Directory "${kebabName}" already exists`;
277
+ }
278
+ return true;
279
+ }
280
+ }
281
+ ]);
282
+ name = toKebabCase(inputName);
283
+ } else {
284
+ name = toKebabCase(projectName);
285
+ }
286
+ const projectPath = path__default.default.join(process.cwd(), name);
287
+ if (fs2__default.default.existsSync(projectPath)) {
288
+ logger.error(`Directory "${name}" already exists.`);
289
+ process.exit(1);
290
+ }
291
+ let displayName;
292
+ let description;
293
+ let author;
294
+ let template;
295
+ if (options.yes) {
296
+ displayName = toPascalCase(name).replace(/([A-Z])/g, " $1").trim();
297
+ description = "A custom OneX theme";
298
+ author = "";
299
+ template = options.template || "default";
300
+ } else {
301
+ const answers = await inquirer__default.default.prompt([
302
+ {
303
+ type: "input",
304
+ name: "displayName",
305
+ message: "Display name:",
306
+ default: toPascalCase(name).replace(/([A-Z])/g, " $1").trim()
307
+ },
308
+ {
309
+ type: "input",
310
+ name: "description",
311
+ message: "Theme description:",
312
+ default: "A custom OneX theme"
313
+ },
314
+ {
315
+ type: "input",
316
+ name: "author",
317
+ message: "Author name:",
318
+ default: ""
319
+ },
320
+ {
321
+ type: "list",
322
+ name: "template",
323
+ message: "Template to use:",
324
+ choices: [
325
+ { name: "Default (basic theme structure)", value: "default" },
326
+ { name: "Minimal (bare minimum)", value: "minimal" }
327
+ ],
328
+ default: options.template || "default"
329
+ }
330
+ ]);
331
+ displayName = answers.displayName;
332
+ description = answers.description;
333
+ author = answers.author;
334
+ template = answers.template;
335
+ }
336
+ const data = {
337
+ projectName: name,
338
+ themeName: name,
339
+ themeNamePascal: toPascalCase(name),
340
+ displayName,
341
+ description,
342
+ author,
343
+ template
344
+ };
345
+ logger.startSpinner("Creating project structure...");
346
+ try {
347
+ fs2__default.default.mkdirSync(projectPath, { recursive: true });
348
+ await copyTemplate(template, projectPath, data);
349
+ const srcPath = path__default.default.join(projectPath, "src");
350
+ fs2__default.default.mkdirSync(srcPath, { recursive: true });
351
+ const manifestContent = generateManifest(data);
352
+ await writeFile(path__default.default.join(srcPath, "manifest.ts"), manifestContent);
353
+ const configContent = generateThemeConfig(data);
354
+ await writeFile(path__default.default.join(srcPath, "config.ts"), configContent);
355
+ const layoutContent = generateThemeLayout(data);
356
+ await writeFile(path__default.default.join(srcPath, "layout.ts"), layoutContent);
357
+ const indexContent = generateThemeIndex(data);
358
+ await writeFile(path__default.default.join(srcPath, "index.ts"), indexContent);
359
+ const sectionsPath = path__default.default.join(srcPath, "sections");
360
+ fs2__default.default.mkdirSync(sectionsPath, { recursive: true });
361
+ await writeFile(
362
+ path__default.default.join(sectionsPath, "README.md"),
363
+ `# ${displayName} Sections
364
+
365
+ Add your theme-specific sections here.
366
+ `
367
+ );
368
+ const blocksPath = path__default.default.join(srcPath, "blocks");
369
+ fs2__default.default.mkdirSync(blocksPath, { recursive: true });
370
+ await writeFile(
371
+ path__default.default.join(blocksPath, "README.md"),
372
+ `# ${displayName} Blocks
373
+
374
+ Add your theme-specific blocks here.
375
+ `
376
+ );
377
+ const pagesPath = path__default.default.join(srcPath, "pages");
378
+ fs2__default.default.mkdirSync(pagesPath, { recursive: true });
379
+ const homePageContent = generateHomePage(data);
380
+ await writeFile(path__default.default.join(pagesPath, "home.ts"), homePageContent);
381
+ logger.stopSpinner(true, "Project structure created!");
382
+ if (options.git) {
383
+ logger.startSpinner("Initializing git repository...");
384
+ try {
385
+ child_process.execSync("git init", { cwd: projectPath, stdio: "ignore" });
386
+ child_process.execSync("git add .", { cwd: projectPath, stdio: "ignore" });
387
+ child_process.execSync('git commit -m "Initial commit from onex init"', {
388
+ cwd: projectPath,
389
+ stdio: "ignore"
390
+ });
391
+ logger.stopSpinner(true, "Git repository initialized!");
392
+ } catch (e) {
393
+ logger.stopSpinner(false, "Failed to initialize git");
394
+ }
395
+ }
396
+ if (!options.noInstall) {
397
+ logger.newLine();
398
+ const packageManager = detectPackageManager();
399
+ logger.startSpinner(`Installing dependencies with ${packageManager}...`);
400
+ try {
401
+ await installDependencies(projectPath, packageManager);
402
+ logger.stopSpinner(true, "Dependencies installed!");
403
+ } catch (e) {
404
+ logger.stopSpinner(false, "Failed to install dependencies");
405
+ logger.info(
406
+ "You can install dependencies manually by running: cd " + name + " && npm install"
407
+ );
408
+ }
409
+ }
410
+ logger.newLine();
411
+ logger.section("Success! \u{1F389}");
412
+ logger.newLine();
413
+ logger.info(`Created OneX theme project at: ${projectPath}`);
414
+ logger.newLine();
415
+ logger.section("Next steps:");
416
+ logger.log(` cd ${name}`);
417
+ if (options.noInstall) {
418
+ logger.log(` npm install`);
419
+ }
420
+ logger.log(` npm run build # Build your theme`);
421
+ logger.log(` npm run dev # Start development mode`);
422
+ logger.newLine();
423
+ logger.section("Theme structure:");
424
+ logger.log(" src/manifest.ts - Theme manifest and exports");
425
+ logger.log(" src/config.ts - Design tokens (colors, typography, etc.)");
426
+ logger.log(" src/layout.ts - Header and footer configuration");
427
+ logger.log(" src/sections/ - Custom sections for your theme");
428
+ logger.log(" src/blocks/ - Reusable blocks");
429
+ logger.log(" src/pages/ - Page configurations");
430
+ logger.newLine();
431
+ logger.success(`Happy theming! \u{1F3A8}`);
432
+ } catch (error) {
433
+ logger.stopSpinner(false, "Failed to create project");
434
+ logger.error(
435
+ error instanceof Error ? error.message : "Unknown error occurred"
436
+ );
437
+ if (fs2__default.default.existsSync(projectPath)) {
438
+ fs2__default.default.rmSync(projectPath, { recursive: true, force: true });
439
+ }
440
+ process.exit(1);
441
+ }
442
+ }
443
+ function generateManifest(data) {
444
+ return `import type { ThemeExport } from "@duongthiu/onex-core";
445
+
446
+ /**
447
+ * ${data.displayName} Theme Manifest
448
+ * ${data.description}
449
+ */
450
+ export const manifest: ThemeExport = {
451
+ id: "${data.themeName}",
452
+ name: "${data.displayName}",
453
+ description: "${data.description}",
454
+ version: "1.0.0",
455
+ author: "${data.author}",
456
+
457
+ // Theme configuration
458
+ config: () => import("./theme.config").then((m) => m.themeConfig),
459
+
460
+ // Theme layout (header/footer sections)
461
+ layout: () => import("./theme.layout").then((m) => m.themeLayout),
462
+
463
+ // Available sections in this theme
464
+ sections: {
465
+ // Example: hero: () => import("./sections/hero").then((m) => m.heroSchema),
466
+ },
467
+
468
+ // Available blocks in this theme
469
+ blocks: {
470
+ // Example: productCard: () => import("./blocks/product-card").then((m) => m.productCardDefinition),
471
+ },
472
+
473
+ // Default pages
474
+ pages: {
475
+ home: () => import("./pages/home").then((m) => m.homePageConfig),
476
+ },
477
+
478
+ // Supported page types
479
+ supportedPageTypes: ["home", "about", "contact", "custom"],
480
+
481
+ // Preview image (optional)
482
+ preview: undefined,
483
+
484
+ // Tags for categorization (optional)
485
+ tags: ["custom"],
486
+ };
487
+
488
+ export default manifest;
489
+ `;
490
+ }
491
+ function generateThemeConfig(data) {
492
+ return `import type { ThemeConfig } from "@duongthiu/onex-core";
493
+
494
+ /**
495
+ * ${data.displayName} Theme Configuration
496
+ * Design tokens: colors, typography, spacing, etc.
497
+ */
498
+ export const themeConfig: ThemeConfig = {
499
+ // Color palette
500
+ colors: {
501
+ primary: {
502
+ 50: "#eff6ff",
503
+ 100: "#dbeafe",
504
+ 200: "#bfdbfe",
505
+ 300: "#93c5fd",
506
+ 400: "#60a5fa",
507
+ 500: "#3b82f6",
508
+ 600: "#2563eb",
509
+ 700: "#1d4ed8",
510
+ 800: "#1e40af",
511
+ 900: "#1e3a8a",
512
+ },
513
+ secondary: {
514
+ 50: "#f8fafc",
515
+ 100: "#f1f5f9",
516
+ 200: "#e2e8f0",
517
+ 300: "#cbd5e1",
518
+ 400: "#94a3b8",
519
+ 500: "#64748b",
520
+ 600: "#475569",
521
+ 700: "#334155",
522
+ 800: "#1e293b",
523
+ 900: "#0f172a",
524
+ },
525
+ accent: {
526
+ 50: "#fdf4ff",
527
+ 100: "#fae8ff",
528
+ 200: "#f5d0fe",
529
+ 300: "#f0abfc",
530
+ 400: "#e879f9",
531
+ 500: "#d946ef",
532
+ 600: "#c026d3",
533
+ 700: "#a21caf",
534
+ 800: "#86198f",
535
+ 900: "#701a75",
536
+ },
537
+ },
538
+
539
+ // Typography
540
+ typography: {
541
+ fontFamily: {
542
+ sans: ["Inter", "system-ui", "sans-serif"],
543
+ serif: ["Georgia", "serif"],
544
+ mono: ["Monaco", "monospace"],
545
+ },
546
+ fontSize: {
547
+ xs: "0.75rem",
548
+ sm: "0.875rem",
549
+ base: "1rem",
550
+ lg: "1.125rem",
551
+ xl: "1.25rem",
552
+ "2xl": "1.5rem",
553
+ "3xl": "1.875rem",
554
+ "4xl": "2.25rem",
555
+ "5xl": "3rem",
556
+ },
557
+ },
558
+
559
+ // Spacing
560
+ spacing: {
561
+ xs: "0.5rem",
562
+ sm: "1rem",
563
+ md: "1.5rem",
564
+ lg: "2rem",
565
+ xl: "3rem",
566
+ "2xl": "4rem",
567
+ "3xl": "6rem",
568
+ "4xl": "8rem",
569
+ },
570
+
571
+ // Border radius
572
+ borderRadius: {
573
+ none: "0",
574
+ sm: "0.125rem",
575
+ md: "0.375rem",
576
+ lg: "0.5rem",
577
+ xl: "0.75rem",
578
+ full: "9999px",
579
+ },
580
+
581
+ // Breakpoints
582
+ breakpoints: {
583
+ sm: "640px",
584
+ md: "768px",
585
+ lg: "1024px",
586
+ xl: "1280px",
587
+ "2xl": "1536px",
588
+ },
589
+ };
590
+ `;
591
+ }
592
+ function generateThemeLayout(data) {
593
+ return `import type { ThemeLayoutConfig } from "@duongthiu/onex-core";
594
+
595
+ /**
596
+ * ${data.themeName} Theme Layout
597
+ * Define header and footer sections
598
+ */
599
+ export const themeLayout: ThemeLayoutConfig = {
600
+ // Header section configuration
601
+ header: undefined,
602
+ // Example:
603
+ // header: {
604
+ // type: "header",
605
+ // template: "default",
606
+ // enabled: true,
607
+ // settings: {},
608
+ // },
609
+
610
+ // Footer section configuration
611
+ footer: undefined,
612
+ // Example:
613
+ // footer: {
614
+ // type: "footer",
615
+ // template: "default",
616
+ // enabled: true,
617
+ // settings: {},
618
+ // },
619
+ };
620
+ `;
621
+ }
622
+ function generateThemeIndex(data) {
623
+ return `/**
624
+ * ${data.themeNamePascal} Theme
625
+ */
626
+
627
+ export { manifest as ${data.themeName}Manifest } from "./manifest";
628
+ export { themeConfig as ${data.themeName}Config } from "./theme.config";
629
+ export { themeLayout as ${data.themeName}Layout } from "./theme.layout";
630
+ `;
631
+ }
632
+ function generateHomePage(data) {
633
+ return `import type { PageConfig } from "@duongthiu/onex-core";
634
+
635
+ /**
636
+ * Home Page Configuration
637
+ */
638
+ export const homePageConfig: PageConfig = {
639
+ type: "home",
640
+ title: "${data.displayName}",
641
+ description: "Welcome to ${data.displayName}",
642
+
643
+ // SEO metadata
644
+ seo: {
645
+ title: "${data.displayName} - Home",
646
+ description: "Welcome to ${data.displayName}",
647
+ keywords: [],
648
+ ogImage: undefined,
649
+ },
650
+
651
+ // Page sections
652
+ sections: [
653
+ // Add your sections here
654
+ // Example:
655
+ // {
656
+ // id: "hero-1",
657
+ // type: "hero",
658
+ // template: "default",
659
+ // order: 0,
660
+ // enabled: true,
661
+ // settings: {},
662
+ // components: [],
663
+ // blocks: [],
664
+ // },
665
+ ],
666
+ };
667
+ `;
668
+ }
669
+ async function createSectionCommand(name, options) {
670
+ logger.header("Create New Section");
671
+ ensureOneXProject();
672
+ const sectionName = toKebabCase(name);
673
+ if (!validateName(sectionName)) {
674
+ logger.error(
675
+ `Invalid section name: ${sectionName}. Use kebab-case (e.g., hero, featured-products)`
676
+ );
677
+ process.exit(1);
678
+ }
679
+ const answers = await inquirer__default.default.prompt([
680
+ {
681
+ type: "list",
682
+ name: "theme",
683
+ message: "Which theme should this section belong to?",
684
+ choices: listThemes(),
685
+ when: !options.theme
686
+ },
687
+ {
688
+ type: "list",
689
+ name: "category",
690
+ message: "What category is this section?",
691
+ choices: getValidCategories(),
692
+ when: !options.category
693
+ },
694
+ {
695
+ type: "input",
696
+ name: "displayName",
697
+ message: "Display name for this section:",
698
+ default: toPascalCase(sectionName).replace(/([A-Z])/g, " $1").trim()
699
+ },
700
+ {
701
+ type: "input",
702
+ name: "description",
703
+ message: "Section description:",
704
+ default: `${toPascalCase(sectionName)} section`
705
+ },
706
+ {
707
+ type: "confirm",
708
+ name: "createTemplate",
709
+ message: "Create a default template variant?",
710
+ default: true
711
+ }
712
+ ]);
713
+ const themeName = options.theme || answers.theme;
714
+ const category = options.category || answers.category;
715
+ const displayName = answers.displayName;
716
+ const description = answers.description;
717
+ const createTemplate = answers.createTemplate;
718
+ if (!themeExists(themeName)) {
719
+ logger.error(`Theme "${themeName}" does not exist.`);
720
+ process.exit(1);
721
+ }
722
+ const data = {
723
+ sectionName,
724
+ sectionNamePascal: toPascalCase(sectionName),
725
+ themeName,
726
+ category,
727
+ displayName,
728
+ description
729
+ };
730
+ logger.startSpinner("Creating section files...");
731
+ try {
732
+ const themePath = path__default.default.join(getThemesDir(), themeName);
733
+ const sectionPath = path__default.default.join(themePath, "sections", sectionName);
734
+ const schemaContent = generateSectionSchema(data);
735
+ await writeFile(
736
+ path__default.default.join(sectionPath, `${sectionName}.schema.ts`),
737
+ schemaContent
738
+ );
739
+ if (createTemplate) {
740
+ const templateContent = generateSectionTemplate(data);
741
+ await writeFile(
742
+ path__default.default.join(sectionPath, `${sectionName}-default.tsx`),
743
+ templateContent
744
+ );
745
+ }
746
+ const indexContent = generateSectionIndex(data, createTemplate);
747
+ await writeFile(path__default.default.join(sectionPath, "index.ts"), indexContent);
748
+ logger.stopSpinner(true, "Section files created successfully!");
749
+ logger.newLine();
750
+ logger.section("Next steps:");
751
+ logger.log(
752
+ ` 1. Edit schema: ${path__default.default.relative(process.cwd(), path__default.default.join(sectionPath, `${sectionName}.schema.ts`))}`
753
+ );
754
+ if (createTemplate) {
755
+ logger.log(
756
+ ` 2. Edit template: ${path__default.default.relative(process.cwd(), path__default.default.join(sectionPath, `${sectionName}-default.tsx`))}`
757
+ );
758
+ }
759
+ logger.log(
760
+ ` 3. Add to theme manifest: ${path__default.default.relative(process.cwd(), path__default.default.join(themePath, "manifest.ts"))}`
761
+ );
762
+ logger.newLine();
763
+ logger.success("Section created successfully!");
764
+ } catch (error) {
765
+ logger.stopSpinner(false, "Failed to create section");
766
+ logger.error(
767
+ error instanceof Error ? error.message : "Unknown error occurred"
768
+ );
769
+ process.exit(1);
770
+ }
771
+ }
772
+ function generateSectionSchema(data) {
773
+ return `import type { SectionSchema } from "@duongthiu/onex-core";
774
+
775
+ /**
776
+ * ${data.displayName} Section Schema
777
+ * ${data.description}
778
+ */
779
+ export const ${data.sectionName}Schema: SectionSchema = {
780
+ type: "${data.sectionName}",
781
+ name: "${data.displayName}",
782
+ description: "${data.description}",
783
+ category: "${data.category}",
784
+ icon: "layout",
785
+
786
+ // Available template variants
787
+ templates: [
788
+ {
789
+ id: "default",
790
+ name: "Default",
791
+ description: "Default ${data.sectionName} layout",
792
+ isDefault: true,
793
+ },
794
+ ],
795
+
796
+ // Section-level settings
797
+ settings: [
798
+ {
799
+ id: "backgroundColor",
800
+ type: "color",
801
+ label: "Background Color",
802
+ category: "style",
803
+ },
804
+ {
805
+ id: "padding",
806
+ type: "select",
807
+ label: "Padding",
808
+ category: "style",
809
+ options: [
810
+ { label: "None", value: "none" },
811
+ { label: "Small", value: "sm" },
812
+ { label: "Medium", value: "md" },
813
+ { label: "Large", value: "lg" },
814
+ ],
815
+ defaultValue: "md",
816
+ },
817
+ ],
818
+
819
+ // Default settings values
820
+ defaults: {
821
+ backgroundColor: "#ffffff",
822
+ padding: "md",
823
+ },
824
+
825
+ // Allowed blocks (optional)
826
+ allowedBlocks: [],
827
+
828
+ // Maximum number of instances on a page (optional)
829
+ maxInstances: undefined,
830
+ };
831
+ `;
832
+ }
833
+ function generateSectionTemplate(data) {
834
+ return `import React from "react";
835
+ import type { SectionComponentProps } from "@duongthiu/onex-core";
836
+
837
+ /**
838
+ * ${data.displayName} - Default Template
839
+ */
840
+ export function ${data.sectionNamePascal}Default({
841
+ section,
842
+ isEditing = false,
843
+ }: SectionComponentProps) {
844
+ const { settings = {} } = section;
845
+ const backgroundColor = settings.backgroundColor as string || "#ffffff";
846
+ const padding = settings.padding as string || "md";
847
+
848
+ // Map padding values to Tailwind classes
849
+ const paddingClasses = {
850
+ none: "",
851
+ sm: "py-8",
852
+ md: "py-16",
853
+ lg: "py-24",
854
+ };
855
+
856
+ return (
857
+ <section
858
+ className={\`${data.sectionName}-section \${paddingClasses[padding as keyof typeof paddingClasses] || paddingClasses.md}\`}
859
+ style={{ backgroundColor }}
860
+ data-section-id={section.id}
861
+ data-section-type={section.type}
862
+ >
863
+ <div className="container mx-auto px-4">
864
+ {/* TODO: Implement section content */}
865
+ <div className="text-center">
866
+ <h2 className="text-3xl font-bold">${data.displayName}</h2>
867
+ <p className="mt-4 text-gray-600">
868
+ Start building your section here
869
+ </p>
870
+ </div>
871
+
872
+ {/* Render blocks if any */}
873
+ {section.blocks && section.blocks.length > 0 && (
874
+ <div className="mt-8">
875
+ {/* TODO: Render blocks */}
876
+ </div>
877
+ )}
878
+
879
+ {/* Render components if any */}
880
+ {section.components && section.components.length > 0 && (
881
+ <div className="mt-8">
882
+ {/* TODO: Render components */}
883
+ </div>
884
+ )}
885
+ </div>
886
+ </section>
887
+ );
888
+ }
889
+ `;
890
+ }
891
+ function generateSectionIndex(data, hasTemplate) {
892
+ return `/**
893
+ * ${data.sectionNamePascal} Section
894
+ */
895
+
896
+ export { ${data.sectionName}Schema } from "./${data.sectionName}.schema";
897
+ ${hasTemplate ? `export { ${data.sectionNamePascal}Default } from "./${data.sectionName}-default";` : ""}
898
+ `;
899
+ }
900
+ async function createBlockCommand(name, options) {
901
+ logger.header("Create New Block");
902
+ ensureOneXProject();
903
+ const blockName = toKebabCase(name);
904
+ if (!validateName(blockName)) {
905
+ logger.error(
906
+ `Invalid block name: ${blockName}. Use kebab-case (e.g., product-card, testimonial-item)`
907
+ );
908
+ process.exit(1);
909
+ }
910
+ const answers = await inquirer__default.default.prompt([
911
+ {
912
+ type: "list",
913
+ name: "scope",
914
+ message: "Block scope:",
915
+ choices: [
916
+ { name: "Shared (available to all themes)", value: "shared" },
917
+ { name: "Theme-specific", value: "theme" }
918
+ ],
919
+ default: "shared"
920
+ },
921
+ {
922
+ type: "list",
923
+ name: "theme",
924
+ message: "Which theme?",
925
+ choices: listThemes(),
926
+ when: (answers2) => answers2.scope === "theme" && !options.theme
927
+ },
928
+ {
929
+ type: "input",
930
+ name: "displayName",
931
+ message: "Display name for this block:",
932
+ default: toPascalCase(blockName).replace(/([A-Z])/g, " $1").trim()
933
+ },
934
+ {
935
+ type: "input",
936
+ name: "description",
937
+ message: "Block description:",
938
+ default: `${toPascalCase(blockName)} block`
939
+ },
940
+ {
941
+ type: "confirm",
942
+ name: "hasComponents",
943
+ message: "Does this block contain components?",
944
+ default: true
945
+ },
946
+ {
947
+ type: "confirm",
948
+ name: "hasNestedBlocks",
949
+ message: "Does this block support nested blocks?",
950
+ default: false
951
+ }
952
+ ]);
953
+ const scope = answers.scope;
954
+ const themeName = options.theme || answers.theme;
955
+ const displayName = answers.displayName;
956
+ const description = answers.description;
957
+ const hasComponents = answers.hasComponents;
958
+ const hasNestedBlocks = answers.hasNestedBlocks;
959
+ if (scope === "theme" && !themeExists(themeName)) {
960
+ logger.error(`Theme "${themeName}" does not exist.`);
961
+ process.exit(1);
962
+ }
963
+ const data = {
964
+ blockName,
965
+ blockNamePascal: toPascalCase(blockName),
966
+ displayName,
967
+ description,
968
+ hasComponents,
969
+ hasNestedBlocks,
970
+ scope,
971
+ themeName
972
+ };
973
+ logger.startSpinner("Creating block files...");
974
+ try {
975
+ const blockPath = scope === "shared" ? path__default.default.join(getFeaturesDir(), "blocks", blockName) : path__default.default.join(getThemesDir(), themeName, "blocks", blockName);
976
+ const schemaContent = generateBlockSchema(data);
977
+ await writeFile(
978
+ path__default.default.join(blockPath, `${blockName}.schema.ts`),
979
+ schemaContent
980
+ );
981
+ const componentContent = generateBlockComponent(data);
982
+ await writeFile(path__default.default.join(blockPath, `${blockName}.tsx`), componentContent);
983
+ const indexContent = generateBlockIndex(data);
984
+ await writeFile(path__default.default.join(blockPath, "index.ts"), indexContent);
985
+ logger.stopSpinner(true, "Block files created successfully!");
986
+ logger.newLine();
987
+ logger.section("Next steps:");
988
+ logger.log(
989
+ ` 1. Edit schema: ${path__default.default.relative(process.cwd(), path__default.default.join(blockPath, `${blockName}.schema.ts`))}`
990
+ );
991
+ logger.log(
992
+ ` 2. Edit component: ${path__default.default.relative(process.cwd(), path__default.default.join(blockPath, `${blockName}.tsx`))}`
993
+ );
994
+ logger.log(
995
+ ` 3. Register in block registry: src/lib/registry/block-registry.ts`
996
+ );
997
+ logger.newLine();
998
+ logger.success("Block created successfully!");
999
+ } catch (error) {
1000
+ logger.stopSpinner(false, "Failed to create block");
1001
+ logger.error(
1002
+ error instanceof Error ? error.message : "Unknown error occurred"
1003
+ );
1004
+ process.exit(1);
1005
+ }
1006
+ }
1007
+ function generateBlockSchema(data) {
1008
+ return `import type { BlockDefinition } from "@duongthiu/onex-core";
1009
+
1010
+ /**
1011
+ * ${data.displayName} Block Schema
1012
+ * ${data.description}
1013
+ */
1014
+ export const ${data.blockName}Definition: BlockDefinition = {
1015
+ type: "${data.blockName}",
1016
+ name: "${data.displayName}",
1017
+ description: "${data.description}",
1018
+ icon: "square",
1019
+
1020
+ // Block settings
1021
+ settings: [
1022
+ {
1023
+ id: "title",
1024
+ type: "text",
1025
+ label: "Title",
1026
+ category: "content",
1027
+ },
1028
+ {
1029
+ id: "alignment",
1030
+ type: "select",
1031
+ label: "Alignment",
1032
+ category: "style",
1033
+ options: [
1034
+ { label: "Left", value: "left" },
1035
+ { label: "Center", value: "center" },
1036
+ { label: "Right", value: "right" },
1037
+ ],
1038
+ defaultValue: "left",
1039
+ },
1040
+ ],
1041
+
1042
+ // Default settings values
1043
+ defaults: {
1044
+ title: "${data.displayName}",
1045
+ alignment: "left",
1046
+ },
1047
+
1048
+ ${data.hasComponents ? `// This block can contain components
1049
+ allowsComponents: true,` : ""}
1050
+ ${data.hasNestedBlocks ? `// This block can contain nested blocks
1051
+ allowsNestedBlocks: true,` : ""}
1052
+ };
1053
+ `;
1054
+ }
1055
+ function generateBlockComponent(data) {
1056
+ return `import React from "react";
1057
+ import type { BlockComponentProps } from "@duongthiu/onex-core";
1058
+
1059
+ /**
1060
+ * ${data.displayName} Block Component
1061
+ */
1062
+ export function ${data.blockNamePascal}({
1063
+ block,
1064
+ isEditing = false,
1065
+ }: BlockComponentProps) {
1066
+ const { settings = {} } = block;
1067
+ const title = settings.title as string || "${data.displayName}";
1068
+ const alignment = settings.alignment as string || "left";
1069
+
1070
+ const alignmentClasses = {
1071
+ left: "text-left",
1072
+ center: "text-center",
1073
+ right: "text-right",
1074
+ };
1075
+
1076
+ return (
1077
+ <div
1078
+ className={\`${data.blockName}-block \${alignmentClasses[alignment as keyof typeof alignmentClasses]}\`}
1079
+ data-block-id={block.id}
1080
+ data-block-type={block.type}
1081
+ >
1082
+ {title && <h3 className="text-xl font-semibold mb-4">{title}</h3>}
1083
+
1084
+ {/* TODO: Implement block content */}
1085
+ <div className="block-content">
1086
+ <p className="text-gray-600">Block content goes here</p>
1087
+ </div>
1088
+
1089
+ ${data.hasComponents ? `{/* Render components if any */}
1090
+ {block.components && block.components.length > 0 && (
1091
+ <div className="block-components mt-4">
1092
+ {/* TODO: Render components using ComponentRenderer */}
1093
+ </div>
1094
+ )}` : ""}
1095
+
1096
+ ${data.hasNestedBlocks ? `{/* Render nested blocks if any */}
1097
+ {block.blocks && block.blocks.length > 0 && (
1098
+ <div className="nested-blocks mt-4">
1099
+ {/* TODO: Render nested blocks using BlockRenderer */}
1100
+ </div>
1101
+ )}` : ""}
1102
+ </div>
1103
+ );
1104
+ }
1105
+ `;
1106
+ }
1107
+ function generateBlockIndex(data) {
1108
+ return `/**
1109
+ * ${data.blockNamePascal} Block
1110
+ */
1111
+
1112
+ export { ${data.blockName}Definition } from "./${data.blockName}.schema";
1113
+ export { ${data.blockNamePascal} } from "./${data.blockName}";
1114
+ `;
1115
+ }
1116
+ async function createComponentCommand(name, options) {
1117
+ logger.header("Create New Component");
1118
+ ensureOneXProject();
1119
+ const componentName = toKebabCase(name);
1120
+ if (!validateName(componentName)) {
1121
+ logger.error(
1122
+ `Invalid component name: ${componentName}. Use kebab-case (e.g., button, icon-badge)`
1123
+ );
1124
+ process.exit(1);
1125
+ }
1126
+ const answers = await inquirer__default.default.prompt([
1127
+ {
1128
+ type: "list",
1129
+ name: "componentType",
1130
+ message: "Component type:",
1131
+ choices: [
1132
+ { name: "UI Component (button, badge, etc.)", value: "ui" },
1133
+ { name: "Form Component (input, select, etc.)", value: "form" },
1134
+ { name: "Layout Component (container, grid, etc.)", value: "layout" },
1135
+ {
1136
+ name: "Content Component (heading, paragraph, etc.)",
1137
+ value: "content"
1138
+ }
1139
+ ],
1140
+ when: !options.type
1141
+ },
1142
+ {
1143
+ type: "input",
1144
+ name: "displayName",
1145
+ message: "Display name for this component:",
1146
+ default: toPascalCase(componentName).replace(/([A-Z])/g, " $1").trim()
1147
+ },
1148
+ {
1149
+ type: "input",
1150
+ name: "description",
1151
+ message: "Component description:",
1152
+ default: `${toPascalCase(componentName)} component`
1153
+ }
1154
+ ]);
1155
+ const componentType = options.type || answers.componentType;
1156
+ const displayName = answers.displayName;
1157
+ const description = answers.description;
1158
+ const data = {
1159
+ componentName,
1160
+ componentNamePascal: toPascalCase(componentName),
1161
+ componentType,
1162
+ displayName,
1163
+ description
1164
+ };
1165
+ logger.startSpinner("Creating component files...");
1166
+ try {
1167
+ const componentPath = path__default.default.join(
1168
+ getFeaturesDir(),
1169
+ "components",
1170
+ componentName
1171
+ );
1172
+ const schemaContent = generateComponentSchema(data);
1173
+ await writeFile(
1174
+ path__default.default.join(componentPath, `${componentName}.schema.ts`),
1175
+ schemaContent
1176
+ );
1177
+ const componentContent = generateComponent(data);
1178
+ await writeFile(
1179
+ path__default.default.join(componentPath, `${componentName}.tsx`),
1180
+ componentContent
1181
+ );
1182
+ const indexContent = generateComponentIndex(data);
1183
+ await writeFile(path__default.default.join(componentPath, "index.ts"), indexContent);
1184
+ logger.stopSpinner(true, "Component files created successfully!");
1185
+ logger.newLine();
1186
+ logger.section("Next steps:");
1187
+ logger.log(
1188
+ ` 1. Edit schema: ${path__default.default.relative(process.cwd(), path__default.default.join(componentPath, `${componentName}.schema.ts`))}`
1189
+ );
1190
+ logger.log(
1191
+ ` 2. Edit component: ${path__default.default.relative(process.cwd(), path__default.default.join(componentPath, `${componentName}.tsx`))}`
1192
+ );
1193
+ logger.log(
1194
+ ` 3. Register in component registry: src/lib/registry/component-registry.ts`
1195
+ );
1196
+ logger.newLine();
1197
+ logger.success("Component created successfully!");
1198
+ } catch (error) {
1199
+ logger.stopSpinner(false, "Failed to create component");
1200
+ logger.error(
1201
+ error instanceof Error ? error.message : "Unknown error occurred"
1202
+ );
1203
+ process.exit(1);
1204
+ }
1205
+ }
1206
+ function generateComponentSchema(data) {
1207
+ return `import type { ComponentDefinition } from "@duongthiu/onex-core";
1208
+
1209
+ /**
1210
+ * ${data.displayName} Component Schema
1211
+ * ${data.description}
1212
+ */
1213
+ export const ${data.componentName}Definition: ComponentDefinition = {
1214
+ type: "${data.componentName}",
1215
+ name: "${data.displayName}",
1216
+ description: "${data.description}",
1217
+ category: "${data.componentType}",
1218
+ icon: "square",
1219
+
1220
+ // Content fields (what the component displays)
1221
+ contentFields: [
1222
+ {
1223
+ id: "text",
1224
+ type: "text",
1225
+ label: "Text",
1226
+ defaultValue: "${data.displayName}",
1227
+ },
1228
+ ],
1229
+
1230
+ // Style fields (how the component looks)
1231
+ styleFields: [
1232
+ {
1233
+ id: "variant",
1234
+ type: "select",
1235
+ label: "Variant",
1236
+ options: [
1237
+ { label: "Default", value: "default" },
1238
+ { label: "Primary", value: "primary" },
1239
+ { label: "Secondary", value: "secondary" },
1240
+ ],
1241
+ defaultValue: "default",
1242
+ },
1243
+ {
1244
+ id: "size",
1245
+ type: "select",
1246
+ label: "Size",
1247
+ options: [
1248
+ { label: "Small", value: "sm" },
1249
+ { label: "Medium", value: "md" },
1250
+ { label: "Large", value: "lg" },
1251
+ ],
1252
+ defaultValue: "md",
1253
+ },
1254
+ ],
1255
+
1256
+ // Default content values
1257
+ defaultContent: {
1258
+ text: "${data.displayName}",
1259
+ },
1260
+
1261
+ // Default style values
1262
+ defaultStyle: {
1263
+ variant: "default",
1264
+ size: "md",
1265
+ },
1266
+ };
1267
+ `;
1268
+ }
1269
+ function generateComponent(data) {
1270
+ return `import React from "react";
1271
+ import type { ComponentProps } from "@duongthiu/onex-core";
1272
+
1273
+ /**
1274
+ * ${data.displayName} Component
1275
+ */
1276
+ export function ${data.componentNamePascal}({
1277
+ component,
1278
+ isEditing = false,
1279
+ }: ComponentProps) {
1280
+ const { content = {}, style = {} } = component;
1281
+ const text = content.text as string || "${data.displayName}";
1282
+ const variant = style.variant as string || "default";
1283
+ const size = style.size as string || "md";
1284
+
1285
+ // Variant classes
1286
+ const variantClasses = {
1287
+ default: "bg-gray-100 text-gray-900",
1288
+ primary: "bg-blue-600 text-white",
1289
+ secondary: "bg-gray-600 text-white",
1290
+ };
1291
+
1292
+ // Size classes
1293
+ const sizeClasses = {
1294
+ sm: "text-sm px-3 py-1",
1295
+ md: "text-base px-4 py-2",
1296
+ lg: "text-lg px-6 py-3",
1297
+ };
1298
+
1299
+ return (
1300
+ <div
1301
+ className={\`${data.componentName} \${variantClasses[variant as keyof typeof variantClasses]} \${sizeClasses[size as keyof typeof sizeClasses]} inline-block rounded\`}
1302
+ data-component-id={component.id}
1303
+ data-component-type={component.type}
1304
+ >
1305
+ {text}
1306
+ </div>
1307
+ );
1308
+ }
1309
+ `;
1310
+ }
1311
+ function generateComponentIndex(data) {
1312
+ return `/**
1313
+ * ${data.componentNamePascal} Component
1314
+ */
1315
+
1316
+ export { ${data.componentName}Definition } from "./${data.componentName}.schema";
1317
+ export { ${data.componentNamePascal} } from "./${data.componentName}";
1318
+ `;
1319
+ }
1320
+ async function listCommand(options) {
1321
+ logger.header("OneX Project Inventory");
1322
+ ensureOneXProject();
1323
+ const showAll = !options.sections && !options.blocks && !options.components;
1324
+ if (showAll || options.sections) {
1325
+ await listSections(options.theme);
1326
+ }
1327
+ if (showAll || options.blocks) {
1328
+ await listBlocks(options.theme);
1329
+ }
1330
+ if (showAll || options.components) {
1331
+ await listComponents();
1332
+ }
1333
+ if (showAll) {
1334
+ await listThemesInfo();
1335
+ }
1336
+ }
1337
+ async function listSections(themeFilter) {
1338
+ logger.section("\u{1F4C4} Sections");
1339
+ const themes = themeFilter ? [themeFilter] : listThemes();
1340
+ if (themes.length === 0) {
1341
+ logger.warning("No themes found");
1342
+ return;
1343
+ }
1344
+ for (const theme of themes) {
1345
+ const sectionsDir = path__default.default.join(getThemesDir(), theme, "sections");
1346
+ if (!fs__default.default.existsSync(sectionsDir)) {
1347
+ continue;
1348
+ }
1349
+ const sections = fs__default.default.readdirSync(sectionsDir).filter((name) => {
1350
+ const sectionPath = path__default.default.join(sectionsDir, name);
1351
+ return fs__default.default.statSync(sectionPath).isDirectory() && fs__default.default.existsSync(path__default.default.join(sectionPath, "index.ts"));
1352
+ });
1353
+ if (sections.length > 0) {
1354
+ logger.log(chalk4__default.default.cyan(`
1355
+ ${theme}:`));
1356
+ sections.forEach((section) => {
1357
+ logger.log(` \u2022 ${section}`);
1358
+ });
1359
+ }
1360
+ }
1361
+ logger.newLine();
1362
+ }
1363
+ async function listBlocks(themeFilter) {
1364
+ logger.section("\u{1F9F1} Blocks");
1365
+ const sharedBlocksDir = path__default.default.join(getFeaturesDir(), "blocks");
1366
+ if (fs__default.default.existsSync(sharedBlocksDir)) {
1367
+ const sharedBlocks = fs__default.default.readdirSync(sharedBlocksDir).filter((name) => {
1368
+ const blockPath = path__default.default.join(sharedBlocksDir, name);
1369
+ return fs__default.default.statSync(blockPath).isDirectory() && fs__default.default.existsSync(path__default.default.join(blockPath, "index.ts"));
1370
+ });
1371
+ if (sharedBlocks.length > 0) {
1372
+ logger.log(chalk4__default.default.cyan("\n Shared:"));
1373
+ sharedBlocks.forEach((block) => {
1374
+ logger.log(` \u2022 ${block}`);
1375
+ });
1376
+ }
1377
+ }
1378
+ const themes = themeFilter ? [themeFilter] : listThemes();
1379
+ for (const theme of themes) {
1380
+ const blocksDir = path__default.default.join(getThemesDir(), theme, "blocks");
1381
+ if (!fs__default.default.existsSync(blocksDir)) {
1382
+ continue;
1383
+ }
1384
+ const blocks = fs__default.default.readdirSync(blocksDir).filter((name) => {
1385
+ const blockPath = path__default.default.join(blocksDir, name);
1386
+ return fs__default.default.statSync(blockPath).isDirectory() && fs__default.default.existsSync(path__default.default.join(blockPath, "index.ts"));
1387
+ });
1388
+ if (blocks.length > 0) {
1389
+ logger.log(chalk4__default.default.cyan(`
1390
+ ${theme}:`));
1391
+ blocks.forEach((block) => {
1392
+ logger.log(` \u2022 ${block}`);
1393
+ });
1394
+ }
1395
+ }
1396
+ logger.newLine();
1397
+ }
1398
+ async function listComponents() {
1399
+ logger.section("\u2699\uFE0F Components");
1400
+ const componentsDir = path__default.default.join(getFeaturesDir(), "components");
1401
+ if (!fs__default.default.existsSync(componentsDir)) {
1402
+ logger.warning("No components directory found");
1403
+ return;
1404
+ }
1405
+ const components = fs__default.default.readdirSync(componentsDir).filter((name) => {
1406
+ const componentPath = path__default.default.join(componentsDir, name);
1407
+ return fs__default.default.statSync(componentPath).isDirectory() && fs__default.default.existsSync(path__default.default.join(componentPath, "index.ts"));
1408
+ });
1409
+ if (components.length === 0) {
1410
+ logger.warning("No components found");
1411
+ return;
1412
+ }
1413
+ logger.log("");
1414
+ components.forEach((component) => {
1415
+ logger.log(` \u2022 ${component}`);
1416
+ });
1417
+ logger.newLine();
1418
+ }
1419
+ async function listThemesInfo() {
1420
+ logger.section("\u{1F3A8} Themes");
1421
+ const themes = listThemes();
1422
+ if (themes.length === 0) {
1423
+ logger.warning("No themes found");
1424
+ return;
1425
+ }
1426
+ logger.log("");
1427
+ for (const theme of themes) {
1428
+ const manifestPath = path__default.default.join(getThemesDir(), theme, "manifest.ts");
1429
+ const manifestContent = fs__default.default.readFileSync(manifestPath, "utf-8");
1430
+ const nameMatch = manifestContent.match(/name:\s*["'](.+)["']/);
1431
+ const versionMatch = manifestContent.match(/version:\s*["'](.+)["']/);
1432
+ const descMatch = manifestContent.match(/description:\s*["'](.+)["']/);
1433
+ const displayName = nameMatch ? nameMatch[1] : theme;
1434
+ const version = versionMatch ? versionMatch[1] : "unknown";
1435
+ const description = descMatch ? descMatch[1] : "";
1436
+ logger.log(chalk4__default.default.cyan(` ${displayName}`) + chalk4__default.default.gray(` (v${version})`));
1437
+ if (description) {
1438
+ logger.log(chalk4__default.default.gray(` ${description}`));
1439
+ }
1440
+ }
1441
+ logger.newLine();
1442
+ }
1443
+ async function validateCommand(options) {
1444
+ logger.header("Validate Theme");
1445
+ ensureOneXProject();
1446
+ const issues = [];
1447
+ let themeToValidate;
1448
+ if (options.theme) {
1449
+ if (!themeExists(options.theme)) {
1450
+ logger.error(`Theme "${options.theme}" not found.`);
1451
+ process.exit(1);
1452
+ }
1453
+ themeToValidate = options.theme;
1454
+ } else {
1455
+ const manifestPath2 = path__default.default.join(process.cwd(), "manifest.ts");
1456
+ if (fs__default.default.existsSync(manifestPath2)) {
1457
+ themeToValidate = path__default.default.basename(process.cwd());
1458
+ logger.info(`Validating current theme: ${themeToValidate}`);
1459
+ } else {
1460
+ logger.error(
1461
+ "Not in a theme directory and no --theme specified. Run from theme root or use --theme flag."
1462
+ );
1463
+ process.exit(1);
1464
+ }
1465
+ }
1466
+ const themePath = path__default.default.join(getThemesDir(), themeToValidate);
1467
+ logger.startSpinner("Running validation checks...");
1468
+ const manifestPath = path__default.default.join(themePath, "manifest.ts");
1469
+ if (!fs__default.default.existsSync(manifestPath)) {
1470
+ issues.push({
1471
+ type: "error",
1472
+ file: "manifest.ts",
1473
+ message: "Manifest file not found"
1474
+ });
1475
+ } else {
1476
+ const manifestContent = fs__default.default.readFileSync(manifestPath, "utf-8");
1477
+ if (!manifestContent.includes("export const") && !manifestContent.includes("export default") && !manifestContent.includes("export interface")) {
1478
+ issues.push({
1479
+ type: "error",
1480
+ file: "manifest.ts",
1481
+ message: "Must have at least one export"
1482
+ });
1483
+ }
1484
+ }
1485
+ const configPath = path__default.default.join(themePath, "theme.config.ts");
1486
+ if (!fs__default.default.existsSync(configPath)) {
1487
+ issues.push({
1488
+ type: "warning",
1489
+ file: "theme.config.ts",
1490
+ message: "Theme config file not found (recommended)"
1491
+ });
1492
+ }
1493
+ const indexPath = path__default.default.join(themePath, "index.ts");
1494
+ if (!fs__default.default.existsSync(indexPath)) {
1495
+ issues.push({
1496
+ type: "warning",
1497
+ file: "index.ts",
1498
+ message: "Index file not found (recommended)"
1499
+ });
1500
+ }
1501
+ const sectionsDir = path__default.default.join(themePath, "sections");
1502
+ if (!fs__default.default.existsSync(sectionsDir)) {
1503
+ issues.push({
1504
+ type: "warning",
1505
+ file: "sections/",
1506
+ message: "Sections directory not found"
1507
+ });
1508
+ } else {
1509
+ const sections = fs__default.default.readdirSync(sectionsDir).filter(
1510
+ (name) => fs__default.default.statSync(path__default.default.join(sectionsDir, name)).isDirectory()
1511
+ );
1512
+ for (const sectionName of sections) {
1513
+ const sectionPath = path__default.default.join(sectionsDir, sectionName);
1514
+ const schemaFile = path__default.default.join(sectionPath, `${sectionName}.schema.ts`);
1515
+ const defaultTemplate = path__default.default.join(
1516
+ sectionPath,
1517
+ `${sectionName}-default.tsx`
1518
+ );
1519
+ const indexFile = path__default.default.join(sectionPath, "index.ts");
1520
+ if (!fs__default.default.existsSync(schemaFile)) {
1521
+ issues.push({
1522
+ type: "error",
1523
+ file: `sections/${sectionName}/${sectionName}.schema.ts`,
1524
+ message: "Section schema file missing"
1525
+ });
1526
+ }
1527
+ if (!fs__default.default.existsSync(indexFile)) {
1528
+ issues.push({
1529
+ type: "error",
1530
+ file: `sections/${sectionName}/index.ts`,
1531
+ message: "Section index file missing"
1532
+ });
1533
+ }
1534
+ if (!fs__default.default.existsSync(defaultTemplate)) {
1535
+ issues.push({
1536
+ type: "warning",
1537
+ file: `sections/${sectionName}/${sectionName}-default.tsx`,
1538
+ message: "Default template not found (recommended)"
1539
+ });
1540
+ }
1541
+ }
1542
+ }
1543
+ const blocksDir = path__default.default.join(themePath, "blocks");
1544
+ if (fs__default.default.existsSync(blocksDir)) {
1545
+ const blocks = fs__default.default.readdirSync(blocksDir).filter((name) => fs__default.default.statSync(path__default.default.join(blocksDir, name)).isDirectory());
1546
+ for (const blockName of blocks) {
1547
+ const blockPath = path__default.default.join(blocksDir, blockName);
1548
+ const schemaFile = path__default.default.join(blockPath, `${blockName}.schema.ts`);
1549
+ const componentFile = path__default.default.join(blockPath, `${blockName}.tsx`);
1550
+ const indexFile = path__default.default.join(blockPath, "index.ts");
1551
+ if (!fs__default.default.existsSync(schemaFile)) {
1552
+ issues.push({
1553
+ type: "error",
1554
+ file: `blocks/${blockName}/${blockName}.schema.ts`,
1555
+ message: "Block schema file missing"
1556
+ });
1557
+ }
1558
+ if (!fs__default.default.existsSync(componentFile)) {
1559
+ issues.push({
1560
+ type: "error",
1561
+ file: `blocks/${blockName}/${blockName}.tsx`,
1562
+ message: "Block component file missing"
1563
+ });
1564
+ }
1565
+ if (!fs__default.default.existsSync(indexFile)) {
1566
+ issues.push({
1567
+ type: "error",
1568
+ file: `blocks/${blockName}/index.ts`,
1569
+ message: "Block index file missing"
1570
+ });
1571
+ }
1572
+ }
1573
+ }
1574
+ logger.stopSpinner(true, "Validation complete");
1575
+ const errors = issues.filter((i) => i.type === "error");
1576
+ const warnings = issues.filter((i) => i.type === "warning");
1577
+ logger.newLine();
1578
+ if (errors.length === 0 && warnings.length === 0) {
1579
+ logger.success("\u2713 Theme is valid!");
1580
+ logger.newLine();
1581
+ logger.info(`Theme: ${themeToValidate}`);
1582
+ logger.log(" No issues found");
1583
+ } else {
1584
+ if (errors.length > 0) {
1585
+ logger.error(`\u2717 Found ${errors.length} error(s):`);
1586
+ logger.newLine();
1587
+ errors.forEach((issue) => {
1588
+ logger.log(` ${issue.file}`);
1589
+ logger.log(` ${issue.message}`);
1590
+ });
1591
+ logger.newLine();
1592
+ }
1593
+ if (warnings.length > 0) {
1594
+ logger.warning(`\u26A0 Found ${warnings.length} warning(s):`);
1595
+ logger.newLine();
1596
+ warnings.forEach((issue) => {
1597
+ logger.log(` ${issue.file}`);
1598
+ logger.log(` ${issue.message}`);
1599
+ });
1600
+ logger.newLine();
1601
+ }
1602
+ if (errors.length > 0) {
1603
+ logger.error("Validation failed. Please fix the errors above.");
1604
+ process.exit(1);
1605
+ } else {
1606
+ logger.warning(
1607
+ "Validation passed with warnings. Consider addressing them."
1608
+ );
1609
+ }
1610
+ }
1611
+ }
1612
+ async function buildCommand(options) {
1613
+ logger.header("Build Theme");
1614
+ let themePath;
1615
+ let themeName;
1616
+ if (options.theme) {
1617
+ themeName = options.theme;
1618
+ try {
1619
+ const workspaceThemePath = path__default.default.join(getThemesDir(), themeName);
1620
+ if (fs__default.default.existsSync(workspaceThemePath)) {
1621
+ themePath = workspaceThemePath;
1622
+ } else {
1623
+ themePath = path__default.default.join(process.cwd(), themeName);
1624
+ }
1625
+ } catch (e) {
1626
+ themePath = path__default.default.join(process.cwd(), themeName);
1627
+ }
1628
+ if (!fs__default.default.existsSync(themePath)) {
1629
+ logger.error(`Theme "${themeName}" not found.`);
1630
+ process.exit(1);
1631
+ }
1632
+ } else {
1633
+ const manifestPath = path__default.default.join(process.cwd(), "manifest.ts");
1634
+ if (fs__default.default.existsSync(manifestPath)) {
1635
+ themePath = process.cwd();
1636
+ themeName = path__default.default.basename(themePath);
1637
+ logger.info(`Building current theme: ${themeName}`);
1638
+ } else {
1639
+ logger.error(
1640
+ "Not in a theme directory and no --theme specified. Run from theme root or use --theme flag."
1641
+ );
1642
+ process.exit(1);
1643
+ }
1644
+ }
1645
+ const packageJsonPath = path__default.default.join(themePath, "package.json");
1646
+ const hasPkgJson = fs__default.default.existsSync(packageJsonPath);
1647
+ if (!hasPkgJson) {
1648
+ logger.warning(
1649
+ "No package.json found in theme. Skipping build (themes in monorepo are built via turbo)."
1650
+ );
1651
+ logger.newLine();
1652
+ logger.info("To build all packages, run:");
1653
+ logger.log(" pnpm turbo build");
1654
+ logger.newLine();
1655
+ logger.info("To build specific theme components:");
1656
+ logger.log(" pnpm turbo build --filter=./src/themes/*");
1657
+ return;
1658
+ }
1659
+ logger.newLine();
1660
+ logger.section("Build Steps");
1661
+ logger.startSpinner("Running type check...");
1662
+ const typeCheckSuccess = await runCommand("pnpm", ["type-check"], themePath);
1663
+ if (!typeCheckSuccess) {
1664
+ logger.stopSpinner(false, "Type check failed");
1665
+ logger.error("Fix type errors before building.");
1666
+ process.exit(1);
1667
+ }
1668
+ logger.stopSpinner(true, "Type check passed");
1669
+ logger.startSpinner("Running linter...");
1670
+ const lintSuccess = await runCommand("pnpm", ["lint"], themePath);
1671
+ if (!lintSuccess) {
1672
+ logger.stopSpinner(false, "Lint failed");
1673
+ logger.error("Fix lint errors before building.");
1674
+ process.exit(1);
1675
+ }
1676
+ logger.stopSpinner(true, "Lint passed");
1677
+ const buildArgs = options.watch ? ["build", "--watch"] : ["build"];
1678
+ logger.startSpinner(
1679
+ options.watch ? "Building (watch mode)..." : "Building..."
1680
+ );
1681
+ const buildSuccess = await runCommand("pnpm", buildArgs, themePath);
1682
+ if (!buildSuccess && !options.watch) {
1683
+ logger.stopSpinner(false, "Build failed");
1684
+ process.exit(1);
1685
+ }
1686
+ if (!options.watch) {
1687
+ logger.stopSpinner(true, "Build complete");
1688
+ logger.newLine();
1689
+ logger.success("\u2713 Theme built successfully!");
1690
+ logger.newLine();
1691
+ logger.info(`Theme: ${themeName}`);
1692
+ const distPath = path__default.default.join(themePath, "dist");
1693
+ if (fs__default.default.existsSync(distPath)) {
1694
+ logger.log(`Output: ${path__default.default.relative(process.cwd(), distPath)}`);
1695
+ const files = fs__default.default.readdirSync(distPath);
1696
+ logger.log(`Files: ${files.length}`);
1697
+ }
1698
+ }
1699
+ }
1700
+ function runCommand(command, args, cwd) {
1701
+ return new Promise((resolve) => {
1702
+ const proc = child_process.spawn(command, args, {
1703
+ cwd,
1704
+ stdio: "pipe",
1705
+ shell: true
1706
+ });
1707
+ let hasError = false;
1708
+ proc.stderr.on("data", (data) => {
1709
+ const message = data.toString();
1710
+ if (message.includes("error") || message.includes("Error") || message.includes("ERROR")) {
1711
+ hasError = true;
1712
+ }
1713
+ });
1714
+ proc.on("close", (code) => {
1715
+ resolve(code === 0 && !hasError);
1716
+ });
1717
+ proc.on("error", () => {
1718
+ resolve(false);
1719
+ });
1720
+ });
1721
+ }
1722
+ async function packageCommand(options) {
1723
+ logger.header("Package Theme");
1724
+ ensureOneXProject();
1725
+ let themePath;
1726
+ let themeName;
1727
+ if (options.theme) {
1728
+ themeName = options.theme;
1729
+ themePath = path__default.default.join(getThemesDir(), themeName);
1730
+ if (!fs__default.default.existsSync(themePath)) {
1731
+ logger.error(`Theme "${themeName}" not found.`);
1732
+ process.exit(1);
1733
+ }
1734
+ } else {
1735
+ const manifestPath = path__default.default.join(process.cwd(), "manifest.ts");
1736
+ if (fs__default.default.existsSync(manifestPath)) {
1737
+ themePath = process.cwd();
1738
+ themeName = path__default.default.basename(themePath);
1739
+ logger.info(`Packaging current theme: ${themeName}`);
1740
+ } else {
1741
+ logger.error(
1742
+ "Not in a theme directory and no --theme specified. Run from theme root or use --theme flag."
1743
+ );
1744
+ process.exit(1);
1745
+ }
1746
+ }
1747
+ const packageJsonPath = path__default.default.join(themePath, "package.json");
1748
+ let version = "1.0.0";
1749
+ if (fs__default.default.existsSync(packageJsonPath)) {
1750
+ const packageJson = await fs__default.default.readJson(packageJsonPath);
1751
+ version = packageJson.version || "1.0.0";
1752
+ }
1753
+ logger.newLine();
1754
+ logger.info(`Theme: ${themeName}`);
1755
+ logger.info(`Version: ${version}`);
1756
+ logger.newLine();
1757
+ const compiledThemePath = path__default.default.join(
1758
+ process.cwd(),
1759
+ "apps",
1760
+ "api-server",
1761
+ "compiled-themes",
1762
+ `${themeName}@${version}`
1763
+ );
1764
+ if (!options.skipBuild) {
1765
+ logger.section("Step 1: Compile Theme");
1766
+ logger.startSpinner("Compiling theme with esbuild...");
1767
+ const compileArgs = ["tsx", "scripts/compile-theme.ts", themeName];
1768
+ if (options.minify) {
1769
+ compileArgs.push("--minify");
1770
+ }
1771
+ const compileSuccess = await runCommand2(
1772
+ compileArgs[0],
1773
+ compileArgs.slice(1)
1774
+ );
1775
+ if (!compileSuccess) {
1776
+ logger.stopSpinner(false, "Compilation failed");
1777
+ logger.error("Fix compilation errors before packaging.");
1778
+ process.exit(1);
1779
+ }
1780
+ logger.stopSpinner(true, "Theme compiled");
1781
+ } else {
1782
+ logger.info("Skipping build (--skip-build flag)");
1783
+ }
1784
+ if (!fs__default.default.existsSync(compiledThemePath)) {
1785
+ logger.error(`Compiled theme not found at: ${compiledThemePath}`);
1786
+ logger.info("Run without --skip-build to compile first.");
1787
+ process.exit(1);
1788
+ }
1789
+ logger.newLine();
1790
+ logger.section("Step 2: Create Package");
1791
+ const packageName = options.name || `${themeName}-${version}`;
1792
+ const outputDir = options.output || path__default.default.join(process.cwd(), "dist");
1793
+ const outputPath = path__default.default.join(outputDir, `${packageName}.zip`);
1794
+ await fs__default.default.ensureDir(outputDir);
1795
+ logger.startSpinner("Creating zip archive...");
1796
+ try {
1797
+ await createZipArchive(compiledThemePath, outputPath);
1798
+ logger.stopSpinner(true, "Package created");
1799
+ const stats = await fs__default.default.stat(outputPath);
1800
+ const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
1801
+ logger.newLine();
1802
+ logger.success("\u2713 Theme packaged successfully!");
1803
+ logger.newLine();
1804
+ logger.info(`Package: ${packageName}.zip`);
1805
+ logger.log(`Size: ${sizeMB} MB`);
1806
+ logger.log(`Location: ${path__default.default.relative(process.cwd(), outputPath)}`);
1807
+ logger.newLine();
1808
+ logger.section("Next steps:");
1809
+ logger.log(
1810
+ ` onex deploy --package ${path__default.default.relative(process.cwd(), outputPath)}`
1811
+ );
1812
+ } catch (error) {
1813
+ logger.stopSpinner(false, "Failed to create package");
1814
+ logger.error(
1815
+ error instanceof Error ? error.message : "Unknown error occurred"
1816
+ );
1817
+ process.exit(1);
1818
+ }
1819
+ }
1820
+ function runCommand2(command, args) {
1821
+ return new Promise((resolve) => {
1822
+ const proc = child_process.spawn(command, args, {
1823
+ cwd: process.cwd(),
1824
+ stdio: "pipe",
1825
+ shell: true
1826
+ });
1827
+ let hasError = false;
1828
+ proc.stderr.on("data", (data) => {
1829
+ const message = data.toString();
1830
+ if (message.includes("error") || message.includes("Error") || message.includes("ERROR")) {
1831
+ hasError = true;
1832
+ }
1833
+ });
1834
+ proc.on("close", (code) => {
1835
+ resolve(code === 0 && !hasError);
1836
+ });
1837
+ proc.on("error", () => {
1838
+ resolve(false);
1839
+ });
1840
+ });
1841
+ }
1842
+ async function createZipArchive(compiledThemePath, outputPath) {
1843
+ return new Promise((resolve, reject) => {
1844
+ const output = fs__default.default.createWriteStream(outputPath);
1845
+ const archive = archiver__default.default("zip", {
1846
+ zlib: { level: 9 }
1847
+ // Maximum compression
1848
+ });
1849
+ output.on("close", () => {
1850
+ resolve();
1851
+ });
1852
+ archive.on("error", (err) => {
1853
+ reject(err);
1854
+ });
1855
+ archive.pipe(output);
1856
+ archive.directory(compiledThemePath, false);
1857
+ archive.finalize();
1858
+ });
1859
+ }
1860
+ async function deployCommand(options) {
1861
+ logger.header("Deploy Theme");
1862
+ ensureOneXProject();
1863
+ let packagePath;
1864
+ if (options.package) {
1865
+ packagePath = path__default.default.resolve(options.package);
1866
+ } else if (options.theme) {
1867
+ const distDir = path__default.default.join(process.cwd(), "dist");
1868
+ if (!fs__default.default.existsSync(distDir)) {
1869
+ logger.error("No dist/ directory found. Run 'onex package' first.");
1870
+ process.exit(1);
1871
+ }
1872
+ const files = fs__default.default.readdirSync(distDir);
1873
+ const packageFiles = files.filter(
1874
+ (f) => f.startsWith(options.theme) && f.endsWith(".zip")
1875
+ );
1876
+ if (packageFiles.length === 0) {
1877
+ logger.error(`No package found for theme "${options.theme}".`);
1878
+ logger.info("Run: onex package --theme " + options.theme);
1879
+ process.exit(1);
1880
+ }
1881
+ packageFiles.sort().reverse();
1882
+ packagePath = path__default.default.join(distDir, packageFiles[0]);
1883
+ } else {
1884
+ logger.error("Either --package or --theme must be specified.");
1885
+ logger.info("Examples:");
1886
+ logger.log(" onex deploy --package dist/tinan-1.0.0.zip");
1887
+ logger.log(" onex deploy --theme tinan");
1888
+ process.exit(1);
1889
+ }
1890
+ if (!fs__default.default.existsSync(packagePath)) {
1891
+ logger.error(`Package not found: ${packagePath}`);
1892
+ process.exit(1);
1893
+ }
1894
+ const stats = await fs__default.default.stat(packagePath);
1895
+ const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
1896
+ const fileName = path__default.default.basename(packagePath);
1897
+ logger.newLine();
1898
+ logger.info(`Package: ${fileName}`);
1899
+ logger.log(`Size: ${sizeMB} MB`);
1900
+ logger.log(`Path: ${path__default.default.relative(process.cwd(), packagePath)}`);
1901
+ logger.newLine();
1902
+ const apiUrl = options.apiUrl || process.env.ONEX_API_URL || "http://localhost:3001";
1903
+ const uploadEndpoint = `${apiUrl}/api/themes/upload`;
1904
+ logger.section("Uploading to API Server");
1905
+ logger.info(`Endpoint: ${uploadEndpoint}`);
1906
+ logger.newLine();
1907
+ logger.startSpinner("Uploading theme package...");
1908
+ try {
1909
+ const formData = new FormData__default.default();
1910
+ formData.append("theme", fs__default.default.createReadStream(packagePath), {
1911
+ filename: fileName,
1912
+ contentType: "application/zip"
1913
+ });
1914
+ if (options.apiKey) {
1915
+ formData.append("apiKey", options.apiKey);
1916
+ }
1917
+ if (options.environment) {
1918
+ formData.append("environment", options.environment);
1919
+ }
1920
+ const response = await fetch__default.default(uploadEndpoint, {
1921
+ method: "POST",
1922
+ body: formData,
1923
+ headers: formData.getHeaders()
1924
+ });
1925
+ const result = await response.json();
1926
+ if (!response.ok) {
1927
+ logger.stopSpinner(false, "Upload failed");
1928
+ logger.error(
1929
+ `Server returned ${response.status}: ${response.statusText}`
1930
+ );
1931
+ if (result.error) {
1932
+ logger.log(`Error: ${result.error}`);
1933
+ }
1934
+ process.exit(1);
1935
+ }
1936
+ logger.stopSpinner(true, "Upload complete");
1937
+ logger.newLine();
1938
+ logger.success("\u2713 Theme deployed successfully!");
1939
+ logger.newLine();
1940
+ if (result.themeId) {
1941
+ logger.info(`Theme ID: ${result.themeId}`);
1942
+ }
1943
+ if (result.version) {
1944
+ logger.info(`Version: ${result.version}`);
1945
+ }
1946
+ if (result.url) {
1947
+ logger.info(`Preview URL: ${result.url}`);
1948
+ }
1949
+ logger.newLine();
1950
+ logger.log("Theme is now available on the API server!");
1951
+ } catch (error) {
1952
+ logger.stopSpinner(false, "Upload failed");
1953
+ logger.error(
1954
+ `Failed to upload: ${error instanceof Error ? error.message : String(error)}`
1955
+ );
1956
+ logger.newLine();
1957
+ logger.info("Make sure the API server is running:");
1958
+ logger.log(" cd apps/api-server && pnpm dev");
1959
+ process.exit(1);
1960
+ }
1961
+ }
1962
+ function getS3Client() {
1963
+ const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
1964
+ if (adapterMode === "vps") {
1965
+ const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
1966
+ const secure = process.env.MINIO_SECURE === "true";
1967
+ const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
1968
+ return new clientS3.S3Client({
1969
+ endpoint: endpointUrl,
1970
+ region: "us-east-1",
1971
+ // MinIO doesn't use real regions
1972
+ credentials: {
1973
+ accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
1974
+ secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
1975
+ },
1976
+ forcePathStyle: true
1977
+ // Required for MinIO
1978
+ });
1979
+ }
1980
+ if (adapterMode === "local") {
1981
+ return new clientS3.S3Client({
1982
+ endpoint: "http://localhost:4569",
1983
+ region: "ap-southeast-1",
1984
+ credentials: {
1985
+ accessKeyId: "S3RVER",
1986
+ secretAccessKey: "S3RVER"
1987
+ },
1988
+ forcePathStyle: true
1989
+ });
1990
+ }
1991
+ return new clientS3.S3Client({
1992
+ region: process.env.AWS_REGION || "ap-southeast-1"
1993
+ });
1994
+ }
1995
+ function getBucketName(env) {
1996
+ if (process.env.BUCKET_NAME) {
1997
+ return process.env.BUCKET_NAME;
1998
+ }
1999
+ const environment = env || process.env.ENVIRONMENT || "staging";
2000
+ return environment === "production" ? "onex-themes-prod" : "onex-themes-staging";
2001
+ }
2002
+ async function findCompiledThemeDir(themeId, version) {
2003
+ const searchPaths = [
2004
+ path__default.default.resolve(process.cwd(), "dist"),
2005
+ path__default.default.resolve(
2006
+ process.cwd(),
2007
+ `../../apps/api-server/compiled-themes/${themeId}@${version}`
2008
+ ),
2009
+ path__default.default.resolve(
2010
+ process.cwd(),
2011
+ `../api-server/compiled-themes/${themeId}@${version}`
2012
+ )
2013
+ ];
2014
+ for (const dir of searchPaths) {
2015
+ if (await fs__default.default.pathExists(dir)) {
2016
+ const manifestPath = path__default.default.join(dir, "manifest.json");
2017
+ if (await fs__default.default.pathExists(manifestPath)) {
2018
+ return dir;
2019
+ }
2020
+ }
2021
+ }
2022
+ return null;
2023
+ }
2024
+ async function readManifest() {
2025
+ var _a;
2026
+ const manifestTsPath = path__default.default.resolve(process.cwd(), "manifest.ts");
2027
+ if (await fs__default.default.pathExists(manifestTsPath)) {
2028
+ try {
2029
+ const module = await import(manifestTsPath);
2030
+ return module.default || module;
2031
+ } catch (error) {
2032
+ logger.warning("Failed to import manifest.ts, trying package.json");
2033
+ }
2034
+ }
2035
+ const packageJsonPath = path__default.default.resolve(process.cwd(), "package.json");
2036
+ if (await fs__default.default.pathExists(packageJsonPath)) {
2037
+ const pkg = await fs__default.default.readJson(packageJsonPath);
2038
+ return {
2039
+ themeId: ((_a = pkg.name) == null ? void 0 : _a.replace("@onex-themes/", "")) || "unknown",
2040
+ version: pkg.version || "1.0.0"
2041
+ };
2042
+ }
2043
+ throw new Error(
2044
+ "No manifest.ts or package.json found. Are you in a theme directory?"
2045
+ );
2046
+ }
2047
+ function getContentType(filename) {
2048
+ const ext = path__default.default.extname(filename).toLowerCase();
2049
+ const types = {
2050
+ ".js": "application/javascript",
2051
+ ".mjs": "application/javascript",
2052
+ ".json": "application/json",
2053
+ ".css": "text/css",
2054
+ ".png": "image/png",
2055
+ ".jpg": "image/jpeg",
2056
+ ".jpeg": "image/jpeg",
2057
+ ".svg": "image/svg+xml",
2058
+ ".woff2": "font/woff2",
2059
+ ".woff": "font/woff",
2060
+ ".ttf": "font/ttf",
2061
+ ".ts": "text/plain"
2062
+ };
2063
+ return types[ext] || "application/octet-stream";
2064
+ }
2065
+ async function uploadFile(s3Client, bucket, themeId, version, baseDir, file) {
2066
+ const filePath = path__default.default.join(baseDir, file);
2067
+ const content = await fs__default.default.readFile(filePath);
2068
+ const s3Key = `themes/${themeId}/${version}/${file}`;
2069
+ await s3Client.send(
2070
+ new clientS3.PutObjectCommand({
2071
+ Bucket: bucket,
2072
+ Key: s3Key,
2073
+ Body: content,
2074
+ ContentType: getContentType(file)
2075
+ })
2076
+ );
2077
+ }
2078
+ async function updateLatestPointer(s3Client, bucket, themeId, version) {
2079
+ const latestData = {
2080
+ version,
2081
+ uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
2082
+ };
2083
+ await s3Client.send(
2084
+ new clientS3.PutObjectCommand({
2085
+ Bucket: bucket,
2086
+ Key: `themes/${themeId}/latest.json`,
2087
+ Body: JSON.stringify(latestData, null, 2),
2088
+ ContentType: "application/json"
2089
+ })
2090
+ );
2091
+ }
2092
+ async function uploadCommand(options) {
2093
+ logger.header("Upload Theme to S3");
2094
+ ensureOneXProject();
2095
+ const spinner = ora__default.default("Preparing theme upload...").start();
2096
+ try {
2097
+ const manifest = await readManifest();
2098
+ const themeId = manifest.themeId;
2099
+ const version = options.version || manifest.version || "1.0.0";
2100
+ spinner.text = `Found theme: ${themeId}@${version}`;
2101
+ const bucket = options.bucket || getBucketName(options.environment);
2102
+ const s3Client = getS3Client();
2103
+ const compiledDir = await findCompiledThemeDir(themeId, version);
2104
+ if (!compiledDir) {
2105
+ spinner.fail(
2106
+ chalk4__default.default.red(
2107
+ `Compiled theme not found for ${themeId}@${version}. Run 'onex build' first.`
2108
+ )
2109
+ );
2110
+ logger.info(
2111
+ chalk4__default.default.gray(
2112
+ `Expected locations:
2113
+ - ./dist/
2114
+ - ../../apps/api-server/compiled-themes/${themeId}@${version}/`
2115
+ )
2116
+ );
2117
+ process.exit(1);
2118
+ }
2119
+ spinner.succeed(`Found compiled theme at: ${compiledDir}`);
2120
+ spinner.start(`Scanning files...`);
2121
+ const files = await glob.glob("**/*", {
2122
+ cwd: compiledDir,
2123
+ nodir: true,
2124
+ dot: true
2125
+ });
2126
+ if (files.length === 0) {
2127
+ spinner.fail(chalk4__default.default.red("No files found in compiled theme directory"));
2128
+ process.exit(1);
2129
+ }
2130
+ spinner.succeed(`Found ${files.length} files to upload`);
2131
+ if (options.dryRun) {
2132
+ spinner.info(chalk4__default.default.yellow("Dry run mode - no files will be uploaded"));
2133
+ console.log(chalk4__default.default.gray("\nFiles to upload:"));
2134
+ const displayFiles = files.slice(0, 10);
2135
+ displayFiles.forEach((file) => console.log(chalk4__default.default.gray(` - ${file}`)));
2136
+ if (files.length > 10) {
2137
+ console.log(chalk4__default.default.gray(` ... and ${files.length - 10} more files
2138
+ `));
2139
+ }
2140
+ console.log(chalk4__default.default.cyan(`
2141
+ S3 bucket: ${bucket}`));
2142
+ console.log(
2143
+ chalk4__default.default.cyan(`S3 path: s3://${bucket}/themes/${themeId}/${version}/
2144
+ `)
2145
+ );
2146
+ return;
2147
+ }
2148
+ spinner.start(`Uploading to S3: ${bucket}...`);
2149
+ let uploaded = 0;
2150
+ const total = files.length;
2151
+ const batchSize = 10;
2152
+ for (let i = 0; i < files.length; i += batchSize) {
2153
+ const batch = files.slice(i, i + batchSize);
2154
+ await Promise.all(
2155
+ batch.map(async (file) => {
2156
+ await uploadFile(
2157
+ s3Client,
2158
+ bucket,
2159
+ themeId,
2160
+ version,
2161
+ compiledDir,
2162
+ file
2163
+ );
2164
+ uploaded++;
2165
+ spinner.text = `Uploading... ${uploaded}/${total} files (${Math.round(uploaded / total * 100)}%)`;
2166
+ })
2167
+ );
2168
+ }
2169
+ spinner.succeed(`Uploaded ${total} files to S3`);
2170
+ spinner.start("Updating latest.json pointer...");
2171
+ await updateLatestPointer(s3Client, bucket, themeId, version);
2172
+ spinner.succeed("Updated latest.json pointer");
2173
+ console.log();
2174
+ logger.success(chalk4__default.default.green.bold("\u2705 Theme uploaded successfully!"));
2175
+ console.log();
2176
+ console.log(
2177
+ chalk4__default.default.cyan(" Theme: ") + chalk4__default.default.white(`${themeId}@${version}`)
2178
+ );
2179
+ console.log(chalk4__default.default.cyan(" Bucket: ") + chalk4__default.default.white(bucket));
2180
+ console.log(chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(total));
2181
+ console.log(
2182
+ chalk4__default.default.cyan(" Path: ") + chalk4__default.default.gray(`s3://${bucket}/themes/${themeId}/${version}/`)
2183
+ );
2184
+ console.log();
2185
+ } catch (error) {
2186
+ spinner.fail(chalk4__default.default.red(`Upload failed: ${error.message}`));
2187
+ logger.error(error.stack || error.message);
2188
+ process.exit(1);
2189
+ }
2190
+ }
2191
+ function getS3Client2() {
2192
+ const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
2193
+ if (adapterMode === "vps") {
2194
+ const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
2195
+ const secure = process.env.MINIO_SECURE === "true";
2196
+ const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
2197
+ return new clientS3.S3Client({
2198
+ endpoint: endpointUrl,
2199
+ region: "us-east-1",
2200
+ credentials: {
2201
+ accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
2202
+ secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
2203
+ },
2204
+ forcePathStyle: true
2205
+ });
2206
+ }
2207
+ if (adapterMode === "local") {
2208
+ return new clientS3.S3Client({
2209
+ endpoint: "http://localhost:4569",
2210
+ region: "ap-southeast-1",
2211
+ credentials: {
2212
+ accessKeyId: "S3RVER",
2213
+ secretAccessKey: "S3RVER"
2214
+ },
2215
+ forcePathStyle: true
2216
+ });
2217
+ }
2218
+ return new clientS3.S3Client({
2219
+ region: process.env.AWS_REGION || "ap-southeast-1"
2220
+ });
2221
+ }
2222
+ function getBucketName2(env) {
2223
+ if (process.env.BUCKET_NAME) {
2224
+ return process.env.BUCKET_NAME;
2225
+ }
2226
+ const environment = env || process.env.ENVIRONMENT || "staging";
2227
+ return environment === "production" ? "onex-themes-prod" : "onex-themes-staging";
2228
+ }
2229
+ async function streamToString(stream) {
2230
+ const chunks = [];
2231
+ try {
2232
+ for (var iter = __forAwait(stream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
2233
+ const chunk = temp.value;
2234
+ chunks.push(Buffer.from(chunk));
2235
+ }
2236
+ } catch (temp) {
2237
+ error = [temp];
2238
+ } finally {
2239
+ try {
2240
+ more && (temp = iter.return) && await temp.call(iter);
2241
+ } finally {
2242
+ if (error)
2243
+ throw error[0];
2244
+ }
2245
+ }
2246
+ return Buffer.concat(chunks).toString("utf-8");
2247
+ }
2248
+ async function streamToBuffer(stream) {
2249
+ const chunks = [];
2250
+ try {
2251
+ for (var iter = __forAwait(stream), more, temp, error; more = !(temp = await iter.next()).done; more = false) {
2252
+ const chunk = temp.value;
2253
+ chunks.push(Buffer.from(chunk));
2254
+ }
2255
+ } catch (temp) {
2256
+ error = [temp];
2257
+ } finally {
2258
+ try {
2259
+ more && (temp = iter.return) && await temp.call(iter);
2260
+ } finally {
2261
+ if (error)
2262
+ throw error[0];
2263
+ }
2264
+ }
2265
+ return Buffer.concat(chunks);
2266
+ }
2267
+ async function resolveLatestVersion(s3Client, bucket, themeId) {
2268
+ try {
2269
+ const response = await s3Client.send(
2270
+ new clientS3.GetObjectCommand({
2271
+ Bucket: bucket,
2272
+ Key: `themes/${themeId}/latest.json`
2273
+ })
2274
+ );
2275
+ const body = await streamToString(response.Body);
2276
+ const data = JSON.parse(body);
2277
+ return data.version;
2278
+ } catch (error) {
2279
+ throw new Error(
2280
+ `Failed to resolve latest version for theme "${themeId}": ${error.message}`
2281
+ );
2282
+ }
2283
+ }
2284
+ async function downloadManifest(s3Client, bucket, themeId, version) {
2285
+ try {
2286
+ const response = await s3Client.send(
2287
+ new clientS3.GetObjectCommand({
2288
+ Bucket: bucket,
2289
+ Key: `themes/${themeId}/${version}/manifest.json`
2290
+ })
2291
+ );
2292
+ const body = await streamToString(response.Body);
2293
+ return JSON.parse(body);
2294
+ } catch (error) {
2295
+ throw new Error(
2296
+ `Failed to download manifest for ${themeId}@${version}: ${error.message}
2297
+ The theme may not exist in S3 bucket "${bucket}".`
2298
+ );
2299
+ }
2300
+ }
2301
+ async function downloadFile(s3Client, bucket, themeId, version, file, outputDir) {
2302
+ const s3Key = `themes/${themeId}/${version}/${file}`;
2303
+ const localPath = path__default.default.join(outputDir, file);
2304
+ await fs__default.default.ensureDir(path__default.default.dirname(localPath));
2305
+ try {
2306
+ const response = await s3Client.send(
2307
+ new clientS3.GetObjectCommand({
2308
+ Bucket: bucket,
2309
+ Key: s3Key
2310
+ })
2311
+ );
2312
+ const content = await streamToBuffer(response.Body);
2313
+ await fs__default.default.writeFile(localPath, content);
2314
+ } catch (error) {
2315
+ throw new Error(`Failed to download ${file}: ${error.message}`);
2316
+ }
2317
+ }
2318
+ function collectFilesToDownload(manifest) {
2319
+ const files = ["manifest.json"];
2320
+ if (manifest.output) {
2321
+ if (manifest.output.entry) {
2322
+ files.push(manifest.output.entry);
2323
+ }
2324
+ if (manifest.output.chunks) {
2325
+ files.push(...manifest.output.chunks);
2326
+ }
2327
+ if (manifest.output.assets) {
2328
+ files.push(...manifest.output.assets);
2329
+ }
2330
+ }
2331
+ return files;
2332
+ }
2333
+ async function createCompatibilityFiles(outputDir) {
2334
+ const sectionsRegistryPath = path__default.default.join(outputDir, "sections-registry.js");
2335
+ const content = `// Re-export all sections from bundle-entry
2336
+ // This file exists to maintain compatibility with the import path
2337
+ export * from './bundle-entry.js';
2338
+ `;
2339
+ await fs__default.default.writeFile(sectionsRegistryPath, content, "utf-8");
2340
+ }
2341
+ function showDownloadFailureHelp(themeId, bucket) {
2342
+ console.log();
2343
+ logger.error(chalk4__default.default.red.bold("\u274C Theme download failed"));
2344
+ console.log();
2345
+ console.log(chalk4__default.default.yellow("Possible reasons:"));
2346
+ console.log(chalk4__default.default.gray(" 1. Theme not uploaded to S3 yet"));
2347
+ console.log(chalk4__default.default.gray(" 2. AWS credentials not configured correctly"));
2348
+ console.log(chalk4__default.default.gray(" 3. Bucket name or region is incorrect"));
2349
+ console.log(chalk4__default.default.gray(" 4. Theme files are incomplete or corrupted"));
2350
+ console.log();
2351
+ console.log(chalk4__default.default.cyan.bold("To fix this:"));
2352
+ console.log();
2353
+ console.log(chalk4__default.default.white("1. Compile and upload the theme:"));
2354
+ console.log(chalk4__default.default.gray(` cd themes/${themeId}`));
2355
+ console.log(chalk4__default.default.gray(" pnpm build"));
2356
+ console.log(chalk4__default.default.gray(" pnpm upload"));
2357
+ console.log();
2358
+ console.log(chalk4__default.default.white("2. Verify AWS credentials are set:"));
2359
+ console.log(
2360
+ chalk4__default.default.gray(" - Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY")
2361
+ );
2362
+ console.log(chalk4__default.default.gray(" - Or use AWS_PROFILE=your-profile"));
2363
+ console.log(chalk4__default.default.gray(" - Set AWS_REGION (e.g., ap-southeast-1)"));
2364
+ console.log();
2365
+ console.log(chalk4__default.default.white("3. Check bucket configuration:"));
2366
+ console.log(chalk4__default.default.gray(` Current bucket: ${bucket}`));
2367
+ console.log(
2368
+ chalk4__default.default.gray(" Set BUCKET_NAME environment variable if different")
2369
+ );
2370
+ console.log();
2371
+ console.log(chalk4__default.default.white("4. Verify theme exists in S3:"));
2372
+ console.log(chalk4__default.default.gray(` aws s3 ls s3://${bucket}/themes/${themeId}/`));
2373
+ console.log();
2374
+ }
2375
+ async function downloadCommand(options) {
2376
+ logger.header("Download Theme from S3");
2377
+ const spinner = ora__default.default("Initializing download...").start();
2378
+ try {
2379
+ const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || process.env.THEME_ID;
2380
+ const version = options.version || process.env.THEME_VERSION || "latest";
2381
+ const bucket = options.bucket || getBucketName2(options.environment);
2382
+ const outputDir = options.output || "./active-theme";
2383
+ if (!themeId) {
2384
+ spinner.fail(
2385
+ chalk4__default.default.red(
2386
+ "Theme ID not specified. Use --theme-id or set NEXT_PUBLIC_THEME_ID environment variable."
2387
+ )
2388
+ );
2389
+ process.exit(1);
2390
+ }
2391
+ spinner.text = `Downloading ${themeId}@${version} from ${bucket}...`;
2392
+ const s3Client = getS3Client2();
2393
+ let resolvedVersion = version;
2394
+ if (version === "latest") {
2395
+ spinner.text = "Resolving latest version...";
2396
+ resolvedVersion = await resolveLatestVersion(s3Client, bucket, themeId);
2397
+ spinner.succeed(
2398
+ `Resolved latest version: ${chalk4__default.default.cyan(resolvedVersion)}`
2399
+ );
2400
+ spinner.start("Downloading manifest...");
2401
+ }
2402
+ const manifest = await downloadManifest(
2403
+ s3Client,
2404
+ bucket,
2405
+ themeId,
2406
+ resolvedVersion
2407
+ );
2408
+ spinner.succeed("Downloaded manifest");
2409
+ spinner.start("Preparing output directory...");
2410
+ await fs__default.default.remove(outputDir);
2411
+ await fs__default.default.ensureDir(outputDir);
2412
+ spinner.succeed("Output directory ready");
2413
+ spinner.start("Downloading theme files...");
2414
+ const files = collectFilesToDownload(manifest);
2415
+ let downloaded = 0;
2416
+ const total = files.length;
2417
+ const batchSize = 10;
2418
+ for (let i = 0; i < files.length; i += batchSize) {
2419
+ const batch = files.slice(i, i + batchSize);
2420
+ await Promise.all(
2421
+ batch.map(async (file) => {
2422
+ await downloadFile(
2423
+ s3Client,
2424
+ bucket,
2425
+ themeId,
2426
+ resolvedVersion,
2427
+ file,
2428
+ outputDir
2429
+ );
2430
+ downloaded++;
2431
+ spinner.text = `Downloading... ${downloaded}/${total} files (${Math.round(downloaded / total * 100)}%)`;
2432
+ })
2433
+ );
2434
+ }
2435
+ spinner.succeed(`Downloaded ${total} files`);
2436
+ await createCompatibilityFiles(outputDir);
2437
+ console.log();
2438
+ logger.success(chalk4__default.default.green.bold("\u2705 Theme downloaded successfully!"));
2439
+ console.log();
2440
+ console.log(
2441
+ chalk4__default.default.cyan(" Theme: ") + chalk4__default.default.white(`${themeId}@${resolvedVersion}`)
2442
+ );
2443
+ console.log(chalk4__default.default.cyan(" Bucket: ") + chalk4__default.default.white(bucket));
2444
+ console.log(chalk4__default.default.cyan(" Output: ") + chalk4__default.default.white(outputDir));
2445
+ console.log(chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(total));
2446
+ console.log(
2447
+ chalk4__default.default.cyan(" Sections:") + chalk4__default.default.white(` ${manifest.counts.sections}`)
2448
+ );
2449
+ console.log();
2450
+ } catch (error) {
2451
+ spinner.fail(chalk4__default.default.red("Download failed"));
2452
+ logger.error(error.message);
2453
+ const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || "unknown";
2454
+ const bucket = options.bucket || getBucketName2(options.environment);
2455
+ showDownloadFailureHelp(themeId, bucket);
2456
+ process.exit(1);
2457
+ }
2458
+ }
2459
+
2460
+ // src/cli.ts
2461
+ var program = new commander.Command();
2462
+ program.name("onex").description("CLI tool for OneX theme development").version("0.1.0");
2463
+ program.command("init").description("Create a new OneX theme project").argument("[project-name]", "Name of the project").option(
2464
+ "-t, --template <template>",
2465
+ "Template to use (default, minimal)",
2466
+ "default"
2467
+ ).option("--no-install", "Skip installing dependencies").option("--git", "Initialize git repository").option("-y, --yes", "Skip prompts and use defaults").action(initCommand);
2468
+ program.command("create:section").alias("cs").description("Create a new section").argument("<name>", "Name of the section (e.g., hero, features)").option("-t, --theme <theme>", "Theme to create section in").option(
2469
+ "-c, --category <category>",
2470
+ "Section category (headers, content, footers)"
2471
+ ).option(
2472
+ "--template <template>",
2473
+ "Initial template variant (default, minimal)"
2474
+ ).action(createSectionCommand);
2475
+ program.command("create:block").alias("cb").description("Create a new block").argument(
2476
+ "<name>",
2477
+ "Name of the block (e.g., product-card, testimonial-item)"
2478
+ ).option("-t, --theme <theme>", "Theme to create block in (optional)").action(createBlockCommand);
2479
+ 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);
2480
+ 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);
2481
+ 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);
2482
+ 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);
2483
+ 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);
2484
+ 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(
2485
+ "-e, --environment <env>",
2486
+ "Environment (production, staging, development)"
2487
+ ).action(deployCommand);
2488
+ program.command("upload").description("Upload compiled theme to S3 bucket").option("-t, --theme <theme>", "Theme to upload").option("-b, --bucket <name>", "S3 bucket name").option("-v, --version <version>", "Theme version").option(
2489
+ "-e, --environment <env>",
2490
+ "Environment (staging|production)",
2491
+ "staging"
2492
+ ).option("--dry-run", "Show what would be uploaded without uploading").action(uploadCommand);
2493
+ program.command("download").description("Download theme from S3 bucket").option("-t, --theme-id <id>", "Theme ID to download").option(
2494
+ "-v, --version <version>",
2495
+ "Theme version (default: latest)",
2496
+ "latest"
2497
+ ).option("-b, --bucket <name>", "S3 bucket name").option(
2498
+ "-e, --environment <env>",
2499
+ "Environment (staging|production)",
2500
+ "staging"
2501
+ ).option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
2502
+ program.configureOutput({
2503
+ writeErr: (str) => process.stderr.write(chalk4__default.default.red(str))
2504
+ });
2505
+ program.parse();
2506
+ //# sourceMappingURL=cli.js.map
2507
+ //# sourceMappingURL=cli.js.map