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