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