@onexapis/cli 1.1.64 → 1.1.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -3,18 +3,18 @@
3
3
 
4
4
  var chalk4 = require('chalk');
5
5
  var ora = require('ora');
6
- var esbuild = require('esbuild');
7
- var path9 = require('path');
8
- var fs8 = require('fs/promises');
9
- var crypto = require('crypto');
6
+ var path11 = require('path');
10
7
  var glob = require('glob');
8
+ var fs = require('fs-extra');
9
+ var crypto = require('crypto');
10
+ var esbuild = require('esbuild');
11
+ var fs9 = require('fs/promises');
11
12
  var module$1 = require('module');
12
13
  var http = require('http');
13
14
  var fs3 = require('fs');
14
15
  var ws = require('ws');
15
16
  var os = require('os');
16
17
  var dotenv = require('dotenv');
17
- var fs = require('fs-extra');
18
18
  var ejs = require('ejs');
19
19
  var child_process = require('child_process');
20
20
  var commander = require('commander');
@@ -49,15 +49,15 @@ function _interopNamespace(e) {
49
49
 
50
50
  var chalk4__default = /*#__PURE__*/_interopDefault(chalk4);
51
51
  var ora__default = /*#__PURE__*/_interopDefault(ora);
52
- var esbuild__namespace = /*#__PURE__*/_interopNamespace(esbuild);
53
- var path9__default = /*#__PURE__*/_interopDefault(path9);
54
- var fs8__default = /*#__PURE__*/_interopDefault(fs8);
52
+ var path11__default = /*#__PURE__*/_interopDefault(path11);
53
+ var fs__default = /*#__PURE__*/_interopDefault(fs);
55
54
  var crypto__default = /*#__PURE__*/_interopDefault(crypto);
55
+ var esbuild__namespace = /*#__PURE__*/_interopNamespace(esbuild);
56
+ var fs9__default = /*#__PURE__*/_interopDefault(fs9);
56
57
  var http__default = /*#__PURE__*/_interopDefault(http);
57
58
  var fs3__default = /*#__PURE__*/_interopDefault(fs3);
58
59
  var os__default = /*#__PURE__*/_interopDefault(os);
59
60
  var dotenv__default = /*#__PURE__*/_interopDefault(dotenv);
60
- var fs__default = /*#__PURE__*/_interopDefault(fs);
61
61
  var ejs__default = /*#__PURE__*/_interopDefault(ejs);
62
62
  var inquirer__default = /*#__PURE__*/_interopDefault(inquirer);
63
63
  var archiver__default = /*#__PURE__*/_interopDefault(archiver);
@@ -132,6 +132,206 @@ var init_logger = __esm({
132
132
  logger = new Logger();
133
133
  }
134
134
  });
135
+ function sortedCopy(value) {
136
+ if (Array.isArray(value)) {
137
+ return value.map((v) => sortedCopy(v));
138
+ }
139
+ if (value && typeof value === "object") {
140
+ const sorted = {};
141
+ for (const key of Object.keys(value).sort()) {
142
+ sorted[key] = sortedCopy(value[key]);
143
+ }
144
+ return sorted;
145
+ }
146
+ return value;
147
+ }
148
+ function normalizeField(raw) {
149
+ const out = {
150
+ id: String(raw.id),
151
+ type: String(raw.type)
152
+ };
153
+ if (raw.required === true) out.required = true;
154
+ if (raw.default !== void 0) out.default = raw.default;
155
+ if (Array.isArray(raw.aliases) && raw.aliases.length > 0) {
156
+ out.aliases = [...raw.aliases].map(String).sort();
157
+ }
158
+ if (typeof raw.maxLength === "number") out.maxLength = raw.maxLength;
159
+ if (typeof raw.min === "number") out.min = raw.min;
160
+ if (typeof raw.max === "number") out.max = raw.max;
161
+ if (typeof raw.step === "number") out.step = raw.step;
162
+ if (Array.isArray(raw.accept)) {
163
+ out.accept = [...raw.accept].map(String).sort();
164
+ }
165
+ if (Array.isArray(raw.options)) {
166
+ out.options = raw.options.map((o) => String(o?.value ?? o)).sort();
167
+ }
168
+ return out;
169
+ }
170
+ function normalizeBlock(raw) {
171
+ return {
172
+ type: String(raw.type),
173
+ settings: Array.isArray(raw.settings) ? raw.settings.map(normalizeField).sort(sortFieldsById) : [],
174
+ defaults: raw.defaults && typeof raw.defaults === "object" ? sortedCopy(raw.defaults) : {},
175
+ ...typeof raw.limit === "number" ? { limit: raw.limit } : {},
176
+ ...typeof raw.min === "number" ? { min: raw.min } : {},
177
+ ...raw.sortable === true ? { sortable: true } : {},
178
+ ...raw.baseType ? { baseType: String(raw.baseType) } : {}
179
+ };
180
+ }
181
+ function normalizeTemplate(raw) {
182
+ const out = { id: String(raw.id) };
183
+ if (raw.isDefault === true) out.isDefault = true;
184
+ if (Array.isArray(raw.settings)) {
185
+ out.settings = raw.settings.map(normalizeField).sort(sortFieldsById);
186
+ }
187
+ if (raw.defaults && typeof raw.defaults === "object") {
188
+ out.defaults = sortedCopy(raw.defaults);
189
+ }
190
+ return out;
191
+ }
192
+ function sortFieldsById(a, b) {
193
+ return a.id.localeCompare(b.id);
194
+ }
195
+ function sortByType(a, b) {
196
+ return a.type.localeCompare(b.type);
197
+ }
198
+ function normalizeSection(raw) {
199
+ return {
200
+ type: String(raw.type),
201
+ settings: Array.isArray(raw.settings) ? raw.settings.map(normalizeField).sort(sortFieldsById) : [],
202
+ defaults: raw.defaults && typeof raw.defaults === "object" ? sortedCopy(raw.defaults) : {},
203
+ blocks: Array.isArray(raw.blocks) ? raw.blocks.map(normalizeBlock).sort(sortByType) : [],
204
+ templates: Array.isArray(raw.templates) ? raw.templates.map(normalizeTemplate).sort(sortFieldsById) : [],
205
+ dataRequirements: raw.dataRequirements && typeof raw.dataRequirements === "object" ? sortedCopy(raw.dataRequirements) : null,
206
+ ...raw.global === true ? { global: true } : {},
207
+ ...typeof raw.maxBlocks === "number" ? { maxBlocks: raw.maxBlocks } : {}
208
+ };
209
+ }
210
+ async function extractSchemas(themePath) {
211
+ const { createJiti } = await import('jiti');
212
+ const jiti = createJiti((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.js', document.baseURI).href)));
213
+ const schemaFiles = await glob.glob("sections/**/*.schema.ts", { cwd: themePath });
214
+ const sections = {};
215
+ for (const file of schemaFiles) {
216
+ try {
217
+ const mod = await jiti.import(path11__default.default.join(themePath, file));
218
+ const exports$1 = mod;
219
+ for (const value of Object.values(exports$1)) {
220
+ if (value && typeof value === "object" && typeof value.type === "string" && Array.isArray(value.settings)) {
221
+ const section = normalizeSection(value);
222
+ sections[section.type] = section;
223
+ }
224
+ }
225
+ } catch {
226
+ }
227
+ }
228
+ const manifest = {
229
+ manifestVersion: 1,
230
+ sections: {}
231
+ };
232
+ for (const type of Object.keys(sections).sort()) {
233
+ manifest.sections[type] = sections[type];
234
+ }
235
+ return manifest;
236
+ }
237
+ function serializeManifest(manifest) {
238
+ return JSON.stringify(sortedCopy(manifest), null, 2);
239
+ }
240
+ var init_extract_schemas = __esm({
241
+ "src/utils/extract-schemas.ts"() {
242
+ }
243
+ });
244
+ function isVideoAsset(filePath) {
245
+ const lower = filePath.toLowerCase();
246
+ return VIDEO_EXTENSIONS.some((ext) => lower.endsWith(ext));
247
+ }
248
+ function mimeFor(filename) {
249
+ const ext = path11__default.default.extname(filename).toLowerCase();
250
+ return MIME_MAP[ext] || "application/octet-stream";
251
+ }
252
+ async function sha256Prefix(absPath, len) {
253
+ const buf = await fs__default.default.readFile(absPath);
254
+ return crypto__default.default.createHash("sha256").update(buf).digest("hex").slice(0, len);
255
+ }
256
+ function insertHashIntoName(relPath, hash) {
257
+ const dir = path11__default.default.posix.dirname(relPath);
258
+ const base = path11__default.default.posix.basename(relPath);
259
+ const ext = path11__default.default.posix.extname(base);
260
+ const stem = ext ? base.slice(0, -ext.length) : base;
261
+ const hashed = `${stem}-${hash}${ext}`;
262
+ return dir === "." ? hashed : `${dir}/${hashed}`;
263
+ }
264
+ async function scanThemeAssets(distDir) {
265
+ const assetsDir = path11__default.default.join(distDir, "theme-assets");
266
+ if (!await fs__default.default.pathExists(assetsDir)) return [];
267
+ const files = await glob.glob("**/*", {
268
+ cwd: assetsDir,
269
+ nodir: true,
270
+ dot: false
271
+ });
272
+ const results = [];
273
+ for (const rel of files) {
274
+ const absPath = path11__default.default.join(assetsDir, rel);
275
+ const stat = await fs__default.default.stat(absPath);
276
+ if (!stat.isFile()) continue;
277
+ const originalPath = rel.split(path11__default.default.sep).join("/");
278
+ const hash = await sha256Prefix(absPath, HASH_LEN);
279
+ const hashedPath = insertHashIntoName(originalPath, hash);
280
+ const contentType = mimeFor(rel);
281
+ results.push({
282
+ originalPath,
283
+ hashedPath,
284
+ hash,
285
+ size: stat.size,
286
+ contentType,
287
+ absPath
288
+ });
289
+ }
290
+ results.sort((a, b) => a.originalPath.localeCompare(b.originalPath));
291
+ return results;
292
+ }
293
+ function buildAssetMap(entries) {
294
+ const map = {};
295
+ for (const e of entries) {
296
+ map[e.originalPath] = e.hashedPath;
297
+ }
298
+ return map;
299
+ }
300
+ var MIME_MAP, HASH_LEN, VIDEO_EXTENSIONS;
301
+ var init_scan_theme_assets = __esm({
302
+ "src/utils/scan-theme-assets.ts"() {
303
+ MIME_MAP = {
304
+ ".png": "image/png",
305
+ ".jpg": "image/jpeg",
306
+ ".jpeg": "image/jpeg",
307
+ ".gif": "image/gif",
308
+ ".webp": "image/webp",
309
+ ".avif": "image/avif",
310
+ ".svg": "image/svg+xml",
311
+ ".ico": "image/x-icon",
312
+ ".bmp": "image/bmp",
313
+ ".woff": "font/woff",
314
+ ".woff2": "font/woff2",
315
+ ".ttf": "font/ttf",
316
+ ".otf": "font/otf",
317
+ ".eot": "application/vnd.ms-fontobject",
318
+ ".mp4": "video/mp4",
319
+ ".webm": "video/webm",
320
+ ".mov": "video/quicktime",
321
+ ".ogg": "video/ogg",
322
+ ".json": "application/json"
323
+ };
324
+ HASH_LEN = 8;
325
+ VIDEO_EXTENSIONS = [
326
+ ".mp4",
327
+ ".webm",
328
+ ".ogg",
329
+ ".mov",
330
+ ".avi",
331
+ ".mkv"
332
+ ];
333
+ }
334
+ });
135
335
 
136
336
  // src/utils/compile-theme.ts
137
337
  var compile_theme_exports = {};
@@ -147,8 +347,8 @@ async function generateThemeCSS(themePath, outDir) {
147
347
  const tailwindcss = (await import('tailwindcss')).default;
148
348
  const tailwindConfig = {
149
349
  content: [
150
- path9__default.default.join(themePath, "sections/**/*.{ts,tsx}"),
151
- path9__default.default.join(themePath, "components/**/*.{ts,tsx}")
350
+ path11__default.default.join(themePath, "sections/**/*.{ts,tsx}"),
351
+ path11__default.default.join(themePath, "components/**/*.{ts,tsx}")
152
352
  ],
153
353
  theme: { extend: {} },
154
354
  plugins: []
@@ -158,7 +358,7 @@ async function generateThemeCSS(themePath, outDir) {
158
358
  inputCSS,
159
359
  { from: void 0 }
160
360
  );
161
- await fs8__default.default.writeFile(path9__default.default.join(outDir, "bundle.css"), result.css);
361
+ await fs9__default.default.writeFile(path11__default.default.join(outDir, "bundle.css"), result.css);
162
362
  logger.info("Generated bundle.css");
163
363
  } catch (err) {
164
364
  logger.warning(
@@ -169,12 +369,12 @@ async function generateThemeCSS(themePath, outDir) {
169
369
  async function resolveNodeModulesFile(startDir, relativePath) {
170
370
  let dir = startDir;
171
371
  while (true) {
172
- const candidate = path9__default.default.join(dir, "node_modules", relativePath);
372
+ const candidate = path11__default.default.join(dir, "node_modules", relativePath);
173
373
  try {
174
- await fs8__default.default.access(candidate);
374
+ await fs9__default.default.access(candidate);
175
375
  return candidate;
176
376
  } catch {
177
- const parent = path9__default.default.dirname(dir);
377
+ const parent = path11__default.default.dirname(dir);
178
378
  if (parent === dir) break;
179
379
  dir = parent;
180
380
  }
@@ -198,7 +398,7 @@ async function scanImportsFromPackage(sourceDir, packageName) {
198
398
  });
199
399
  for (const file of sourceFiles) {
200
400
  try {
201
- const content = await fs8__default.default.readFile(path9__default.default.join(sourceDir, file), "utf-8");
401
+ const content = await fs9__default.default.readFile(path11__default.default.join(sourceDir, file), "utf-8");
202
402
  for (const match of content.matchAll(namespaceImportRegex)) {
203
403
  const subpath = match[1] ? match[1].slice(1) : "";
204
404
  if (!result[subpath]) result[subpath] = /* @__PURE__ */ new Set();
@@ -252,17 +452,17 @@ function createCoreGlobalPlugin(themePath) {
252
452
  const distFileName = subpath ? `${subpath}.mjs` : "index.mjs";
253
453
  let distPath = await resolveNodeModulesFile(
254
454
  themePath,
255
- path9__default.default.join("@onexapis", "core", "dist", distFileName)
455
+ path11__default.default.join("@onexapis", "core", "dist", distFileName)
256
456
  );
257
457
  if (!distPath) {
258
458
  distPath = await resolveNodeModulesFile(
259
459
  __dirname,
260
- path9__default.default.join("@onexapis", "core", "dist", distFileName)
460
+ path11__default.default.join("@onexapis", "core", "dist", distFileName)
261
461
  );
262
462
  }
263
463
  try {
264
464
  if (!distPath) throw new Error("not found");
265
- const distContent = await fs8__default.default.readFile(distPath, "utf-8");
465
+ const distContent = await fs9__default.default.readFile(distPath, "utf-8");
266
466
  const exportMatches = distContent.matchAll(/export\s*\{([^}]+)\}/g);
267
467
  for (const m of exportMatches) {
268
468
  const names = m[1].split(",").map((n) => {
@@ -491,7 +691,7 @@ async function generateThemeData(themePath, outputDir, themeId) {
491
691
  const pages = {};
492
692
  for (const ext of [".ts", ".js"]) {
493
693
  try {
494
- const mod = await jiti.import(path9__default.default.join(themePath, `theme.config${ext}`));
694
+ const mod = await jiti.import(path11__default.default.join(themePath, `theme.config${ext}`));
495
695
  themeConfig = mod.default || mod;
496
696
  break;
497
697
  } catch {
@@ -499,20 +699,20 @@ async function generateThemeData(themePath, outputDir, themeId) {
499
699
  }
500
700
  for (const ext of [".ts", ".js"]) {
501
701
  try {
502
- const mod = await jiti.import(path9__default.default.join(themePath, `theme.layout${ext}`));
702
+ const mod = await jiti.import(path11__default.default.join(themePath, `theme.layout${ext}`));
503
703
  layoutConfig = mod.default || mod;
504
704
  break;
505
705
  } catch {
506
706
  }
507
707
  }
508
708
  const schemas = {};
509
- const sectionsDir = path9__default.default.join(themePath, "sections");
709
+ const sectionsDir = path11__default.default.join(themePath, "sections");
510
710
  try {
511
- const sectionDirs = await fs8__default.default.readdir(sectionsDir);
711
+ const sectionDirs = await fs9__default.default.readdir(sectionsDir);
512
712
  for (const dir of sectionDirs) {
513
- const schemaFile = path9__default.default.join(sectionsDir, dir, `${dir}.schema.ts`);
713
+ const schemaFile = path11__default.default.join(sectionsDir, dir, `${dir}.schema.ts`);
514
714
  try {
515
- await fs8__default.default.access(schemaFile);
715
+ await fs9__default.default.access(schemaFile);
516
716
  const mod = await jiti.import(schemaFile);
517
717
  for (const [key, value] of Object.entries(mod)) {
518
718
  if (key.endsWith("Schema") && value && typeof value === "object" && value.type) {
@@ -524,14 +724,14 @@ async function generateThemeData(themePath, outputDir, themeId) {
524
724
  }
525
725
  } catch {
526
726
  }
527
- const pagesDir = path9__default.default.join(themePath, "pages");
727
+ const pagesDir = path11__default.default.join(themePath, "pages");
528
728
  try {
529
- const files = await fs8__default.default.readdir(pagesDir);
729
+ const files = await fs9__default.default.readdir(pagesDir);
530
730
  for (const file of files) {
531
731
  if (!file.match(/\.(ts|js)$/)) continue;
532
732
  const name = file.replace(/\.(ts|js)$/, "");
533
733
  try {
534
- const mod = await jiti.import(path9__default.default.join(pagesDir, file));
734
+ const mod = await jiti.import(path11__default.default.join(pagesDir, file));
535
735
  const config = mod.default || mod;
536
736
  const sections = (config.sections || []).map((section) => {
537
737
  const schema = schemas[section.type];
@@ -559,8 +759,8 @@ async function generateThemeData(themePath, outputDir, themeId) {
559
759
  }
560
760
  } catch {
561
761
  }
562
- await fs8__default.default.writeFile(
563
- path9__default.default.join(outputDir, "theme-data.json"),
762
+ await fs9__default.default.writeFile(
763
+ path11__default.default.join(outputDir, "theme-data.json"),
564
764
  JSON.stringify(
565
765
  {
566
766
  themeId,
@@ -583,22 +783,22 @@ async function generateThemeData(themePath, outputDir, themeId) {
583
783
  logger.info(`Generated theme-data.json (${Object.keys(pages).length} pages)`);
584
784
  }
585
785
  async function contentHashEntry(outputDir) {
586
- const entryPath = path9__default.default.join(outputDir, "bundle-entry.js");
587
- const mapPath = path9__default.default.join(outputDir, "bundle-entry.js.map");
786
+ const entryPath = path11__default.default.join(outputDir, "bundle-entry.js");
787
+ const mapPath = path11__default.default.join(outputDir, "bundle-entry.js.map");
588
788
  let entryContent;
589
789
  try {
590
- entryContent = await fs8__default.default.readFile(entryPath, "utf-8");
790
+ entryContent = await fs9__default.default.readFile(entryPath, "utf-8");
591
791
  } catch {
592
- const indexPath = path9__default.default.join(outputDir, "index.js");
792
+ const indexPath = path11__default.default.join(outputDir, "index.js");
593
793
  try {
594
- entryContent = await fs8__default.default.readFile(indexPath, "utf-8");
794
+ entryContent = await fs9__default.default.readFile(indexPath, "utf-8");
595
795
  } catch {
596
796
  logger.warning("No entry file found in output, skipping content hash");
597
797
  return;
598
798
  }
599
799
  const hash2 = crypto__default.default.createHash("sha256").update(entryContent).digest("hex").slice(0, 8);
600
800
  const hashedName2 = `bundle-entry-${hash2}.js`;
601
- const indexMapPath = path9__default.default.join(outputDir, "index.js.map");
801
+ const indexMapPath = path11__default.default.join(outputDir, "index.js.map");
602
802
  const hashedMapName2 = `bundle-entry-${hash2}.js.map`;
603
803
  entryContent = entryContent.replace(
604
804
  /\/\/# sourceMappingURL=index\.js\.map/,
@@ -606,18 +806,18 @@ async function contentHashEntry(outputDir) {
606
806
  );
607
807
  const oldFiles2 = await glob.glob("bundle-entry-*.js*", { cwd: outputDir });
608
808
  for (const f of oldFiles2) {
609
- await fs8__default.default.unlink(path9__default.default.join(outputDir, f));
809
+ await fs9__default.default.unlink(path11__default.default.join(outputDir, f));
610
810
  }
611
- await fs8__default.default.writeFile(path9__default.default.join(outputDir, hashedName2), entryContent);
612
- await fs8__default.default.unlink(indexPath);
811
+ await fs9__default.default.writeFile(path11__default.default.join(outputDir, hashedName2), entryContent);
812
+ await fs9__default.default.unlink(indexPath);
613
813
  try {
614
- await fs8__default.default.unlink(entryPath);
814
+ await fs9__default.default.unlink(entryPath);
615
815
  } catch {
616
816
  }
617
- await fs8__default.default.writeFile(entryPath, entryContent);
817
+ await fs9__default.default.writeFile(entryPath, entryContent);
618
818
  try {
619
- await fs8__default.default.access(indexMapPath);
620
- await fs8__default.default.rename(indexMapPath, path9__default.default.join(outputDir, hashedMapName2));
819
+ await fs9__default.default.access(indexMapPath);
820
+ await fs9__default.default.rename(indexMapPath, path11__default.default.join(outputDir, hashedMapName2));
621
821
  } catch {
622
822
  }
623
823
  logger.info(`Entry hashed: ${hashedName2}`);
@@ -632,17 +832,17 @@ async function contentHashEntry(outputDir) {
632
832
  );
633
833
  const oldFiles = await glob.glob("bundle-entry-*.js*", { cwd: outputDir });
634
834
  for (const f of oldFiles) {
635
- await fs8__default.default.unlink(path9__default.default.join(outputDir, f));
835
+ await fs9__default.default.unlink(path11__default.default.join(outputDir, f));
636
836
  }
637
- await fs8__default.default.writeFile(path9__default.default.join(outputDir, hashedName), entryContent);
837
+ await fs9__default.default.writeFile(path11__default.default.join(outputDir, hashedName), entryContent);
638
838
  try {
639
- await fs8__default.default.unlink(entryPath);
839
+ await fs9__default.default.unlink(entryPath);
640
840
  } catch {
641
841
  }
642
- await fs8__default.default.writeFile(entryPath, entryContent);
842
+ await fs9__default.default.writeFile(entryPath, entryContent);
643
843
  try {
644
- await fs8__default.default.access(mapPath);
645
- await fs8__default.default.rename(mapPath, path9__default.default.join(outputDir, hashedMapName));
844
+ await fs9__default.default.access(mapPath);
845
+ await fs9__default.default.rename(mapPath, path11__default.default.join(outputDir, hashedMapName));
646
846
  } catch {
647
847
  }
648
848
  logger.info(`Entry hashed: ${hashedName}`);
@@ -654,7 +854,7 @@ async function extractDataRequirements(themePath) {
654
854
  const requirements = {};
655
855
  for (const file of schemaFiles) {
656
856
  try {
657
- const mod = await jiti.import(path9__default.default.join(themePath, file));
857
+ const mod = await jiti.import(path11__default.default.join(themePath, file));
658
858
  const exports$1 = mod;
659
859
  for (const value of Object.values(exports$1)) {
660
860
  if (value && typeof value === "object" && typeof value.type === "string" && value.dataRequirements && typeof value.dataRequirements === "object") {
@@ -669,12 +869,46 @@ async function extractDataRequirements(themePath) {
669
869
  }
670
870
  return requirements;
671
871
  }
872
+ async function writeGateManifests(themePath, outputDir) {
873
+ try {
874
+ const schemas = await extractSchemas(themePath);
875
+ await fs9__default.default.writeFile(
876
+ path11__default.default.join(outputDir, "schemas.json"),
877
+ serializeManifest(schemas)
878
+ );
879
+ logger.info(
880
+ `Generated schemas.json (${Object.keys(schemas.sections).length} sections)`
881
+ );
882
+ } catch (err) {
883
+ logger.warning(
884
+ `schemas.json not written: ${err instanceof Error ? err.message : String(err)}`
885
+ );
886
+ }
887
+ try {
888
+ const entries = await scanThemeAssets(outputDir);
889
+ const assets = entries.map((e) => ({
890
+ path: e.originalPath,
891
+ hash: e.hash,
892
+ size: e.size,
893
+ contentType: e.contentType
894
+ }));
895
+ await fs9__default.default.writeFile(
896
+ path11__default.default.join(outputDir, "asset-manifest.json"),
897
+ JSON.stringify({ manifestVersion: 1, assets }, null, 2)
898
+ );
899
+ logger.info(`Generated asset-manifest.json (${assets.length} assets)`);
900
+ } catch (err) {
901
+ logger.warning(
902
+ `asset-manifest.json not written: ${err instanceof Error ? err.message : String(err)}`
903
+ );
904
+ }
905
+ }
672
906
  async function generateManifest(themeName, themePath, outputDir) {
673
907
  let version2 = "1.0.0";
674
908
  let themeId = themeName;
675
909
  try {
676
- const pkgContent = await fs8__default.default.readFile(
677
- path9__default.default.join(themePath, "package.json"),
910
+ const pkgContent = await fs9__default.default.readFile(
911
+ path11__default.default.join(themePath, "package.json"),
678
912
  "utf-8"
679
913
  );
680
914
  const pkg = JSON.parse(pkgContent);
@@ -692,7 +926,7 @@ async function generateManifest(themeName, themePath, outputDir) {
692
926
  const dataRequirements = await extractDataRequirements(themePath);
693
927
  let hasThemeConfig = false;
694
928
  try {
695
- await fs8__default.default.access(path9__default.default.join(themePath, "theme.config.ts"));
929
+ await fs9__default.default.access(path11__default.default.join(themePath, "theme.config.ts"));
696
930
  hasThemeConfig = true;
697
931
  } catch {
698
932
  }
@@ -733,24 +967,24 @@ async function generateManifest(themeName, themePath, outputDir) {
733
967
  // Section data requirements for server-side prefetching (keyed by section type)
734
968
  dataRequirements
735
969
  };
736
- await fs8__default.default.writeFile(
737
- path9__default.default.join(outputDir, "manifest.json"),
970
+ await fs9__default.default.writeFile(
971
+ path11__default.default.join(outputDir, "manifest.json"),
738
972
  JSON.stringify(manifest, null, 2)
739
973
  );
740
974
  }
741
975
  async function compileStandaloneTheme(themePath, themeName) {
742
- const outputDir = path9__default.default.join(themePath, "dist");
743
- const bundleEntry = path9__default.default.join(themePath, "bundle-entry.ts");
744
- const indexEntry = path9__default.default.join(themePath, "index.ts");
976
+ const outputDir = path11__default.default.join(themePath, "dist");
977
+ const bundleEntry = path11__default.default.join(themePath, "bundle-entry.ts");
978
+ const indexEntry = path11__default.default.join(themePath, "index.ts");
745
979
  let entryPoint = indexEntry;
746
980
  try {
747
- await fs8__default.default.access(bundleEntry);
981
+ await fs9__default.default.access(bundleEntry);
748
982
  entryPoint = bundleEntry;
749
983
  } catch {
750
984
  }
751
- const shimPath = path9__default.default.join(outputDir, ".process-shim.js");
752
- await fs8__default.default.mkdir(outputDir, { recursive: true });
753
- await fs8__default.default.writeFile(shimPath, PROCESS_SHIM);
985
+ const shimPath = path11__default.default.join(outputDir, ".process-shim.js");
986
+ await fs9__default.default.mkdir(outputDir, { recursive: true });
987
+ await fs9__default.default.writeFile(shimPath, PROCESS_SHIM);
754
988
  const buildOptions = {
755
989
  entryPoints: [entryPoint],
756
990
  bundle: true,
@@ -801,19 +1035,20 @@ async function compileStandaloneTheme(themePath, themeName) {
801
1035
  try {
802
1036
  const result = await esbuild__namespace.build(buildOptions);
803
1037
  try {
804
- await fs8__default.default.unlink(shimPath);
1038
+ await fs9__default.default.unlink(shimPath);
805
1039
  } catch {
806
1040
  }
807
1041
  await contentHashEntry(outputDir);
808
- const themeAssetsDir = path9__default.default.join(themePath, "assets");
809
- const distThemeAssets = path9__default.default.join(outputDir, "theme-assets");
1042
+ const themeAssetsDir = path11__default.default.join(themePath, "assets");
1043
+ const distThemeAssets = path11__default.default.join(outputDir, "theme-assets");
810
1044
  try {
811
- await fs8__default.default.access(themeAssetsDir);
812
- await fs8__default.default.cp(themeAssetsDir, distThemeAssets, { recursive: true });
1045
+ await fs9__default.default.access(themeAssetsDir);
1046
+ await fs9__default.default.cp(themeAssetsDir, distThemeAssets, { recursive: true });
813
1047
  logger.info("Copied static assets to dist/theme-assets/");
814
1048
  } catch {
815
1049
  }
816
1050
  await generateManifest(themeName, themePath, outputDir);
1051
+ await writeGateManifests(themePath, outputDir);
817
1052
  await generateThemeData(themePath, outputDir, themeName);
818
1053
  await generateThemeCSS(themePath, outputDir);
819
1054
  if (result.metafile) {
@@ -828,7 +1063,7 @@ async function compileStandaloneTheme(themePath, themeName) {
828
1063
  return true;
829
1064
  } catch (error) {
830
1065
  try {
831
- await fs8__default.default.unlink(shimPath);
1066
+ await fs9__default.default.unlink(shimPath);
832
1067
  } catch {
833
1068
  }
834
1069
  logger.error(`esbuild compilation failed: ${error}`);
@@ -836,18 +1071,18 @@ async function compileStandaloneTheme(themePath, themeName) {
836
1071
  }
837
1072
  }
838
1073
  async function compileStandaloneThemeDev(themePath, themeName) {
839
- const outputDir = path9__default.default.join(themePath, "dist");
840
- const bundleEntry = path9__default.default.join(themePath, "bundle-entry.ts");
841
- const indexEntry = path9__default.default.join(themePath, "index.ts");
1074
+ const outputDir = path11__default.default.join(themePath, "dist");
1075
+ const bundleEntry = path11__default.default.join(themePath, "bundle-entry.ts");
1076
+ const indexEntry = path11__default.default.join(themePath, "index.ts");
842
1077
  let entryPoint = indexEntry;
843
1078
  try {
844
- await fs8__default.default.access(bundleEntry);
1079
+ await fs9__default.default.access(bundleEntry);
845
1080
  entryPoint = bundleEntry;
846
1081
  } catch {
847
1082
  }
848
- const shimPath = path9__default.default.join(outputDir, ".process-shim.js");
849
- await fs8__default.default.mkdir(outputDir, { recursive: true });
850
- await fs8__default.default.writeFile(shimPath, PROCESS_SHIM);
1083
+ const shimPath = path11__default.default.join(outputDir, ".process-shim.js");
1084
+ await fs9__default.default.mkdir(outputDir, { recursive: true });
1085
+ await fs9__default.default.writeFile(shimPath, PROCESS_SHIM);
851
1086
  const buildOptions = {
852
1087
  entryPoints: [entryPoint],
853
1088
  bundle: true,
@@ -901,18 +1136,18 @@ async function compileStandaloneThemeDev(themePath, themeName) {
901
1136
  return { context: context2, outputDir };
902
1137
  }
903
1138
  async function compilePreviewRuntime(themePath) {
904
- const outputDir = path9__default.default.join(themePath, "dist");
905
- await fs8__default.default.mkdir(outputDir, { recursive: true });
906
- const outputPath = path9__default.default.join(outputDir, "preview-runtime.js");
1139
+ const outputDir = path11__default.default.join(themePath, "dist");
1140
+ await fs9__default.default.mkdir(outputDir, { recursive: true });
1141
+ const outputPath = path11__default.default.join(outputDir, "preview-runtime.js");
907
1142
  const locations = [
908
- path9__default.default.join(__dirname, "..", "preview", "preview-app.tsx"),
909
- path9__default.default.join(__dirname, "preview", "preview-app.tsx"),
910
- path9__default.default.join(__dirname, "..", "..", "src", "preview", "preview-app.tsx")
1143
+ path11__default.default.join(__dirname, "..", "preview", "preview-app.tsx"),
1144
+ path11__default.default.join(__dirname, "preview", "preview-app.tsx"),
1145
+ path11__default.default.join(__dirname, "..", "..", "src", "preview", "preview-app.tsx")
911
1146
  ];
912
1147
  let previewEntryPath = null;
913
1148
  for (const loc of locations) {
914
1149
  try {
915
- await fs8__default.default.access(loc);
1150
+ await fs9__default.default.access(loc);
916
1151
  previewEntryPath = loc;
917
1152
  break;
918
1153
  } catch {
@@ -995,10 +1230,10 @@ ${locations.join("\n")}`
995
1230
  if (!lucideScanned) {
996
1231
  lucideScanned = true;
997
1232
  const coreSrcCandidates = [
998
- path9__default.default.join(themePath, "node_modules", "@onexapis", "core", "src"),
999
- path9__default.default.join(themePath, "..", "..", "packages", "core", "src"),
1233
+ path11__default.default.join(themePath, "node_modules", "@onexapis", "core", "src"),
1234
+ path11__default.default.join(themePath, "..", "..", "packages", "core", "src"),
1000
1235
  // monorepo sibling
1001
- path9__default.default.join(
1236
+ path11__default.default.join(
1002
1237
  __dirname,
1003
1238
  "..",
1004
1239
  "..",
@@ -1013,7 +1248,7 @@ ${locations.join("\n")}`
1013
1248
  let coreSourceDir = null;
1014
1249
  for (const candidate of coreSrcCandidates) {
1015
1250
  try {
1016
- await fs8__default.default.access(candidate);
1251
+ await fs9__default.default.access(candidate);
1017
1252
  coreSourceDir = candidate;
1018
1253
  break;
1019
1254
  } catch {
@@ -1032,21 +1267,21 @@ ${locations.join("\n")}`
1032
1267
  }
1033
1268
  } else {
1034
1269
  const coreDistCandidates = [
1035
- path9__default.default.join(themePath, "node_modules", "@onexapis", "core", "dist")
1270
+ path11__default.default.join(themePath, "node_modules", "@onexapis", "core", "dist")
1036
1271
  ];
1037
1272
  const resolvedDist = await resolveNodeModulesFile(
1038
1273
  __dirname,
1039
- path9__default.default.join("@onexapis", "core", "dist")
1274
+ path11__default.default.join("@onexapis", "core", "dist")
1040
1275
  );
1041
1276
  if (resolvedDist) coreDistCandidates.push(resolvedDist);
1042
1277
  for (const candidate of coreDistCandidates) {
1043
1278
  try {
1044
- await fs8__default.default.access(candidate);
1279
+ await fs9__default.default.access(candidate);
1045
1280
  const mjsFiles = await glob.glob("*.mjs", { cwd: candidate });
1046
1281
  const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']lucide-react["']/g;
1047
1282
  for (const file of mjsFiles) {
1048
- const content = await fs8__default.default.readFile(
1049
- path9__default.default.join(candidate, file),
1283
+ const content = await fs9__default.default.readFile(
1284
+ path11__default.default.join(candidate, file),
1050
1285
  "utf-8"
1051
1286
  );
1052
1287
  for (const match of content.matchAll(importRegex)) {
@@ -1101,7 +1336,7 @@ export default new Proxy({}, { get: (_, name) => name === '__esModule' ? true :
1101
1336
  const req = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.js', document.baseURI).href)) || __filename);
1102
1337
  const cjsPath = req.resolve("framer-motion");
1103
1338
  const pkgDir = cjsPath.replace(/[/\\]dist[/\\].*$/, "");
1104
- const esmEntry = path9__default.default.join(pkgDir, "dist", "es", "index.mjs");
1339
+ const esmEntry = path11__default.default.join(pkgDir, "dist", "es", "index.mjs");
1105
1340
  const { existsSync } = await import('fs');
1106
1341
  if (existsSync(esmEntry)) {
1107
1342
  return { path: esmEntry, namespace: "file" };
@@ -1200,8 +1435,8 @@ export function headers() { return new Headers(); }
1200
1435
  });
1201
1436
  }
1202
1437
  };
1203
- const shimPath = path9__default.default.join(outputDir, ".process-shim-preview.js");
1204
- await fs8__default.default.writeFile(shimPath, PROCESS_SHIM);
1438
+ const shimPath = path11__default.default.join(outputDir, ".process-shim-preview.js");
1439
+ await fs9__default.default.writeFile(shimPath, PROCESS_SHIM);
1205
1440
  await esbuild__namespace.build({
1206
1441
  entryPoints: [previewEntryPath],
1207
1442
  bundle: true,
@@ -1236,7 +1471,7 @@ export function headers() { return new Headers(); }
1236
1471
  }
1237
1472
  });
1238
1473
  try {
1239
- await fs8__default.default.unlink(shimPath);
1474
+ await fs9__default.default.unlink(shimPath);
1240
1475
  } catch {
1241
1476
  }
1242
1477
  return outputPath;
@@ -1245,6 +1480,8 @@ var PROCESS_SHIM, reactGlobalPlugin, reactQueryGlobalPlugin;
1245
1480
  var init_compile_theme = __esm({
1246
1481
  "src/utils/compile-theme.ts"() {
1247
1482
  init_logger();
1483
+ init_extract_schemas();
1484
+ init_scan_theme_assets();
1248
1485
  PROCESS_SHIM = `
1249
1486
  if (typeof process === "undefined") {
1250
1487
  globalThis.process = {
@@ -1400,7 +1637,7 @@ __export(dev_server_exports, {
1400
1637
  });
1401
1638
  function createDevServer(options) {
1402
1639
  const clients = /* @__PURE__ */ new Set();
1403
- const themeDataPath = path9__default.default.join(options.distDir, "theme-data.json");
1640
+ const themeDataPath = path11__default.default.join(options.distDir, "theme-data.json");
1404
1641
  const server = http__default.default.createServer((req, res) => {
1405
1642
  res.setHeader("Access-Control-Allow-Origin", "*");
1406
1643
  res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
@@ -1426,8 +1663,8 @@ function createDevServer(options) {
1426
1663
  if (pathname.startsWith("/_assets/")) {
1427
1664
  const parts = pathname.replace(/^\/_assets\//, "").split("/");
1428
1665
  const assetSubpath = parts.slice(1).join("/");
1429
- const assetPath = path9__default.default.join(options.themePath, "assets", assetSubpath);
1430
- if (!assetPath.startsWith(path9__default.default.join(options.themePath, "assets"))) {
1666
+ const assetPath = path11__default.default.join(options.themePath, "assets", assetSubpath);
1667
+ if (!assetPath.startsWith(path11__default.default.join(options.themePath, "assets"))) {
1431
1668
  res.writeHead(403);
1432
1669
  res.end("Forbidden");
1433
1670
  return;
@@ -1438,8 +1675,8 @@ function createDevServer(options) {
1438
1675
  if (pathname.startsWith("/themes/")) {
1439
1676
  const match = pathname.match(/^\/themes\/[^/]+\/assets\/(.+)/);
1440
1677
  if (match) {
1441
- const assetPath = path9__default.default.join(options.themePath, "assets", match[1]);
1442
- if (!assetPath.startsWith(path9__default.default.join(options.themePath, "assets"))) {
1678
+ const assetPath = path11__default.default.join(options.themePath, "assets", match[1]);
1679
+ if (!assetPath.startsWith(path11__default.default.join(options.themePath, "assets"))) {
1443
1680
  res.writeHead(403);
1444
1681
  res.end("Forbidden");
1445
1682
  return;
@@ -1451,26 +1688,26 @@ function createDevServer(options) {
1451
1688
  if (pathname.startsWith("/assets/")) {
1452
1689
  const subpath = pathname.replace(/^\/assets\//, "");
1453
1690
  const segments = subpath.split("/");
1454
- const assetsBase = path9__default.default.join(options.themePath, "assets");
1691
+ const assetsBase = path11__default.default.join(options.themePath, "assets");
1455
1692
  let assetPath;
1456
1693
  if (segments[0] === options.themeName || segments[0] === options.themeName.replace(/^my-/, "")) {
1457
- assetPath = path9__default.default.join(assetsBase, segments.slice(1).join("/"));
1694
+ assetPath = path11__default.default.join(assetsBase, segments.slice(1).join("/"));
1458
1695
  } else {
1459
- assetPath = path9__default.default.join(assetsBase, subpath);
1696
+ assetPath = path11__default.default.join(assetsBase, subpath);
1460
1697
  }
1461
1698
  if (assetPath.startsWith(assetsBase) && fs3__default.default.existsSync(assetPath)) {
1462
1699
  serveFile(res, assetPath);
1463
1700
  return;
1464
1701
  }
1465
1702
  if (segments.length > 1) {
1466
- const fallbackPath = path9__default.default.join(assetsBase, segments.slice(1).join("/"));
1703
+ const fallbackPath = path11__default.default.join(assetsBase, segments.slice(1).join("/"));
1467
1704
  if (fallbackPath.startsWith(assetsBase) && fs3__default.default.existsSync(fallbackPath)) {
1468
1705
  serveFile(res, fallbackPath);
1469
1706
  return;
1470
1707
  }
1471
1708
  }
1472
1709
  }
1473
- const filePath = path9__default.default.join(options.distDir, pathname);
1710
+ const filePath = path11__default.default.join(options.distDir, pathname);
1474
1711
  if (!filePath.startsWith(options.distDir)) {
1475
1712
  res.writeHead(403);
1476
1713
  res.end("Forbidden");
@@ -1513,7 +1750,7 @@ function serveFile(res, filePath) {
1513
1750
  res.end("Not Found");
1514
1751
  return;
1515
1752
  }
1516
- const ext = path9__default.default.extname(filePath);
1753
+ const ext = path11__default.default.extname(filePath);
1517
1754
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
1518
1755
  const content = fs3__default.default.readFileSync(filePath);
1519
1756
  res.writeHead(200, { "Content-Type": contentType });
@@ -1634,18 +1871,18 @@ async function renderTemplate(templatePath, data) {
1634
1871
  return ejs__default.default.render(template, data);
1635
1872
  }
1636
1873
  async function writeFile(filePath, content) {
1637
- await fs__default.default.ensureDir(path9__default.default.dirname(filePath));
1874
+ await fs__default.default.ensureDir(path11__default.default.dirname(filePath));
1638
1875
  await fs__default.default.writeFile(filePath, content, "utf-8");
1639
1876
  }
1640
1877
  function getTemplatesDir() {
1641
1878
  const locations = [
1642
- path9__default.default.join(__dirname, "../../templates"),
1879
+ path11__default.default.join(__dirname, "../../templates"),
1643
1880
  // Development
1644
- path9__default.default.join(__dirname, "../templates"),
1881
+ path11__default.default.join(__dirname, "../templates"),
1645
1882
  // Production (dist/)
1646
- path9__default.default.join(process.cwd(), "templates"),
1883
+ path11__default.default.join(process.cwd(), "templates"),
1647
1884
  // Fallback
1648
- path9__default.default.join(process.cwd(), "packages/cli/templates")
1885
+ path11__default.default.join(process.cwd(), "packages/cli/templates")
1649
1886
  // Monorepo
1650
1887
  ];
1651
1888
  for (const location of locations) {
@@ -1657,7 +1894,7 @@ function getTemplatesDir() {
1657
1894
  }
1658
1895
  async function copyTemplate(templateName, targetDir, data) {
1659
1896
  const templatesDir = getTemplatesDir();
1660
- const templateDir = path9__default.default.join(templatesDir, templateName);
1897
+ const templateDir = path11__default.default.join(templatesDir, templateName);
1661
1898
  if (!fs__default.default.existsSync(templateDir)) {
1662
1899
  throw new Error(
1663
1900
  `Template "${templateName}" not found at ${templateDir}. Available templates: ${fs__default.default.readdirSync(templatesDir).join(", ")}`
@@ -1666,8 +1903,8 @@ async function copyTemplate(templateName, targetDir, data) {
1666
1903
  await fs__default.default.ensureDir(targetDir);
1667
1904
  const files = await fs__default.default.readdir(templateDir);
1668
1905
  for (const file of files) {
1669
- const templatePath = path9__default.default.join(templateDir, file);
1670
- const targetPath = path9__default.default.join(targetDir, file);
1906
+ const templatePath = path11__default.default.join(templateDir, file);
1907
+ const targetPath = path11__default.default.join(targetDir, file);
1671
1908
  const stat = await fs__default.default.stat(templatePath);
1672
1909
  if (stat.isDirectory()) {
1673
1910
  await copyTemplateDir(templatePath, targetPath, data);
@@ -1684,8 +1921,8 @@ async function copyTemplateDir(templateDir, targetDir, data) {
1684
1921
  await fs__default.default.ensureDir(targetDir);
1685
1922
  const files = await fs__default.default.readdir(templateDir);
1686
1923
  for (const file of files) {
1687
- const templatePath = path9__default.default.join(templateDir, file);
1688
- const targetPath = path9__default.default.join(targetDir, file);
1924
+ const templatePath = path11__default.default.join(templateDir, file);
1925
+ const targetPath = path11__default.default.join(targetDir, file);
1689
1926
  const stat = await fs__default.default.stat(templatePath);
1690
1927
  if (stat.isDirectory()) {
1691
1928
  await copyTemplateDir(templatePath, targetPath, data);
@@ -1700,32 +1937,32 @@ async function copyTemplateDir(templateDir, targetDir, data) {
1700
1937
  }
1701
1938
  function getProjectRoot() {
1702
1939
  let currentDir = process.cwd();
1703
- while (currentDir !== path9__default.default.parse(currentDir).root) {
1704
- const packageJsonPath = path9__default.default.join(currentDir, "package.json");
1940
+ while (currentDir !== path11__default.default.parse(currentDir).root) {
1941
+ const packageJsonPath = path11__default.default.join(currentDir, "package.json");
1705
1942
  if (fs__default.default.existsSync(packageJsonPath)) {
1706
1943
  const packageJson = fs__default.default.readJsonSync(packageJsonPath);
1707
- if (packageJson.workspaces || fs__default.default.existsSync(path9__default.default.join(currentDir, "src/themes")) || fs__default.default.existsSync(path9__default.default.join(currentDir, "themes"))) {
1944
+ if (packageJson.workspaces || fs__default.default.existsSync(path11__default.default.join(currentDir, "src/themes")) || fs__default.default.existsSync(path11__default.default.join(currentDir, "themes"))) {
1708
1945
  return currentDir;
1709
1946
  }
1710
1947
  }
1711
- currentDir = path9__default.default.dirname(currentDir);
1948
+ currentDir = path11__default.default.dirname(currentDir);
1712
1949
  }
1713
1950
  return process.cwd();
1714
1951
  }
1715
1952
  function getThemesDir() {
1716
1953
  const root = getProjectRoot();
1717
- if (fs__default.default.existsSync(path9__default.default.join(root, "themes")))
1718
- return path9__default.default.join(root, "themes");
1719
- if (fs__default.default.existsSync(path9__default.default.join(root, "src/themes")))
1720
- return path9__default.default.join(root, "src/themes");
1721
- return path9__default.default.dirname(root);
1954
+ if (fs__default.default.existsSync(path11__default.default.join(root, "themes")))
1955
+ return path11__default.default.join(root, "themes");
1956
+ if (fs__default.default.existsSync(path11__default.default.join(root, "src/themes")))
1957
+ return path11__default.default.join(root, "src/themes");
1958
+ return path11__default.default.dirname(root);
1722
1959
  }
1723
1960
  function getFeaturesDir() {
1724
- return path9__default.default.join(getProjectRoot(), "src/features");
1961
+ return path11__default.default.join(getProjectRoot(), "src/features");
1725
1962
  }
1726
1963
  function isOneXProject() {
1727
1964
  const root = getProjectRoot();
1728
- return fs__default.default.existsSync(path9__default.default.join(root, "themes")) || fs__default.default.existsSync(path9__default.default.join(root, "src/themes")) || fs__default.default.existsSync(path9__default.default.join(root, "theme.config.ts")) || fs__default.default.existsSync(path9__default.default.join(root, "bundle-entry.ts"));
1965
+ return fs__default.default.existsSync(path11__default.default.join(root, "themes")) || fs__default.default.existsSync(path11__default.default.join(root, "src/themes")) || fs__default.default.existsSync(path11__default.default.join(root, "theme.config.ts")) || fs__default.default.existsSync(path11__default.default.join(root, "bundle-entry.ts"));
1729
1966
  }
1730
1967
  function ensureOneXProject() {
1731
1968
  if (!isOneXProject()) {
@@ -1741,13 +1978,13 @@ function listThemes() {
1741
1978
  return [];
1742
1979
  }
1743
1980
  return fs__default.default.readdirSync(themesDir).filter((name) => {
1744
- const themePath = path9__default.default.join(themesDir, name);
1745
- return fs__default.default.statSync(themePath).isDirectory() && (fs__default.default.existsSync(path9__default.default.join(themePath, "theme.config.ts")) || fs__default.default.existsSync(path9__default.default.join(themePath, "bundle-entry.ts")) || fs__default.default.existsSync(path9__default.default.join(themePath, "manifest.ts")));
1981
+ const themePath = path11__default.default.join(themesDir, name);
1982
+ return fs__default.default.statSync(themePath).isDirectory() && (fs__default.default.existsSync(path11__default.default.join(themePath, "theme.config.ts")) || fs__default.default.existsSync(path11__default.default.join(themePath, "bundle-entry.ts")) || fs__default.default.existsSync(path11__default.default.join(themePath, "manifest.ts")));
1746
1983
  });
1747
1984
  }
1748
1985
  function themeExists(themeName) {
1749
- const themePath = path9__default.default.join(getThemesDir(), themeName);
1750
- return fs__default.default.existsSync(themePath) && (fs__default.default.existsSync(path9__default.default.join(themePath, "theme.config.ts")) || fs__default.default.existsSync(path9__default.default.join(themePath, "bundle-entry.ts")) || fs__default.default.existsSync(path9__default.default.join(themePath, "manifest.ts")));
1986
+ const themePath = path11__default.default.join(getThemesDir(), themeName);
1987
+ return fs__default.default.existsSync(themePath) && (fs__default.default.existsSync(path11__default.default.join(themePath, "theme.config.ts")) || fs__default.default.existsSync(path11__default.default.join(themePath, "bundle-entry.ts")) || fs__default.default.existsSync(path11__default.default.join(themePath, "manifest.ts")));
1751
1988
  }
1752
1989
  function detectPackageManager() {
1753
1990
  const userAgent = process.env.npm_config_user_agent || "";
@@ -1755,9 +1992,9 @@ function detectPackageManager() {
1755
1992
  if (userAgent.includes("yarn")) return "yarn";
1756
1993
  if (userAgent.includes("bun")) return "bun";
1757
1994
  const cwd = process.cwd();
1758
- if (fs__default.default.existsSync(path9__default.default.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
1759
- if (fs__default.default.existsSync(path9__default.default.join(cwd, "yarn.lock"))) return "yarn";
1760
- if (fs__default.default.existsSync(path9__default.default.join(cwd, "bun.lockb"))) return "bun";
1995
+ if (fs__default.default.existsSync(path11__default.default.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
1996
+ if (fs__default.default.existsSync(path11__default.default.join(cwd, "yarn.lock"))) return "yarn";
1997
+ if (fs__default.default.existsSync(path11__default.default.join(cwd, "bun.lockb"))) return "bun";
1761
1998
  return "npm";
1762
1999
  }
1763
2000
  async function installDependencies(projectPath, packageManager = "npm") {
@@ -1806,22 +2043,45 @@ function getValidCategories() {
1806
2043
  "contact"
1807
2044
  ];
1808
2045
  }
1809
- var AUTH_DIR = path9__default.default.join(os__default.default.homedir(), ".onexthm");
1810
- var AUTH_FILE = path9__default.default.join(AUTH_DIR, "auth.json");
1811
- function getApiUrl() {
1812
- return process.env.ONEXTHM_API_URL || "https://platform-dev.onexeos.com";
2046
+ var AUTH_DIR = path11__default.default.join(os__default.default.homedir(), ".onexthm");
2047
+ var ENV_URLS = {
2048
+ dev: "https://platform-dev.onexeos.com",
2049
+ staging: "https://platform-staging.onexeos.com",
2050
+ prod: "https://platform-apis.onexeos.com"
2051
+ };
2052
+ function getAuthFile(env = "dev") {
2053
+ const newFile = path11__default.default.join(AUTH_DIR, `auth-${env}.json`);
2054
+ if (env === "dev") {
2055
+ const legacyFile = path11__default.default.join(AUTH_DIR, "auth.json");
2056
+ if (fs__default.default.existsSync(legacyFile) && !fs__default.default.existsSync(newFile)) {
2057
+ try {
2058
+ fs__default.default.moveSync(legacyFile, newFile);
2059
+ } catch {
2060
+ try {
2061
+ fs__default.default.copySync(legacyFile, newFile);
2062
+ fs__default.default.removeSync(legacyFile);
2063
+ } catch {
2064
+ }
2065
+ }
2066
+ }
2067
+ }
2068
+ return newFile;
2069
+ }
2070
+ function getApiUrl(env = "dev") {
2071
+ return process.env.ONEXTHM_API_URL || ENV_URLS[env];
1813
2072
  }
1814
- async function saveAuthTokens(tokens) {
2073
+ async function saveAuthTokens(tokens, env = "dev") {
1815
2074
  await fs__default.default.ensureDir(AUTH_DIR);
1816
2075
  const key = getMachineKey();
1817
2076
  const data = JSON.stringify(tokens);
1818
2077
  const encrypted = encrypt(data, key);
1819
- await fs__default.default.writeFile(AUTH_FILE, encrypted, "utf-8");
2078
+ await fs__default.default.writeFile(getAuthFile(env), encrypted, "utf-8");
1820
2079
  }
1821
- function loadAuthTokens() {
2080
+ function loadAuthTokens(env = "dev") {
1822
2081
  try {
1823
- if (!fs__default.default.existsSync(AUTH_FILE)) return null;
1824
- const encrypted = fs__default.default.readFileSync(AUTH_FILE, "utf-8");
2082
+ const file = getAuthFile(env);
2083
+ if (!fs__default.default.existsSync(file)) return null;
2084
+ const encrypted = fs__default.default.readFileSync(file, "utf-8");
1825
2085
  const key = getMachineKey();
1826
2086
  const data = decrypt(encrypted, key);
1827
2087
  return JSON.parse(data);
@@ -1829,34 +2089,34 @@ function loadAuthTokens() {
1829
2089
  return null;
1830
2090
  }
1831
2091
  }
1832
- async function clearAuthTokens() {
2092
+ async function clearAuthTokens(env = "dev") {
1833
2093
  try {
1834
- await fs__default.default.remove(AUTH_FILE);
2094
+ await fs__default.default.remove(getAuthFile(env));
1835
2095
  } catch {
1836
2096
  }
1837
2097
  }
1838
2098
  function isTokenExpired(tokens) {
1839
2099
  return Date.now() / 1e3 > tokens.expiresAt - 300;
1840
2100
  }
1841
- async function getValidTokens() {
1842
- const tokens = loadAuthTokens();
2101
+ async function getValidTokens(env = "dev") {
2102
+ const tokens = loadAuthTokens(env);
1843
2103
  if (!tokens) return null;
1844
2104
  if (!isTokenExpired(tokens)) return tokens;
1845
2105
  try {
1846
- const apiUrl = getApiUrl();
2106
+ const apiUrl = getApiUrl(env);
1847
2107
  const response = await fetch(`${apiUrl}/auth/refresh`, {
1848
2108
  method: "POST",
1849
2109
  headers: { "Content-Type": "application/json" },
1850
2110
  body: JSON.stringify({ refresh_token: tokens.refreshToken })
1851
2111
  });
1852
2112
  if (!response.ok) {
1853
- await clearAuthTokens();
2113
+ await clearAuthTokens(env);
1854
2114
  return null;
1855
2115
  }
1856
2116
  const data = await response.json();
1857
2117
  const body = data.statusCode ? data.body : data;
1858
2118
  if (!body.IdToken) {
1859
- await clearAuthTokens();
2119
+ await clearAuthTokens(env);
1860
2120
  return null;
1861
2121
  }
1862
2122
  const refreshed = {
@@ -1865,17 +2125,19 @@ async function getValidTokens() {
1865
2125
  idToken: body.IdToken,
1866
2126
  expiresAt: Math.floor(Date.now() / 1e3) + (body.ExpiresIn || 3600)
1867
2127
  };
1868
- await saveAuthTokens(refreshed);
2128
+ await saveAuthTokens(refreshed, env);
1869
2129
  return refreshed;
1870
2130
  } catch {
1871
- await clearAuthTokens();
2131
+ await clearAuthTokens(env);
1872
2132
  return null;
1873
2133
  }
1874
2134
  }
1875
- async function authenticatedFetch(url, init) {
1876
- const tokens = await getValidTokens();
2135
+ async function authenticatedFetch(url, init, env = "dev") {
2136
+ const tokens = await getValidTokens(env);
1877
2137
  if (!tokens) {
1878
- throw new Error("Not logged in. Run: onexthm login");
2138
+ throw new Error(
2139
+ `Not logged in to ${env} environment. Run: onexthm login --env ${env}`
2140
+ );
1879
2141
  }
1880
2142
  const headers = new Headers(init?.headers);
1881
2143
  headers.set("Authorization", `Bearer ${tokens.idToken}`);
@@ -1941,7 +2203,7 @@ async function initCommand(projectName, options = {}) {
1941
2203
  if (!validateThemeName(kebabName)) {
1942
2204
  return "Invalid project name. Use lowercase letters, numbers, and hyphens only.";
1943
2205
  }
1944
- if (fs3__default.default.existsSync(path9__default.default.join(process.cwd(), kebabName))) {
2206
+ if (fs3__default.default.existsSync(path11__default.default.join(process.cwd(), kebabName))) {
1945
2207
  return `Directory "${kebabName}" already exists`;
1946
2208
  }
1947
2209
  return true;
@@ -1952,14 +2214,14 @@ async function initCommand(projectName, options = {}) {
1952
2214
  } else {
1953
2215
  name = toKebabCase(projectName);
1954
2216
  }
1955
- const projectPath = path9__default.default.join(process.cwd(), name);
2217
+ const projectPath = path11__default.default.join(process.cwd(), name);
1956
2218
  if (fs3__default.default.existsSync(projectPath)) {
1957
2219
  logger.error(`Directory "${name}" already exists.`);
1958
2220
  process.exit(1);
1959
2221
  }
1960
2222
  if (!options.yes) {
1961
2223
  try {
1962
- const apiUrl = getApiUrl();
2224
+ const apiUrl = getApiUrl(options.env ?? "dev");
1963
2225
  const controller = new AbortController();
1964
2226
  const timeout = setTimeout(() => controller.abort(), 3e3);
1965
2227
  const response = await fetch(
@@ -2068,7 +2330,7 @@ async function initCommand(projectName, options = {}) {
2068
2330
  description,
2069
2331
  author
2070
2332
  );
2071
- const mcpJsonPath = path9__default.default.join(projectPath, ".mcp.json");
2333
+ const mcpJsonPath = path11__default.default.join(projectPath, ".mcp.json");
2072
2334
  if (fs3__default.default.existsSync(mcpJsonPath)) {
2073
2335
  let mcpContent = fs3__default.default.readFileSync(mcpJsonPath, "utf-8");
2074
2336
  if (figmaApiKey) {
@@ -2148,7 +2410,7 @@ async function initCommand(projectName, options = {}) {
2148
2410
  }
2149
2411
  }
2150
2412
  async function renameThemeInFiles(projectPath, themeName, displayName, description, author) {
2151
- const configPath = path9__default.default.join(projectPath, "theme.config.ts");
2413
+ const configPath = path11__default.default.join(projectPath, "theme.config.ts");
2152
2414
  if (fs3__default.default.existsSync(configPath)) {
2153
2415
  let content = fs3__default.default.readFileSync(configPath, "utf-8");
2154
2416
  content = content.replace(
@@ -2161,7 +2423,7 @@ async function renameThemeInFiles(projectPath, themeName, displayName, descripti
2161
2423
  );
2162
2424
  fs3__default.default.writeFileSync(configPath, content, "utf-8");
2163
2425
  }
2164
- const pkgPath = path9__default.default.join(projectPath, "package.json");
2426
+ const pkgPath = path11__default.default.join(projectPath, "package.json");
2165
2427
  if (fs3__default.default.existsSync(pkgPath)) {
2166
2428
  let content = fs3__default.default.readFileSync(pkgPath, "utf-8");
2167
2429
  content = content.replace(
@@ -2183,10 +2445,10 @@ async function createSectionCommand(name, options) {
2183
2445
  ensureOneXProject();
2184
2446
  if (!options.theme) {
2185
2447
  const isStandaloneTheme = ["theme.config.ts", "bundle-entry.ts"].some(
2186
- (f) => fs__default.default.existsSync(path9__default.default.join(process.cwd(), f))
2448
+ (f) => fs__default.default.existsSync(path11__default.default.join(process.cwd(), f))
2187
2449
  );
2188
2450
  if (isStandaloneTheme) {
2189
- options.theme = path9__default.default.basename(process.cwd());
2451
+ options.theme = path11__default.default.basename(process.cwd());
2190
2452
  }
2191
2453
  }
2192
2454
  const sectionName = toKebabCase(name);
@@ -2249,35 +2511,35 @@ async function createSectionCommand(name, options) {
2249
2511
  };
2250
2512
  logger.startSpinner("Creating section files...");
2251
2513
  try {
2252
- const themePath = path9__default.default.join(getThemesDir(), themeName);
2253
- const sectionPath = path9__default.default.join(themePath, "sections", sectionName);
2514
+ const themePath = path11__default.default.join(getThemesDir(), themeName);
2515
+ const sectionPath = path11__default.default.join(themePath, "sections", sectionName);
2254
2516
  const schemaContent = generateSectionSchema(data);
2255
2517
  await writeFile(
2256
- path9__default.default.join(sectionPath, `${sectionName}.schema.ts`),
2518
+ path11__default.default.join(sectionPath, `${sectionName}.schema.ts`),
2257
2519
  schemaContent
2258
2520
  );
2259
2521
  if (createTemplate) {
2260
2522
  const templateContent = generateSectionTemplate(data);
2261
2523
  await writeFile(
2262
- path9__default.default.join(sectionPath, `${sectionName}-default.tsx`),
2524
+ path11__default.default.join(sectionPath, `${sectionName}-default.tsx`),
2263
2525
  templateContent
2264
2526
  );
2265
2527
  }
2266
2528
  const indexContent = generateSectionIndex(data, createTemplate);
2267
- await writeFile(path9__default.default.join(sectionPath, "index.ts"), indexContent);
2529
+ await writeFile(path11__default.default.join(sectionPath, "index.ts"), indexContent);
2268
2530
  logger.stopSpinner(true, "Section files created successfully!");
2269
2531
  logger.newLine();
2270
2532
  logger.section("Next steps:");
2271
2533
  logger.log(
2272
- ` 1. Edit schema: ${path9__default.default.relative(process.cwd(), path9__default.default.join(sectionPath, `${sectionName}.schema.ts`))}`
2534
+ ` 1. Edit schema: ${path11__default.default.relative(process.cwd(), path11__default.default.join(sectionPath, `${sectionName}.schema.ts`))}`
2273
2535
  );
2274
2536
  if (createTemplate) {
2275
2537
  logger.log(
2276
- ` 2. Edit template: ${path9__default.default.relative(process.cwd(), path9__default.default.join(sectionPath, `${sectionName}-default.tsx`))}`
2538
+ ` 2. Edit template: ${path11__default.default.relative(process.cwd(), path11__default.default.join(sectionPath, `${sectionName}-default.tsx`))}`
2277
2539
  );
2278
2540
  }
2279
2541
  logger.log(
2280
- ` 3. Add to theme manifest: ${path9__default.default.relative(process.cwd(), path9__default.default.join(themePath, "manifest.ts"))}`
2542
+ ` 3. Add to theme manifest: ${path11__default.default.relative(process.cwd(), path11__default.default.join(themePath, "manifest.ts"))}`
2281
2543
  );
2282
2544
  logger.newLine();
2283
2545
  logger.success("Section created successfully!");
@@ -2425,10 +2687,10 @@ async function createBlockCommand(name, options) {
2425
2687
  ensureOneXProject();
2426
2688
  if (!options.theme) {
2427
2689
  const isStandaloneTheme = ["theme.config.ts", "bundle-entry.ts"].some(
2428
- (f) => fs__default.default.existsSync(path9__default.default.join(process.cwd(), f))
2690
+ (f) => fs__default.default.existsSync(path11__default.default.join(process.cwd(), f))
2429
2691
  );
2430
2692
  if (isStandaloneTheme) {
2431
- options.theme = path9__default.default.basename(process.cwd());
2693
+ options.theme = path11__default.default.basename(process.cwd());
2432
2694
  }
2433
2695
  }
2434
2696
  const blockName = toKebabCase(name);
@@ -2503,24 +2765,24 @@ async function createBlockCommand(name, options) {
2503
2765
  };
2504
2766
  logger.startSpinner("Creating block files...");
2505
2767
  try {
2506
- const blockPath = scope === "shared" ? path9__default.default.join(getFeaturesDir(), "blocks", blockName) : path9__default.default.join(getThemesDir(), themeName, "blocks", blockName);
2768
+ const blockPath = scope === "shared" ? path11__default.default.join(getFeaturesDir(), "blocks", blockName) : path11__default.default.join(getThemesDir(), themeName, "blocks", blockName);
2507
2769
  const schemaContent = generateBlockSchema(data);
2508
2770
  await writeFile(
2509
- path9__default.default.join(blockPath, `${blockName}.schema.ts`),
2771
+ path11__default.default.join(blockPath, `${blockName}.schema.ts`),
2510
2772
  schemaContent
2511
2773
  );
2512
2774
  const componentContent = generateBlockComponent(data);
2513
- await writeFile(path9__default.default.join(blockPath, `${blockName}.tsx`), componentContent);
2775
+ await writeFile(path11__default.default.join(blockPath, `${blockName}.tsx`), componentContent);
2514
2776
  const indexContent = generateBlockIndex(data);
2515
- await writeFile(path9__default.default.join(blockPath, "index.ts"), indexContent);
2777
+ await writeFile(path11__default.default.join(blockPath, "index.ts"), indexContent);
2516
2778
  logger.stopSpinner(true, "Block files created successfully!");
2517
2779
  logger.newLine();
2518
2780
  logger.section("Next steps:");
2519
2781
  logger.log(
2520
- ` 1. Edit schema: ${path9__default.default.relative(process.cwd(), path9__default.default.join(blockPath, `${blockName}.schema.ts`))}`
2782
+ ` 1. Edit schema: ${path11__default.default.relative(process.cwd(), path11__default.default.join(blockPath, `${blockName}.schema.ts`))}`
2521
2783
  );
2522
2784
  logger.log(
2523
- ` 2. Edit component: ${path9__default.default.relative(process.cwd(), path9__default.default.join(blockPath, `${blockName}.tsx`))}`
2785
+ ` 2. Edit component: ${path11__default.default.relative(process.cwd(), path11__default.default.join(blockPath, `${blockName}.tsx`))}`
2524
2786
  );
2525
2787
  logger.log(
2526
2788
  ` 3. Register in block registry: src/lib/registry/block-registry.ts`
@@ -2698,31 +2960,31 @@ async function createComponentCommand(name, options) {
2698
2960
  };
2699
2961
  logger.startSpinner("Creating component files...");
2700
2962
  try {
2701
- const componentPath = path9__default.default.join(
2963
+ const componentPath = path11__default.default.join(
2702
2964
  getFeaturesDir(),
2703
2965
  "components",
2704
2966
  componentName
2705
2967
  );
2706
2968
  const schemaContent = generateComponentSchema(data);
2707
2969
  await writeFile(
2708
- path9__default.default.join(componentPath, `${componentName}.schema.ts`),
2970
+ path11__default.default.join(componentPath, `${componentName}.schema.ts`),
2709
2971
  schemaContent
2710
2972
  );
2711
2973
  const componentContent = generateComponent(data);
2712
2974
  await writeFile(
2713
- path9__default.default.join(componentPath, `${componentName}.tsx`),
2975
+ path11__default.default.join(componentPath, `${componentName}.tsx`),
2714
2976
  componentContent
2715
2977
  );
2716
2978
  const indexContent = generateComponentIndex(data);
2717
- await writeFile(path9__default.default.join(componentPath, "index.ts"), indexContent);
2979
+ await writeFile(path11__default.default.join(componentPath, "index.ts"), indexContent);
2718
2980
  logger.stopSpinner(true, "Component files created successfully!");
2719
2981
  logger.newLine();
2720
2982
  logger.section("Next steps:");
2721
2983
  logger.log(
2722
- ` 1. Edit schema: ${path9__default.default.relative(process.cwd(), path9__default.default.join(componentPath, `${componentName}.schema.ts`))}`
2984
+ ` 1. Edit schema: ${path11__default.default.relative(process.cwd(), path11__default.default.join(componentPath, `${componentName}.schema.ts`))}`
2723
2985
  );
2724
2986
  logger.log(
2725
- ` 2. Edit component: ${path9__default.default.relative(process.cwd(), path9__default.default.join(componentPath, `${componentName}.tsx`))}`
2987
+ ` 2. Edit component: ${path11__default.default.relative(process.cwd(), path11__default.default.join(componentPath, `${componentName}.tsx`))}`
2726
2988
  );
2727
2989
  logger.log(
2728
2990
  ` 3. Register in component registry: src/lib/registry/component-registry.ts`
@@ -2879,13 +3141,13 @@ async function listSections(themeFilter) {
2879
3141
  return;
2880
3142
  }
2881
3143
  for (const theme of themes) {
2882
- const sectionsDir = path9__default.default.join(getThemesDir(), theme, "sections");
3144
+ const sectionsDir = path11__default.default.join(getThemesDir(), theme, "sections");
2883
3145
  if (!fs__default.default.existsSync(sectionsDir)) {
2884
3146
  continue;
2885
3147
  }
2886
3148
  const sections = fs__default.default.readdirSync(sectionsDir).filter((name) => {
2887
- const sectionPath = path9__default.default.join(sectionsDir, name);
2888
- return fs__default.default.statSync(sectionPath).isDirectory() && fs__default.default.existsSync(path9__default.default.join(sectionPath, "index.ts"));
3149
+ const sectionPath = path11__default.default.join(sectionsDir, name);
3150
+ return fs__default.default.statSync(sectionPath).isDirectory() && fs__default.default.existsSync(path11__default.default.join(sectionPath, "index.ts"));
2889
3151
  });
2890
3152
  if (sections.length > 0) {
2891
3153
  logger.log(chalk4__default.default.cyan(`
@@ -2899,11 +3161,11 @@ async function listSections(themeFilter) {
2899
3161
  }
2900
3162
  async function listBlocks(themeFilter) {
2901
3163
  logger.section("\u{1F9F1} Blocks");
2902
- const sharedBlocksDir = path9__default.default.join(getFeaturesDir(), "blocks");
3164
+ const sharedBlocksDir = path11__default.default.join(getFeaturesDir(), "blocks");
2903
3165
  if (fs__default.default.existsSync(sharedBlocksDir)) {
2904
3166
  const sharedBlocks = fs__default.default.readdirSync(sharedBlocksDir).filter((name) => {
2905
- const blockPath = path9__default.default.join(sharedBlocksDir, name);
2906
- return fs__default.default.statSync(blockPath).isDirectory() && fs__default.default.existsSync(path9__default.default.join(blockPath, "index.ts"));
3167
+ const blockPath = path11__default.default.join(sharedBlocksDir, name);
3168
+ return fs__default.default.statSync(blockPath).isDirectory() && fs__default.default.existsSync(path11__default.default.join(blockPath, "index.ts"));
2907
3169
  });
2908
3170
  if (sharedBlocks.length > 0) {
2909
3171
  logger.log(chalk4__default.default.cyan("\n Shared:"));
@@ -2914,13 +3176,13 @@ async function listBlocks(themeFilter) {
2914
3176
  }
2915
3177
  const themes = themeFilter ? [themeFilter] : listThemes();
2916
3178
  for (const theme of themes) {
2917
- const blocksDir = path9__default.default.join(getThemesDir(), theme, "blocks");
3179
+ const blocksDir = path11__default.default.join(getThemesDir(), theme, "blocks");
2918
3180
  if (!fs__default.default.existsSync(blocksDir)) {
2919
3181
  continue;
2920
3182
  }
2921
3183
  const blocks = fs__default.default.readdirSync(blocksDir).filter((name) => {
2922
- const blockPath = path9__default.default.join(blocksDir, name);
2923
- return fs__default.default.statSync(blockPath).isDirectory() && fs__default.default.existsSync(path9__default.default.join(blockPath, "index.ts"));
3184
+ const blockPath = path11__default.default.join(blocksDir, name);
3185
+ return fs__default.default.statSync(blockPath).isDirectory() && fs__default.default.existsSync(path11__default.default.join(blockPath, "index.ts"));
2924
3186
  });
2925
3187
  if (blocks.length > 0) {
2926
3188
  logger.log(chalk4__default.default.cyan(`
@@ -2934,14 +3196,14 @@ async function listBlocks(themeFilter) {
2934
3196
  }
2935
3197
  async function listComponents() {
2936
3198
  logger.section("\u2699\uFE0F Components");
2937
- const componentsDir = path9__default.default.join(getFeaturesDir(), "components");
3199
+ const componentsDir = path11__default.default.join(getFeaturesDir(), "components");
2938
3200
  if (!fs__default.default.existsSync(componentsDir)) {
2939
3201
  logger.warning("No components directory found");
2940
3202
  return;
2941
3203
  }
2942
3204
  const components = fs__default.default.readdirSync(componentsDir).filter((name) => {
2943
- const componentPath = path9__default.default.join(componentsDir, name);
2944
- return fs__default.default.statSync(componentPath).isDirectory() && fs__default.default.existsSync(path9__default.default.join(componentPath, "index.ts"));
3205
+ const componentPath = path11__default.default.join(componentsDir, name);
3206
+ return fs__default.default.statSync(componentPath).isDirectory() && fs__default.default.existsSync(path11__default.default.join(componentPath, "index.ts"));
2945
3207
  });
2946
3208
  if (components.length === 0) {
2947
3209
  logger.warning("No components found");
@@ -2962,11 +3224,11 @@ async function listThemesInfo() {
2962
3224
  }
2963
3225
  logger.log("");
2964
3226
  for (const theme of themes) {
2965
- const themeDir = path9__default.default.join(getThemesDir(), theme);
3227
+ const themeDir = path11__default.default.join(getThemesDir(), theme);
2966
3228
  const candidates = ["theme.config.ts", "bundle-entry.ts", "manifest.ts"];
2967
3229
  let manifestContent = "";
2968
3230
  for (const candidate of candidates) {
2969
- const candidatePath = path9__default.default.join(themeDir, candidate);
3231
+ const candidatePath = path11__default.default.join(themeDir, candidate);
2970
3232
  if (fs__default.default.existsSync(candidatePath)) {
2971
3233
  manifestContent = fs__default.default.readFileSync(candidatePath, "utf-8");
2972
3234
  break;
@@ -3004,9 +3266,9 @@ async function validateCommand(options) {
3004
3266
  "theme.config.ts",
3005
3267
  "bundle-entry.ts",
3006
3268
  "manifest.ts"
3007
- ].some((f) => fs__default.default.existsSync(path9__default.default.join(process.cwd(), f)));
3269
+ ].some((f) => fs__default.default.existsSync(path11__default.default.join(process.cwd(), f)));
3008
3270
  if (isThemeDir2) {
3009
- themeToValidate = path9__default.default.basename(process.cwd());
3271
+ themeToValidate = path11__default.default.basename(process.cwd());
3010
3272
  logger.info(`Validating current theme: ${themeToValidate}`);
3011
3273
  } else {
3012
3274
  logger.error(
@@ -3015,11 +3277,11 @@ async function validateCommand(options) {
3015
3277
  process.exit(1);
3016
3278
  }
3017
3279
  }
3018
- const themePath = path9__default.default.join(getThemesDir(), themeToValidate);
3280
+ const themePath = path11__default.default.join(getThemesDir(), themeToValidate);
3019
3281
  logger.startSpinner("Running validation checks...");
3020
3282
  const entryFiles = ["manifest.ts", "theme.config.ts", "bundle-entry.ts"];
3021
3283
  const foundEntry = entryFiles.find(
3022
- (f) => fs__default.default.existsSync(path9__default.default.join(themePath, f))
3284
+ (f) => fs__default.default.existsSync(path11__default.default.join(themePath, f))
3023
3285
  );
3024
3286
  if (!foundEntry) {
3025
3287
  issues.push({
@@ -3029,7 +3291,7 @@ async function validateCommand(options) {
3029
3291
  });
3030
3292
  } else if (foundEntry === "manifest.ts") {
3031
3293
  const manifestContent = fs__default.default.readFileSync(
3032
- path9__default.default.join(themePath, foundEntry),
3294
+ path11__default.default.join(themePath, foundEntry),
3033
3295
  "utf-8"
3034
3296
  );
3035
3297
  if (!manifestContent.includes("export const") && !manifestContent.includes("export default") && !manifestContent.includes("export interface")) {
@@ -3040,7 +3302,7 @@ async function validateCommand(options) {
3040
3302
  });
3041
3303
  }
3042
3304
  }
3043
- const configPath = path9__default.default.join(themePath, "theme.config.ts");
3305
+ const configPath = path11__default.default.join(themePath, "theme.config.ts");
3044
3306
  if (!fs__default.default.existsSync(configPath)) {
3045
3307
  issues.push({
3046
3308
  type: "warning",
@@ -3048,7 +3310,7 @@ async function validateCommand(options) {
3048
3310
  message: "Theme config file not found (recommended)"
3049
3311
  });
3050
3312
  }
3051
- const indexPath = path9__default.default.join(themePath, "index.ts");
3313
+ const indexPath = path11__default.default.join(themePath, "index.ts");
3052
3314
  if (!fs__default.default.existsSync(indexPath)) {
3053
3315
  issues.push({
3054
3316
  type: "warning",
@@ -3056,7 +3318,7 @@ async function validateCommand(options) {
3056
3318
  message: "Index file not found (recommended)"
3057
3319
  });
3058
3320
  }
3059
- const sectionsDir = path9__default.default.join(themePath, "sections");
3321
+ const sectionsDir = path11__default.default.join(themePath, "sections");
3060
3322
  if (!fs__default.default.existsSync(sectionsDir)) {
3061
3323
  issues.push({
3062
3324
  type: "warning",
@@ -3065,16 +3327,16 @@ async function validateCommand(options) {
3065
3327
  });
3066
3328
  } else {
3067
3329
  const sections = fs__default.default.readdirSync(sectionsDir).filter(
3068
- (name) => fs__default.default.statSync(path9__default.default.join(sectionsDir, name)).isDirectory()
3330
+ (name) => fs__default.default.statSync(path11__default.default.join(sectionsDir, name)).isDirectory()
3069
3331
  );
3070
3332
  for (const sectionName of sections) {
3071
- const sectionPath = path9__default.default.join(sectionsDir, sectionName);
3072
- const schemaFile = path9__default.default.join(sectionPath, `${sectionName}.schema.ts`);
3073
- const defaultTemplate = path9__default.default.join(
3333
+ const sectionPath = path11__default.default.join(sectionsDir, sectionName);
3334
+ const schemaFile = path11__default.default.join(sectionPath, `${sectionName}.schema.ts`);
3335
+ const defaultTemplate = path11__default.default.join(
3074
3336
  sectionPath,
3075
3337
  `${sectionName}-default.tsx`
3076
3338
  );
3077
- const indexFile = path9__default.default.join(sectionPath, "index.ts");
3339
+ const indexFile = path11__default.default.join(sectionPath, "index.ts");
3078
3340
  if (!fs__default.default.existsSync(schemaFile)) {
3079
3341
  issues.push({
3080
3342
  type: "error",
@@ -3098,14 +3360,14 @@ async function validateCommand(options) {
3098
3360
  }
3099
3361
  }
3100
3362
  }
3101
- const blocksDir = path9__default.default.join(themePath, "blocks");
3363
+ const blocksDir = path11__default.default.join(themePath, "blocks");
3102
3364
  if (fs__default.default.existsSync(blocksDir)) {
3103
- const blocks = fs__default.default.readdirSync(blocksDir).filter((name) => fs__default.default.statSync(path9__default.default.join(blocksDir, name)).isDirectory());
3365
+ const blocks = fs__default.default.readdirSync(blocksDir).filter((name) => fs__default.default.statSync(path11__default.default.join(blocksDir, name)).isDirectory());
3104
3366
  for (const blockName of blocks) {
3105
- const blockPath = path9__default.default.join(blocksDir, blockName);
3106
- const schemaFile = path9__default.default.join(blockPath, `${blockName}.schema.ts`);
3107
- const componentFile = path9__default.default.join(blockPath, `${blockName}.tsx`);
3108
- const indexFile = path9__default.default.join(blockPath, "index.ts");
3367
+ const blockPath = path11__default.default.join(blocksDir, blockName);
3368
+ const schemaFile = path11__default.default.join(blockPath, `${blockName}.schema.ts`);
3369
+ const componentFile = path11__default.default.join(blockPath, `${blockName}.tsx`);
3370
+ const indexFile = path11__default.default.join(blockPath, "index.ts");
3109
3371
  if (!fs__default.default.existsSync(schemaFile)) {
3110
3372
  issues.push({
3111
3373
  type: "error",
@@ -3131,13 +3393,13 @@ async function validateCommand(options) {
3131
3393
  }
3132
3394
  if (fs__default.default.existsSync(sectionsDir)) {
3133
3395
  const sections = fs__default.default.readdirSync(sectionsDir).filter(
3134
- (name) => fs__default.default.statSync(path9__default.default.join(sectionsDir, name)).isDirectory()
3396
+ (name) => fs__default.default.statSync(path11__default.default.join(sectionsDir, name)).isDirectory()
3135
3397
  );
3136
3398
  for (const sectionName of sections) {
3137
- const sectionPath = path9__default.default.join(sectionsDir, sectionName);
3399
+ const sectionPath = path11__default.default.join(sectionsDir, sectionName);
3138
3400
  const tsxFiles = fs__default.default.readdirSync(sectionPath).filter((f) => f.endsWith(".tsx") && !f.endsWith(".schema.ts"));
3139
3401
  for (const tsxFile of tsxFiles) {
3140
- const filePath = path9__default.default.join(sectionPath, tsxFile);
3402
+ const filePath = path11__default.default.join(sectionPath, tsxFile);
3141
3403
  const content = fs__default.default.readFileSync(filePath, "utf-8");
3142
3404
  const relPath = `sections/${sectionName}/${tsxFile}`;
3143
3405
  if (!content.includes('"use client"') && !content.includes("'use client'")) {
@@ -3185,12 +3447,12 @@ async function validateCommand(options) {
3185
3447
  }
3186
3448
  }
3187
3449
  }
3188
- const registryPath = path9__default.default.join(themePath, "sections-registry.ts");
3189
- const bundleEntryPath = path9__default.default.join(themePath, "bundle-entry.ts");
3450
+ const registryPath = path11__default.default.join(themePath, "sections-registry.ts");
3451
+ const bundleEntryPath = path11__default.default.join(themePath, "bundle-entry.ts");
3190
3452
  const registryContent = fs__default.default.existsSync(registryPath) ? fs__default.default.readFileSync(registryPath, "utf-8") : fs__default.default.existsSync(bundleEntryPath) ? fs__default.default.readFileSync(bundleEntryPath, "utf-8") : "";
3191
3453
  if (fs__default.default.existsSync(sectionsDir) && registryContent) {
3192
3454
  const sections = fs__default.default.readdirSync(sectionsDir).filter(
3193
- (name) => fs__default.default.statSync(path9__default.default.join(sectionsDir, name)).isDirectory()
3455
+ (name) => fs__default.default.statSync(path11__default.default.join(sectionsDir, name)).isDirectory()
3194
3456
  );
3195
3457
  for (const sectionName of sections) {
3196
3458
  if (!registryContent.includes(`sections/${sectionName}`) && !registryContent.includes(`"${sectionName}"`)) {
@@ -3213,7 +3475,7 @@ async function validateCommand(options) {
3213
3475
  });
3214
3476
  }
3215
3477
  }
3216
- const pagesDir = path9__default.default.join(themePath, "pages");
3478
+ const pagesDir = path11__default.default.join(themePath, "pages");
3217
3479
  if (fs__default.default.existsSync(pagesDir)) {
3218
3480
  const allSchemaTypeSet = new Set(
3219
3481
  schemaTypes.map((s) => s.schemaType || s.folderName)
@@ -3265,9 +3527,9 @@ async function validateCommand(options) {
3265
3527
  }
3266
3528
  async function loadSchemaTypes(themePath, sectionsDir) {
3267
3529
  const results = [];
3268
- const sections = fs__default.default.readdirSync(sectionsDir).filter((name) => fs__default.default.statSync(path9__default.default.join(sectionsDir, name)).isDirectory());
3530
+ const sections = fs__default.default.readdirSync(sectionsDir).filter((name) => fs__default.default.statSync(path11__default.default.join(sectionsDir, name)).isDirectory());
3269
3531
  for (const sectionName of sections) {
3270
- const schemaFile = path9__default.default.join(
3532
+ const schemaFile = path11__default.default.join(
3271
3533
  sectionsDir,
3272
3534
  sectionName,
3273
3535
  `${sectionName}.schema.ts`
@@ -3305,7 +3567,7 @@ async function validatePageSectionTypes(pagesDir, validTypes) {
3305
3567
  const issues = [];
3306
3568
  const files = fs__default.default.readdirSync(pagesDir).filter((f) => f.match(/\.(ts|js)$/));
3307
3569
  for (const file of files) {
3308
- const content = fs__default.default.readFileSync(path9__default.default.join(pagesDir, file), "utf-8");
3570
+ const content = fs__default.default.readFileSync(path11__default.default.join(pagesDir, file), "utf-8");
3309
3571
  const pageName = file.replace(/\.(ts|js)$/, "");
3310
3572
  const sectionsMatch = content.match(/\bsections:\s*\[/);
3311
3573
  if (!sectionsMatch || sectionsMatch.index === void 0) continue;
@@ -3318,9 +3580,13 @@ async function validatePageSectionTypes(pagesDir, validTypes) {
3318
3580
  endIdx = i;
3319
3581
  }
3320
3582
  const sectionsBlock = content.slice(startIdx, endIdx);
3321
- const typeMatches = sectionsBlock.matchAll(/\btype:\s*["']([^"']+)["']/g);
3322
- for (const match of typeMatches) {
3583
+ const sectionTypeMatches = sectionsBlock.matchAll(
3584
+ /\bid:\s*["'][^"']*["'],\s*\n?\s*type:\s*["']([^"']+)["']/g
3585
+ );
3586
+ for (const match of sectionTypeMatches) {
3323
3587
  const sectionType = match[1];
3588
+ if (COMPONENT_TYPES.has(sectionType)) continue;
3589
+ if (BLOCK_TYPES.has(sectionType)) continue;
3324
3590
  if (!validTypes.has(sectionType)) {
3325
3591
  issues.push({
3326
3592
  type: "error",
@@ -3373,6 +3639,64 @@ var FIELD_TYPES = /* @__PURE__ */ new Set([
3373
3639
  "inline_richtext",
3374
3640
  "repeater"
3375
3641
  ]);
3642
+ var COMPONENT_TYPES = /* @__PURE__ */ new Set([
3643
+ "heading",
3644
+ "paragraph",
3645
+ "button",
3646
+ "image",
3647
+ "link",
3648
+ "icon",
3649
+ "badge",
3650
+ "divider",
3651
+ "spacer",
3652
+ "container",
3653
+ "grid",
3654
+ "columns",
3655
+ "card",
3656
+ "quote",
3657
+ "input",
3658
+ "textarea",
3659
+ "checkbox",
3660
+ "select",
3661
+ "video",
3662
+ "gallery",
3663
+ "alert",
3664
+ "progress",
3665
+ "rating",
3666
+ "timer",
3667
+ "list",
3668
+ "table",
3669
+ "accordion",
3670
+ "tabs",
3671
+ "code",
3672
+ "map",
3673
+ "product-card",
3674
+ "blog-card",
3675
+ "social-links",
3676
+ "hotline-contacts",
3677
+ "company-info",
3678
+ "torn-separator"
3679
+ ]);
3680
+ var BLOCK_TYPES = /* @__PURE__ */ new Set([
3681
+ "brand-feature",
3682
+ "collection-item",
3683
+ "crafting-step",
3684
+ "testimonial-item",
3685
+ "stat-item",
3686
+ "footer-link",
3687
+ "navigation-links-block",
3688
+ "policy-section",
3689
+ "core-value-card",
3690
+ "faq-item",
3691
+ "feature-item",
3692
+ "gallery-item",
3693
+ "logo-item",
3694
+ "pricing-tier",
3695
+ "service-item",
3696
+ "stat-card",
3697
+ "team-member",
3698
+ "hero-content"
3699
+ ]);
3376
3700
 
3377
3701
  // src/commands/build.ts
3378
3702
  init_logger();
@@ -3383,14 +3707,14 @@ async function buildCommand(options) {
3383
3707
  if (options.theme) {
3384
3708
  themeName = options.theme;
3385
3709
  try {
3386
- const workspaceThemePath = path9__default.default.join(getThemesDir(), themeName);
3710
+ const workspaceThemePath = path11__default.default.join(getThemesDir(), themeName);
3387
3711
  if (fs__default.default.existsSync(workspaceThemePath)) {
3388
3712
  themePath = workspaceThemePath;
3389
3713
  } else {
3390
- themePath = path9__default.default.join(process.cwd(), themeName);
3714
+ themePath = path11__default.default.join(process.cwd(), themeName);
3391
3715
  }
3392
3716
  } catch {
3393
- themePath = path9__default.default.join(process.cwd(), themeName);
3717
+ themePath = path11__default.default.join(process.cwd(), themeName);
3394
3718
  }
3395
3719
  if (!fs__default.default.existsSync(themePath)) {
3396
3720
  logger.error(`Theme "${themeName}" not found.`);
@@ -3401,10 +3725,10 @@ async function buildCommand(options) {
3401
3725
  "theme.config.ts",
3402
3726
  "bundle-entry.ts",
3403
3727
  "manifest.ts"
3404
- ].some((f) => fs__default.default.existsSync(path9__default.default.join(process.cwd(), f)));
3728
+ ].some((f) => fs__default.default.existsSync(path11__default.default.join(process.cwd(), f)));
3405
3729
  if (isThemeDir2) {
3406
3730
  themePath = process.cwd();
3407
- themeName = path9__default.default.basename(themePath);
3731
+ themeName = path11__default.default.basename(themePath);
3408
3732
  logger.info(`Building current theme: ${themeName}`);
3409
3733
  } else {
3410
3734
  logger.error(
@@ -3413,7 +3737,7 @@ async function buildCommand(options) {
3413
3737
  process.exit(1);
3414
3738
  }
3415
3739
  }
3416
- const packageJsonPath = path9__default.default.join(themePath, "package.json");
3740
+ const packageJsonPath = path11__default.default.join(themePath, "package.json");
3417
3741
  const hasPkgJson = fs__default.default.existsSync(packageJsonPath);
3418
3742
  if (!hasPkgJson) {
3419
3743
  logger.warning(
@@ -3469,9 +3793,9 @@ async function buildCommand(options) {
3469
3793
  logger.success("\u2713 Theme built successfully!");
3470
3794
  logger.newLine();
3471
3795
  logger.info(`Theme: ${themeName}`);
3472
- const distPath = path9__default.default.join(themePath, "dist");
3796
+ const distPath = path11__default.default.join(themePath, "dist");
3473
3797
  if (fs__default.default.existsSync(distPath)) {
3474
- logger.log(`Output: ${path9__default.default.relative(process.cwd(), distPath)}`);
3798
+ logger.log(`Output: ${path11__default.default.relative(process.cwd(), distPath)}`);
3475
3799
  const files = fs__default.default.readdirSync(distPath);
3476
3800
  logger.log(`Files: ${files.length}`);
3477
3801
  }
@@ -3527,7 +3851,7 @@ async function packageCommand(options) {
3527
3851
  let themeName;
3528
3852
  if (options.theme) {
3529
3853
  themeName = options.theme;
3530
- themePath = path9__default.default.join(getThemesDir(), themeName);
3854
+ themePath = path11__default.default.join(getThemesDir(), themeName);
3531
3855
  if (!fs__default.default.existsSync(themePath)) {
3532
3856
  logger.error(`Theme "${themeName}" not found.`);
3533
3857
  process.exit(1);
@@ -3537,10 +3861,10 @@ async function packageCommand(options) {
3537
3861
  "theme.config.ts",
3538
3862
  "bundle-entry.ts",
3539
3863
  "manifest.ts"
3540
- ].some((f) => fs__default.default.existsSync(path9__default.default.join(process.cwd(), f)));
3864
+ ].some((f) => fs__default.default.existsSync(path11__default.default.join(process.cwd(), f)));
3541
3865
  if (isThemeDir2) {
3542
3866
  themePath = process.cwd();
3543
- themeName = path9__default.default.basename(themePath);
3867
+ themeName = path11__default.default.basename(themePath);
3544
3868
  logger.info(`Packaging current theme: ${themeName}`);
3545
3869
  } else {
3546
3870
  logger.error(
@@ -3549,7 +3873,7 @@ async function packageCommand(options) {
3549
3873
  process.exit(1);
3550
3874
  }
3551
3875
  }
3552
- const packageJsonPath = path9__default.default.join(themePath, "package.json");
3876
+ const packageJsonPath = path11__default.default.join(themePath, "package.json");
3553
3877
  let version2 = "1.0.0";
3554
3878
  if (fs__default.default.existsSync(packageJsonPath)) {
3555
3879
  const packageJson = await fs__default.default.readJson(packageJsonPath);
@@ -3559,7 +3883,7 @@ async function packageCommand(options) {
3559
3883
  logger.info(`Theme: ${themeName}`);
3560
3884
  logger.info(`Version: ${version2}`);
3561
3885
  logger.newLine();
3562
- const compiledThemePath = path9__default.default.join(
3886
+ const compiledThemePath = path11__default.default.join(
3563
3887
  process.cwd(),
3564
3888
  "themes",
3565
3889
  themeName,
@@ -3593,8 +3917,8 @@ async function packageCommand(options) {
3593
3917
  logger.newLine();
3594
3918
  logger.section("Step 2: Create Package");
3595
3919
  const packageName = options.name || `${themeName}-${version2}`;
3596
- const outputDir = options.output || path9__default.default.join(process.cwd(), "dist");
3597
- const outputPath = path9__default.default.join(outputDir, `${packageName}.zip`);
3920
+ const outputDir = options.output || path11__default.default.join(process.cwd(), "dist");
3921
+ const outputPath = path11__default.default.join(outputDir, `${packageName}.zip`);
3598
3922
  await fs__default.default.ensureDir(outputDir);
3599
3923
  logger.startSpinner("Creating zip archive...");
3600
3924
  try {
@@ -3607,11 +3931,11 @@ async function packageCommand(options) {
3607
3931
  logger.newLine();
3608
3932
  logger.info(`Package: ${packageName}.zip`);
3609
3933
  logger.log(`Size: ${sizeMB} MB`);
3610
- logger.log(`Location: ${path9__default.default.relative(process.cwd(), outputPath)}`);
3934
+ logger.log(`Location: ${path11__default.default.relative(process.cwd(), outputPath)}`);
3611
3935
  logger.newLine();
3612
3936
  logger.section("Next steps:");
3613
3937
  logger.log(
3614
- ` onexthm deploy --package ${path9__default.default.relative(process.cwd(), outputPath)}`
3938
+ ` onexthm deploy --package ${path11__default.default.relative(process.cwd(), outputPath)}`
3615
3939
  );
3616
3940
  } catch (error) {
3617
3941
  logger.stopSpinner(false, "Failed to create package");
@@ -3669,9 +3993,9 @@ async function deployCommand(options) {
3669
3993
  ensureOneXProject();
3670
3994
  let packagePath;
3671
3995
  if (options.package) {
3672
- packagePath = path9__default.default.resolve(options.package);
3996
+ packagePath = path11__default.default.resolve(options.package);
3673
3997
  } else if (options.theme) {
3674
- const distDir = path9__default.default.join(process.cwd(), "dist");
3998
+ const distDir = path11__default.default.join(process.cwd(), "dist");
3675
3999
  if (!fs__default.default.existsSync(distDir)) {
3676
4000
  logger.error("No dist/ directory found. Run 'onexthm package' first.");
3677
4001
  process.exit(1);
@@ -3686,7 +4010,7 @@ async function deployCommand(options) {
3686
4010
  process.exit(1);
3687
4011
  }
3688
4012
  packageFiles.sort().reverse();
3689
- packagePath = path9__default.default.join(distDir, packageFiles[0]);
4013
+ packagePath = path11__default.default.join(distDir, packageFiles[0]);
3690
4014
  } else {
3691
4015
  logger.error("Either --package or --theme must be specified.");
3692
4016
  logger.info("Examples:");
@@ -3700,11 +4024,11 @@ async function deployCommand(options) {
3700
4024
  }
3701
4025
  const stats = await fs__default.default.stat(packagePath);
3702
4026
  const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
3703
- const fileName = path9__default.default.basename(packagePath);
4027
+ const fileName = path11__default.default.basename(packagePath);
3704
4028
  logger.newLine();
3705
4029
  logger.info(`Package: ${fileName}`);
3706
4030
  logger.log(`Size: ${sizeMB} MB`);
3707
- logger.log(`Path: ${path9__default.default.relative(process.cwd(), packagePath)}`);
4031
+ logger.log(`Path: ${path11__default.default.relative(process.cwd(), packagePath)}`);
3708
4032
  logger.newLine();
3709
4033
  const apiUrl = options.apiUrl || process.env.ONEX_API_URL || "http://localhost:3001";
3710
4034
  const uploadEndpoint = `${apiUrl}/website-api/themes/upload`;
@@ -3861,8 +4185,8 @@ async function downloadBundleZip(apiUrl, themeId, version2) {
3861
4185
  async function createCompatibilityFiles(outputDir, manifest) {
3862
4186
  const entryFile = manifest.output?.entry || "bundle-entry.js";
3863
4187
  if (entryFile !== "bundle-entry.js" && entryFile.startsWith("bundle-entry-")) {
3864
- const hashedPath = path9__default.default.join(outputDir, entryFile);
3865
- const stablePath = path9__default.default.join(outputDir, "bundle-entry.js");
4188
+ const hashedPath = path11__default.default.join(outputDir, entryFile);
4189
+ const stablePath = path11__default.default.join(outputDir, "bundle-entry.js");
3866
4190
  if (await fs__default.default.pathExists(hashedPath)) {
3867
4191
  await fs__default.default.copy(hashedPath, stablePath);
3868
4192
  const mapPath = hashedPath + ".map";
@@ -3871,13 +4195,13 @@ async function createCompatibilityFiles(outputDir, manifest) {
3871
4195
  }
3872
4196
  }
3873
4197
  }
3874
- const sectionsRegistryPath = path9__default.default.join(outputDir, "sections-registry.js");
4198
+ const sectionsRegistryPath = path11__default.default.join(outputDir, "sections-registry.js");
3875
4199
  const content = `// Re-export all sections from bundle-entry
3876
4200
  // This file exists to maintain compatibility with the import path
3877
4201
  export * from './bundle-entry.js';
3878
4202
  `;
3879
4203
  await fs__default.default.writeFile(sectionsRegistryPath, content, "utf-8");
3880
- const pkgJsonPath = path9__default.default.join(outputDir, "package.json");
4204
+ const pkgJsonPath = path11__default.default.join(outputDir, "package.json");
3881
4205
  await fs__default.default.writeFile(pkgJsonPath, '{\n "type": "module"\n}\n', "utf-8");
3882
4206
  }
3883
4207
  function showDownloadFailureHelp(themeId, apiUrl) {
@@ -3908,9 +4232,7 @@ function showDownloadFailureHelp(themeId, apiUrl) {
3908
4232
  console.log();
3909
4233
  console.log(chalk4__default.default.white("2. Check API URL configuration:"));
3910
4234
  console.log(chalk4__default.default.gray(` Current API URL: ${apiUrl}`));
3911
- console.log(
3912
- chalk4__default.default.gray(" Override with NEXT_PUBLIC_API_URL or ONEXTHM_API_URL")
3913
- );
4235
+ console.log(chalk4__default.default.gray(" Override with ONEXTHM_API_URL env var if needed"));
3914
4236
  console.log();
3915
4237
  console.log(chalk4__default.default.white("3. Pin a specific version (CI/production):"));
3916
4238
  console.log(
@@ -3920,15 +4242,17 @@ function showDownloadFailureHelp(themeId, apiUrl) {
3920
4242
  }
3921
4243
  async function downloadCommand(options) {
3922
4244
  logger.header("Download Theme");
4245
+ const env = options.env ?? "dev";
4246
+ const apiUrl = getApiUrl(env);
4247
+ logger.info(`Environment: ${env} (${apiUrl})`);
3923
4248
  const spinner = ora__default.default("Initializing download...").start();
3924
- if (options.bucket || options.environment) {
4249
+ if (options.bucket) {
3925
4250
  spinner.stop();
3926
4251
  logger.warning(
3927
- "--bucket and --environment are deprecated and ignored. Themes are now served via HTTP from the website-api Lambda."
4252
+ "--bucket is deprecated and ignored. Themes are now served via HTTP from the website-api Lambda."
3928
4253
  );
3929
4254
  spinner.start();
3930
4255
  }
3931
- const apiUrl = getApiUrl();
3932
4256
  try {
3933
4257
  const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || process.env.THEME_ID;
3934
4258
  const requestedVersion = options.version || process.env.THEME_VERSION || "latest";
@@ -3976,7 +4300,7 @@ async function downloadCommand(options) {
3976
4300
  zip.extractAllTo(outputDir, true);
3977
4301
  const entries = zip.getEntries().filter((e) => !e.isDirectory);
3978
4302
  spinner.succeed(`Extracted ${entries.length} files to ${outputDir}`);
3979
- const manifestPath = path9__default.default.join(outputDir, "manifest.json");
4303
+ const manifestPath = path11__default.default.join(outputDir, "manifest.json");
3980
4304
  const manifest = await fs__default.default.readJson(manifestPath);
3981
4305
  await createCompatibilityFiles(outputDir, manifest);
3982
4306
  console.log();
@@ -3985,6 +4309,7 @@ async function downloadCommand(options) {
3985
4309
  console.log(
3986
4310
  chalk4__default.default.cyan(" Theme: ") + chalk4__default.default.white(`${themeId}@${resolvedVersion}`)
3987
4311
  );
4312
+ console.log(chalk4__default.default.cyan(" Env: ") + chalk4__default.default.white(env));
3988
4313
  console.log(chalk4__default.default.cyan(" Source: ") + chalk4__default.default.white(apiUrl));
3989
4314
  console.log(chalk4__default.default.cyan(" Output: ") + chalk4__default.default.white(outputDir));
3990
4315
  console.log(chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(entries.length));
@@ -4027,11 +4352,9 @@ async function resolveLatestVersion2(apiUrl, themeId) {
4027
4352
  }
4028
4353
  return latest;
4029
4354
  }
4030
- async function fetchSourceZip(apiUrl, themeId, version2) {
4355
+ async function fetchSourceZip(apiUrl, themeId, version2, env) {
4031
4356
  const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/source?version=${encodeURIComponent(version2)}`;
4032
- const response = await authenticatedFetch(url, {
4033
- method: "GET"
4034
- });
4357
+ const response = await authenticatedFetch(url, { method: "GET" }, env);
4035
4358
  if (!response.ok) {
4036
4359
  if (response.status === 404) {
4037
4360
  throw new Error(
@@ -4040,7 +4363,7 @@ async function fetchSourceZip(apiUrl, themeId, version2) {
4040
4363
  }
4041
4364
  if (response.status === 401 || response.status === 403) {
4042
4365
  throw new Error(
4043
- `Not authorized to download source for "${themeId}". Run \`onexthm login\` first.`
4366
+ `Not authorized to download source for "${themeId}". Run \`onexthm login --env ${env}\` first.`
4044
4367
  );
4045
4368
  }
4046
4369
  throw new Error(
@@ -4098,7 +4421,7 @@ async function renameTheme(themeDir, oldName, newName) {
4098
4421
  const oldPrefix = `${oldName}-`;
4099
4422
  const newPrefix = `${newName}-`;
4100
4423
  const newDisplayName = newName.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
4101
- const pkgPath = path9__default.default.join(themeDir, "package.json");
4424
+ const pkgPath = path11__default.default.join(themeDir, "package.json");
4102
4425
  if (await fs__default.default.pathExists(pkgPath)) {
4103
4426
  const pkg = await fs__default.default.readJson(pkgPath);
4104
4427
  pkg.name = `@onex-themes/${newName}`;
@@ -4114,7 +4437,7 @@ async function renameTheme(themeDir, oldName, newName) {
4114
4437
  }
4115
4438
  await fs__default.default.writeJson(pkgPath, pkg, { spaces: 2 });
4116
4439
  }
4117
- const configPath = path9__default.default.join(themeDir, "theme.config.ts");
4440
+ const configPath = path11__default.default.join(themeDir, "theme.config.ts");
4118
4441
  if (await fs__default.default.pathExists(configPath)) {
4119
4442
  let content = await fs__default.default.readFile(configPath, "utf-8");
4120
4443
  content = content.replace(/id:\s*"[^"]*"/, `id: "${newName}"`);
@@ -4124,7 +4447,7 @@ async function renameTheme(themeDir, oldName, newName) {
4124
4447
  );
4125
4448
  await fs__default.default.writeFile(configPath, content);
4126
4449
  }
4127
- const layoutPath = path9__default.default.join(themeDir, "theme.layout.ts");
4450
+ const layoutPath = path11__default.default.join(themeDir, "theme.layout.ts");
4128
4451
  if (await fs__default.default.pathExists(layoutPath)) {
4129
4452
  let content = await fs__default.default.readFile(layoutPath, "utf-8");
4130
4453
  content = content.replace(/id:\s*"[^"]*"/, `id: "${newName}"`);
@@ -4137,7 +4460,7 @@ async function renameTheme(themeDir, oldName, newName) {
4137
4460
  const oldDisplayName = oldName.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
4138
4461
  const tsFiles = await glob.glob("**/*.ts", { cwd: themeDir, nodir: true });
4139
4462
  for (const file of tsFiles) {
4140
- const filePath = path9__default.default.join(themeDir, file);
4463
+ const filePath = path11__default.default.join(themeDir, file);
4141
4464
  let content = await fs__default.default.readFile(filePath, "utf-8");
4142
4465
  const original = content;
4143
4466
  content = content.replace(
@@ -4159,14 +4482,19 @@ async function renameTheme(themeDir, oldName, newName) {
4159
4482
  }
4160
4483
  async function cloneCommand(themeName, options) {
4161
4484
  logger.header("Clone Theme Source");
4162
- if (options.bucket || options.environment) {
4485
+ const env = options.env ?? "dev";
4486
+ const apiUrl = getApiUrl(env);
4487
+ logger.info(`Environment: ${env} (${apiUrl})`);
4488
+ if (options.bucket) {
4163
4489
  logger.warning(
4164
- "--bucket and --environment are deprecated and ignored. Source is now fetched via HTTP from the website-api Lambda."
4490
+ "--bucket is deprecated and ignored. Source is now fetched via HTTP from the website-api Lambda."
4165
4491
  );
4166
4492
  }
4167
- const tokens = await getValidTokens();
4493
+ const tokens = await getValidTokens(env);
4168
4494
  if (!tokens) {
4169
- logger.error("Not logged in. Run: onexthm login");
4495
+ logger.error(
4496
+ `Not logged in to ${env} environment. Run: onexthm login --env ${env}`
4497
+ );
4170
4498
  process.exit(1);
4171
4499
  }
4172
4500
  let newName = options.name;
@@ -4175,8 +4503,7 @@ async function cloneCommand(themeName, options) {
4175
4503
  }
4176
4504
  const spinner = ora__default.default("Initializing clone...").start();
4177
4505
  try {
4178
- const apiUrl = getApiUrl();
4179
- const outputDir = options.output || path9__default.default.resolve(process.cwd(), newName);
4506
+ const outputDir = options.output || path11__default.default.resolve(process.cwd(), newName);
4180
4507
  if (await fs__default.default.pathExists(outputDir)) {
4181
4508
  spinner.fail(chalk4__default.default.red(`Directory already exists: ${outputDir}`));
4182
4509
  logger.info(
@@ -4195,7 +4522,7 @@ async function cloneCommand(themeName, options) {
4195
4522
  spinner.start(`Downloading source.zip for ${themeName}@${version2}...`);
4196
4523
  let zipBuffer;
4197
4524
  try {
4198
- zipBuffer = await fetchSourceZip(apiUrl, themeName, version2);
4525
+ zipBuffer = await fetchSourceZip(apiUrl, themeName, version2, env);
4199
4526
  } catch (error) {
4200
4527
  spinner.fail(chalk4__default.default.red(error.message));
4201
4528
  console.log();
@@ -4222,20 +4549,20 @@ async function cloneCommand(themeName, options) {
4222
4549
  spinner.succeed(
4223
4550
  `Renamed theme: ${chalk4__default.default.gray(themeName)} \u2192 ${chalk4__default.default.cyan(newName)}`
4224
4551
  );
4225
- const envExamplePath = path9__default.default.join(outputDir, ".env.example");
4552
+ const envExamplePath = path11__default.default.join(outputDir, ".env.example");
4226
4553
  if (!await fs__default.default.pathExists(envExamplePath)) {
4227
4554
  await fs__default.default.writeFile(
4228
4555
  envExamplePath,
4229
4556
  [
4230
4557
  "# API Configuration (enables real data in preview)",
4231
4558
  "# Get your Company ID from the OneX dashboard",
4232
- "NEXT_PUBLIC_API_URL=https://platform-dev.onexeos.com",
4559
+ `NEXT_PUBLIC_API_URL=${apiUrl}`,
4233
4560
  "NEXT_PUBLIC_COMPANY_ID=",
4234
4561
  ""
4235
4562
  ].join("\n")
4236
4563
  );
4237
4564
  }
4238
- const mcpJsonPath = path9__default.default.join(outputDir, ".mcp.json");
4565
+ const mcpJsonPath = path11__default.default.join(outputDir, ".mcp.json");
4239
4566
  if (await fs__default.default.pathExists(mcpJsonPath)) {
4240
4567
  const { default: inquirerMod } = await import('inquirer');
4241
4568
  const { figmaApiKey } = await inquirerMod.prompt([
@@ -4260,7 +4587,7 @@ async function cloneCommand(themeName, options) {
4260
4587
  }
4261
4588
  if (options.install !== false) {
4262
4589
  const hasPkgJson = await fs__default.default.pathExists(
4263
- path9__default.default.join(outputDir, "package.json")
4590
+ path11__default.default.join(outputDir, "package.json")
4264
4591
  );
4265
4592
  if (hasPkgJson) {
4266
4593
  spinner.start("Installing dependencies...");
@@ -4282,12 +4609,13 @@ async function cloneCommand(themeName, options) {
4282
4609
  console.log(
4283
4610
  chalk4__default.default.cyan(" Source: ") + chalk4__default.default.gray(`${themeName}@${version2}`)
4284
4611
  );
4612
+ console.log(chalk4__default.default.cyan(" Env: ") + chalk4__default.default.white(env));
4285
4613
  console.log(chalk4__default.default.cyan(" Theme: ") + chalk4__default.default.white(newName));
4286
4614
  console.log(chalk4__default.default.cyan(" Location: ") + chalk4__default.default.white(outputDir));
4287
4615
  console.log(chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(entries.length));
4288
4616
  console.log();
4289
4617
  console.log(chalk4__default.default.cyan("Next steps:"));
4290
- console.log(chalk4__default.default.gray(` cd ${path9__default.default.relative(process.cwd(), outputDir)}`));
4618
+ console.log(chalk4__default.default.gray(` cd ${path11__default.default.relative(process.cwd(), outputDir)}`));
4291
4619
  console.log(
4292
4620
  chalk4__default.default.gray(" cp .env.example .env # then add your Company ID")
4293
4621
  );
@@ -4314,14 +4642,14 @@ async function devCommand(options) {
4314
4642
  if (options.theme) {
4315
4643
  themeName = options.theme;
4316
4644
  try {
4317
- const workspaceThemePath = path9__default.default.join(getThemesDir(), themeName);
4645
+ const workspaceThemePath = path11__default.default.join(getThemesDir(), themeName);
4318
4646
  if (fs__default.default.existsSync(workspaceThemePath)) {
4319
4647
  themePath = workspaceThemePath;
4320
4648
  } else {
4321
- themePath = path9__default.default.join(process.cwd(), themeName);
4649
+ themePath = path11__default.default.join(process.cwd(), themeName);
4322
4650
  }
4323
4651
  } catch {
4324
- themePath = path9__default.default.join(process.cwd(), themeName);
4652
+ themePath = path11__default.default.join(process.cwd(), themeName);
4325
4653
  }
4326
4654
  if (!fs__default.default.existsSync(themePath)) {
4327
4655
  logger.error(`Theme "${themeName}" not found.`);
@@ -4332,10 +4660,10 @@ async function devCommand(options) {
4332
4660
  "theme.config.ts",
4333
4661
  "bundle-entry.ts",
4334
4662
  "manifest.ts"
4335
- ].some((f) => fs__default.default.existsSync(path9__default.default.join(process.cwd(), f)));
4663
+ ].some((f) => fs__default.default.existsSync(path11__default.default.join(process.cwd(), f)));
4336
4664
  if (isThemeDir2) {
4337
4665
  themePath = process.cwd();
4338
- themeName = path9__default.default.basename(themePath);
4666
+ themeName = path11__default.default.basename(themePath);
4339
4667
  } else {
4340
4668
  logger.error(
4341
4669
  "Not in a theme directory and no --theme specified. Run from theme root or use --theme flag."
@@ -4404,9 +4732,9 @@ async function devCommand(options) {
4404
4732
  watcher.close();
4405
4733
  await context2.dispose();
4406
4734
  server.close();
4407
- const shimPath = path9__default.default.join(outputDir, ".process-shim.js");
4735
+ const shimPath = path11__default.default.join(outputDir, ".process-shim.js");
4408
4736
  try {
4409
- await fs8__default.default.unlink(shimPath);
4737
+ await fs9__default.default.unlink(shimPath);
4410
4738
  } catch {
4411
4739
  }
4412
4740
  process.exit(0);
@@ -4415,8 +4743,8 @@ async function devCommand(options) {
4415
4743
 
4416
4744
  // src/commands/config.ts
4417
4745
  init_logger();
4418
- var CONFIG_DIR = path9__default.default.join(os__default.default.homedir(), ".onexthm");
4419
- var CONFIG_FILE = path9__default.default.join(CONFIG_DIR, ".env");
4746
+ var CONFIG_DIR = path11__default.default.join(os__default.default.homedir(), ".onexthm");
4747
+ var CONFIG_FILE = path11__default.default.join(CONFIG_DIR, ".env");
4420
4748
  var CONFIG_ENTRIES = [
4421
4749
  {
4422
4750
  key: "AWS_ACCESS_KEY_ID",
@@ -4558,9 +4886,13 @@ async function configCommand() {
4558
4886
 
4559
4887
  // src/commands/login.ts
4560
4888
  init_logger();
4561
- async function loginCommand() {
4889
+ async function loginCommand(options = {}) {
4890
+ const env = options.env ?? "dev";
4891
+ const apiUrl = getApiUrl(env);
4562
4892
  logger.header("OneX Theme Developer Login");
4563
- const existing = loadAuthTokens();
4893
+ logger.info(`Environment: ${env} (${apiUrl})`);
4894
+ logger.newLine();
4895
+ const existing = loadAuthTokens(env);
4564
4896
  if (existing) {
4565
4897
  logger.info(`Already logged in as: ${existing.user.email}`);
4566
4898
  const { relogin } = await inquirer__default.default.prompt([
@@ -4589,7 +4921,6 @@ async function loginCommand() {
4589
4921
  ]);
4590
4922
  logger.startSpinner("Logging in...");
4591
4923
  try {
4592
- const apiUrl = getApiUrl();
4593
4924
  const response = await fetch(`${apiUrl}/auth/login`, {
4594
4925
  method: "POST",
4595
4926
  headers: { "Content-Type": "application/json" },
@@ -4624,15 +4955,18 @@ async function loginCommand() {
4624
4955
  userId: claims.sub
4625
4956
  }
4626
4957
  };
4627
- await saveAuthTokens(tokens);
4958
+ await saveAuthTokens(tokens, env);
4628
4959
  logger.stopSpinner(true, "Logged in!");
4629
4960
  logger.newLine();
4630
- logger.info(` Email: ${tokens.user.email}`);
4631
- if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
4961
+ logger.info(` Environment: ${env}`);
4962
+ logger.info(` Email: ${tokens.user.email}`);
4963
+ if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
4632
4964
  if (tokens.user.companyId)
4633
- logger.info(` Company: ${tokens.user.companyId}`);
4965
+ logger.info(` Company: ${tokens.user.companyId}`);
4634
4966
  logger.newLine();
4635
- logger.success("Token stored securely in ~/.onexthm/auth.json (encrypted)");
4967
+ logger.success(
4968
+ `Token stored securely in ~/.onexthm/auth-${env}.json (encrypted)`
4969
+ );
4636
4970
  } catch (error) {
4637
4971
  logger.stopSpinner(false, "Login failed");
4638
4972
  logger.error(error instanceof Error ? error.message : "Connection failed");
@@ -4642,142 +4976,418 @@ async function loginCommand() {
4642
4976
 
4643
4977
  // src/commands/logout.ts
4644
4978
  init_logger();
4645
- async function logoutCommand() {
4646
- const tokens = loadAuthTokens();
4979
+ async function logoutCommand(options = {}) {
4980
+ const env = options.env ?? "dev";
4981
+ const tokens = loadAuthTokens(env);
4647
4982
  if (!tokens) {
4648
- logger.info("Not logged in.");
4983
+ logger.info(`Not logged in to ${env} environment.`);
4649
4984
  return;
4650
4985
  }
4651
- await clearAuthTokens();
4652
- logger.success(`Logged out (was: ${tokens.user.email})`);
4986
+ await clearAuthTokens(env);
4987
+ logger.success(`Logged out of ${env} (was: ${tokens.user.email})`);
4653
4988
  }
4654
4989
 
4655
4990
  // src/commands/whoami.ts
4656
4991
  init_logger();
4657
- async function whoamiCommand() {
4658
- const tokens = loadAuthTokens();
4992
+ async function whoamiCommand(options = {}) {
4993
+ const env = options.env ?? "dev";
4994
+ const tokens = loadAuthTokens(env);
4659
4995
  if (!tokens) {
4660
- logger.error("Not logged in. Run: onexthm login");
4996
+ logger.error(
4997
+ `Not logged in to ${env} environment. Run: onexthm login --env ${env}`
4998
+ );
4661
4999
  process.exit(1);
4662
5000
  }
4663
5001
  const expired = isTokenExpired(tokens);
4664
5002
  logger.header("OneX Theme Developer");
4665
- logger.info(` Email: ${tokens.user.email}`);
4666
- if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
5003
+ logger.info(` Environment: ${env} (${getApiUrl(env)})`);
5004
+ logger.info(` Email: ${tokens.user.email}`);
5005
+ if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
4667
5006
  if (tokens.user.companyId)
4668
- logger.info(` Company: ${tokens.user.companyId}`);
5007
+ logger.info(` Company: ${tokens.user.companyId}`);
4669
5008
  logger.info(
4670
- ` Status: ${expired ? "\u26A0 Token expired (will auto-refresh)" : "\u2713 Active"}`
5009
+ ` Status: ${expired ? "\u26A0 Token expired (will auto-refresh)" : "\u2713 Active"}`
4671
5010
  );
4672
5011
  }
4673
5012
 
4674
5013
  // src/commands/publish.ts
4675
5014
  init_logger();
4676
- var MIME_MAP = {
4677
- ".png": "image/png",
4678
- ".jpg": "image/jpeg",
4679
- ".jpeg": "image/jpeg",
4680
- ".gif": "image/gif",
4681
- ".webp": "image/webp",
4682
- ".avif": "image/avif",
4683
- ".svg": "image/svg+xml",
4684
- ".ico": "image/x-icon",
4685
- ".bmp": "image/bmp",
4686
- ".woff": "font/woff",
4687
- ".woff2": "font/woff2",
4688
- ".ttf": "font/ttf",
4689
- ".otf": "font/otf",
4690
- ".eot": "application/vnd.ms-fontobject",
4691
- ".mp4": "video/mp4",
4692
- ".webm": "video/webm",
4693
- ".mov": "video/quicktime",
4694
- ".ogg": "video/ogg",
4695
- ".json": "application/json"
5015
+ init_scan_theme_assets();
5016
+
5017
+ // src/utils/fetch-prior-schemas.ts
5018
+ async function fetchPriorGateManifests(themeId, env) {
5019
+ const apiUrl = getApiUrl(env);
5020
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/gate-manifests/latest`;
5021
+ let res;
5022
+ try {
5023
+ res = await authenticatedFetch(url, { method: "GET" }, env);
5024
+ } catch (err) {
5025
+ return {
5026
+ result: null,
5027
+ reason: `network error: ${err instanceof Error ? err.message : "unknown"}`
5028
+ };
5029
+ }
5030
+ if (res.status === 404) {
5031
+ return { result: null, reason: "no-prior" };
5032
+ }
5033
+ if (!res.ok) {
5034
+ return {
5035
+ result: null,
5036
+ reason: `server returned ${res.status} ${res.statusText}`
5037
+ };
5038
+ }
5039
+ let data;
5040
+ try {
5041
+ data = await res.json();
5042
+ } catch {
5043
+ return { result: null, reason: "non-JSON response from server" };
5044
+ }
5045
+ const body = data.statusCode ? data.body : data;
5046
+ if (!body || typeof body.version !== "string" || !body.schemas || !body.assets) {
5047
+ return { result: null, reason: "malformed response (missing fields)" };
5048
+ }
5049
+ return {
5050
+ result: {
5051
+ version: body.version,
5052
+ schemas: body.schemas,
5053
+ assets: body.assets
5054
+ },
5055
+ reason: null
5056
+ };
5057
+ }
5058
+
5059
+ // src/utils/schema-diff.ts
5060
+ var SEVERITY = {
5061
+ safe: 0,
5062
+ "safe-rename": 1,
5063
+ "defaults-only": 2,
5064
+ additive: 3,
5065
+ breaking: 4,
5066
+ "breaking-asset": 5,
5067
+ "breaking-severe": 6
4696
5068
  };
4697
- var HASH_LEN = 8;
4698
- var VIDEO_EXTENSIONS = [
4699
- ".mp4",
4700
- ".webm",
4701
- ".ogg",
4702
- ".mov",
4703
- ".avi",
4704
- ".mkv"
4705
- ];
4706
- function isVideoAsset(filePath) {
4707
- const lower = filePath.toLowerCase();
4708
- return VIDEO_EXTENSIONS.some((ext) => lower.endsWith(ext));
5069
+ var BUMP_FOR = {
5070
+ safe: "patch",
5071
+ "safe-rename": "patch",
5072
+ "defaults-only": "patch",
5073
+ additive: "minor",
5074
+ breaking: "major",
5075
+ "breaking-asset": "major",
5076
+ "breaking-severe": "major"
5077
+ };
5078
+ function bumpFor(kind) {
5079
+ return BUMP_FOR[kind];
4709
5080
  }
4710
- function mimeFor(filename) {
4711
- const ext = path9__default.default.extname(filename).toLowerCase();
4712
- return MIME_MAP[ext] || "application/octet-stream";
5081
+ function maxSeverity(a, b) {
5082
+ return SEVERITY[a] >= SEVERITY[b] ? a : b;
4713
5083
  }
4714
- async function sha256Prefix(absPath, len) {
4715
- const buf = await fs__default.default.readFile(absPath);
4716
- return crypto__default.default.createHash("sha256").update(buf).digest("hex").slice(0, len);
5084
+ function classify(changes) {
5085
+ let highest = "safe";
5086
+ for (const c of changes) {
5087
+ highest = maxSeverity(highest, c.kind);
5088
+ }
5089
+ return { bump: bumpFor(highest), highest, changes };
4717
5090
  }
4718
- function insertHashIntoName(relPath, hash) {
4719
- const dir = path9__default.default.posix.dirname(relPath);
4720
- const base = path9__default.default.posix.basename(relPath);
4721
- const ext = path9__default.default.posix.extname(base);
4722
- const stem = ext ? base.slice(0, -ext.length) : base;
4723
- const hashed = `${stem}-${hash}${ext}`;
4724
- return dir === "." ? hashed : `${dir}/${hashed}`;
5091
+ function diffManifests(prior, current) {
5092
+ const changes = [];
5093
+ const priorSections = prior.schemas.sections;
5094
+ const currentSections = current.schemas.sections;
5095
+ const sectionTypes = /* @__PURE__ */ new Set([
5096
+ ...Object.keys(priorSections),
5097
+ ...Object.keys(currentSections)
5098
+ ]);
5099
+ for (const type of [...sectionTypes].sort()) {
5100
+ const p = priorSections[type];
5101
+ const c = currentSections[type];
5102
+ if (p && !c) {
5103
+ changes.push({
5104
+ kind: "breaking-severe",
5105
+ path: `sections.${type}`,
5106
+ detail: `Section type "${type}" removed. Pages using this section will render empty.`
5107
+ });
5108
+ continue;
5109
+ }
5110
+ if (!p && c) {
5111
+ changes.push({
5112
+ kind: "additive",
5113
+ path: `sections.${type}`,
5114
+ detail: `Section type "${type}" added.`
5115
+ });
5116
+ continue;
5117
+ }
5118
+ if (p && c) diffSection(p, c, changes);
5119
+ }
5120
+ diffAssets(prior.assets, current.assets, changes);
5121
+ return changes;
4725
5122
  }
4726
- async function scanThemeAssets(distDir) {
4727
- const assetsDir = path9__default.default.join(distDir, "theme-assets");
4728
- if (!await fs__default.default.pathExists(assetsDir)) return [];
4729
- const files = await glob.glob("**/*", {
4730
- cwd: assetsDir,
4731
- nodir: true,
4732
- dot: false
4733
- });
4734
- const results = [];
4735
- for (const rel of files) {
4736
- const absPath = path9__default.default.join(assetsDir, rel);
4737
- const stat = await fs__default.default.stat(absPath);
4738
- if (!stat.isFile()) continue;
4739
- const originalPath = rel.split(path9__default.default.sep).join("/");
4740
- const hash = await sha256Prefix(absPath, HASH_LEN);
4741
- const hashedPath = insertHashIntoName(originalPath, hash);
4742
- const contentType = mimeFor(rel);
4743
- results.push({
4744
- originalPath,
4745
- hashedPath,
4746
- hash,
4747
- size: stat.size,
4748
- contentType,
4749
- absPath
5123
+ function diffSection(prior, current, out) {
5124
+ const type = current.type;
5125
+ if (JSON.stringify(prior.dataRequirements) !== JSON.stringify(current.dataRequirements)) {
5126
+ out.push({
5127
+ kind: "breaking",
5128
+ path: `sections.${type}.dataRequirements`,
5129
+ detail: "dataRequirements changed."
4750
5130
  });
4751
5131
  }
4752
- results.sort((a, b) => a.originalPath.localeCompare(b.originalPath));
4753
- return results;
5132
+ diffFieldList(
5133
+ prior.settings,
5134
+ current.settings,
5135
+ `sections.${type}.settings`,
5136
+ out
5137
+ );
5138
+ diffDefaults(
5139
+ prior.defaults,
5140
+ current.defaults,
5141
+ `sections.${type}.defaults`,
5142
+ out
5143
+ );
5144
+ diffBlocks(prior.blocks, current.blocks, `sections.${type}.blocks`, out);
4754
5145
  }
4755
- function buildAssetMap(entries) {
4756
- const map = {};
4757
- for (const e of entries) {
4758
- map[e.originalPath] = e.hashedPath;
5146
+ function diffBlocks(prior, current, pathPrefix, out) {
5147
+ const priorByType = new Map(prior.map((b) => [b.type, b]));
5148
+ const currentByType = new Map(current.map((b) => [b.type, b]));
5149
+ for (const type of /* @__PURE__ */ new Set([
5150
+ ...priorByType.keys(),
5151
+ ...currentByType.keys()
5152
+ ])) {
5153
+ const p = priorByType.get(type);
5154
+ const c = currentByType.get(type);
5155
+ if (p && !c) {
5156
+ out.push({
5157
+ kind: "breaking",
5158
+ path: `${pathPrefix}.${type}`,
5159
+ detail: `Block type "${type}" removed.`
5160
+ });
5161
+ continue;
5162
+ }
5163
+ if (!p && c) {
5164
+ out.push({
5165
+ kind: "additive",
5166
+ path: `${pathPrefix}.${type}`,
5167
+ detail: `Block type "${type}" added.`
5168
+ });
5169
+ continue;
5170
+ }
5171
+ if (p && c) {
5172
+ diffFieldList(
5173
+ p.settings,
5174
+ c.settings,
5175
+ `${pathPrefix}.${type}.settings`,
5176
+ out
5177
+ );
5178
+ diffDefaults(
5179
+ p.defaults,
5180
+ c.defaults,
5181
+ `${pathPrefix}.${type}.defaults`,
5182
+ out
5183
+ );
5184
+ }
5185
+ }
5186
+ }
5187
+ function diffFieldList(prior, current, pathPrefix, out) {
5188
+ const priorById = new Map(prior.map((f) => [f.id, f]));
5189
+ const currentById = new Map(current.map((f) => [f.id, f]));
5190
+ const aliasToCurrent = /* @__PURE__ */ new Map();
5191
+ for (const f of current) {
5192
+ if (f.aliases) {
5193
+ for (const alias of f.aliases) {
5194
+ aliasToCurrent.set(alias, f);
5195
+ }
5196
+ }
5197
+ }
5198
+ for (const [id, p] of priorById) {
5199
+ const c = currentById.get(id);
5200
+ if (c) {
5201
+ diffFieldPair(p, c, `${pathPrefix}.${id}`, out);
5202
+ continue;
5203
+ }
5204
+ const renamed = aliasToCurrent.get(id);
5205
+ if (renamed) {
5206
+ if (renamed.type === p.type) {
5207
+ out.push({
5208
+ kind: "safe-rename",
5209
+ path: `${pathPrefix}.${id}`,
5210
+ detail: `Field "${id}" renamed to "${renamed.id}" (alias preserved).`
5211
+ });
5212
+ } else {
5213
+ out.push({
5214
+ kind: "breaking",
5215
+ path: `${pathPrefix}.${id}`,
5216
+ detail: `Field "${id}" renamed to "${renamed.id}" but type changed (${p.type} \u2192 ${renamed.type}).`
5217
+ });
5218
+ }
5219
+ } else {
5220
+ out.push({
5221
+ kind: "breaking",
5222
+ path: `${pathPrefix}.${id}`,
5223
+ detail: `Field "${id}" removed. Consider adding aliases: ["${id}"] to the replacement field if this was a rename.`
5224
+ });
5225
+ }
5226
+ }
5227
+ for (const [id, c] of currentById) {
5228
+ if (priorById.has(id)) continue;
5229
+ const coveredByAlias = c.aliases?.some((a) => priorById.has(a)) ?? false;
5230
+ if (coveredByAlias) continue;
5231
+ if (c.required && c.default === void 0) {
5232
+ out.push({
5233
+ kind: "breaking",
5234
+ path: `${pathPrefix}.${id}`,
5235
+ detail: `Required field "${id}" added with no default. Existing instances cannot satisfy it.`
5236
+ });
5237
+ } else {
5238
+ out.push({
5239
+ kind: "additive",
5240
+ path: `${pathPrefix}.${id}`,
5241
+ detail: `Field "${id}" added.`
5242
+ });
5243
+ }
4759
5244
  }
4760
- return map;
5245
+ }
5246
+ function diffFieldPair(p, c, path23, out) {
5247
+ if (p.type !== c.type) {
5248
+ out.push({
5249
+ kind: "breaking",
5250
+ path: path23,
5251
+ detail: `Type changed (${p.type} \u2192 ${c.type}). Saved values may misrender.`
5252
+ });
5253
+ return;
5254
+ }
5255
+ if (p.required !== true && c.required === true) {
5256
+ out.push({
5257
+ kind: "breaking",
5258
+ path: path23,
5259
+ detail: "Field became required. Existing empty instances now invalid."
5260
+ });
5261
+ }
5262
+ if (typeof p.maxLength === "number" || typeof c.maxLength === "number") {
5263
+ if ((c.maxLength ?? Infinity) < (p.maxLength ?? Infinity)) {
5264
+ out.push({
5265
+ kind: "breaking",
5266
+ path: path23,
5267
+ detail: `maxLength tightened (${p.maxLength ?? "\u221E"} \u2192 ${c.maxLength}).`
5268
+ });
5269
+ }
5270
+ }
5271
+ if (typeof p.min === "number" || typeof c.min === "number") {
5272
+ if ((c.min ?? -Infinity) > (p.min ?? -Infinity)) {
5273
+ out.push({
5274
+ kind: "breaking",
5275
+ path: path23,
5276
+ detail: `min raised (${p.min ?? "-\u221E"} \u2192 ${c.min}).`
5277
+ });
5278
+ }
5279
+ }
5280
+ if (typeof p.max === "number" || typeof c.max === "number") {
5281
+ if ((c.max ?? Infinity) < (p.max ?? Infinity)) {
5282
+ out.push({
5283
+ kind: "breaking",
5284
+ path: path23,
5285
+ detail: `max lowered (${p.max ?? "\u221E"} \u2192 ${c.max}).`
5286
+ });
5287
+ }
5288
+ }
5289
+ if (p.options || c.options) {
5290
+ const priorOpts = new Set(p.options ?? []);
5291
+ const currentOpts = new Set(c.options ?? []);
5292
+ const removed = [...priorOpts].filter((o) => !currentOpts.has(o));
5293
+ const added = [...currentOpts].filter((o) => !priorOpts.has(o));
5294
+ if (removed.length > 0) {
5295
+ out.push({
5296
+ kind: "breaking",
5297
+ path: path23,
5298
+ detail: `Option(s) removed: ${removed.join(", ")}. Existing saved values may be orphaned.`
5299
+ });
5300
+ }
5301
+ if (added.length > 0) {
5302
+ out.push({
5303
+ kind: "additive",
5304
+ path: path23,
5305
+ detail: `Option(s) added: ${added.join(", ")}.`
5306
+ });
5307
+ }
5308
+ }
5309
+ if (!deepEqual(p.default, c.default)) {
5310
+ out.push({
5311
+ kind: "defaults-only",
5312
+ path: path23,
5313
+ detail: `Default changed: ${JSON.stringify(p.default)} \u2192 ${JSON.stringify(c.default)}.`
5314
+ });
5315
+ }
5316
+ }
5317
+ function diffDefaults(prior, current, pathPrefix, out) {
5318
+ const keys = /* @__PURE__ */ new Set([...Object.keys(prior), ...Object.keys(current)]);
5319
+ for (const key of [...keys].sort()) {
5320
+ if (!(key in prior) || !(key in current)) continue;
5321
+ if (!deepEqual(prior[key], current[key])) {
5322
+ out.push({
5323
+ kind: "defaults-only",
5324
+ path: `${pathPrefix}.${key}`,
5325
+ detail: `Default value changed.`
5326
+ });
5327
+ }
5328
+ }
5329
+ }
5330
+ function diffAssets(prior, current, out) {
5331
+ const currentPaths = new Set(current.assets.map((a) => a.path));
5332
+ for (const a of prior.assets) {
5333
+ if (!currentPaths.has(a.path)) {
5334
+ out.push({
5335
+ kind: "breaking-asset",
5336
+ path: `theme-assets/${a.path}`,
5337
+ detail: `Asset "${a.path}" was present in the prior version and is now missing. Code that references it hardcoded will break.`
5338
+ });
5339
+ }
5340
+ }
5341
+ }
5342
+ function deepEqual(a, b) {
5343
+ if (a === b) return true;
5344
+ if (a === null || b === null) return false;
5345
+ if (typeof a !== typeof b) return false;
5346
+ if (typeof a !== "object") return false;
5347
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
5348
+ if (Array.isArray(a) && Array.isArray(b)) {
5349
+ if (a.length !== b.length) return false;
5350
+ for (let i = 0; i < a.length; i++) {
5351
+ if (!deepEqual(a[i], b[i])) return false;
5352
+ }
5353
+ return true;
5354
+ }
5355
+ const ak = Object.keys(a);
5356
+ const bk = Object.keys(b);
5357
+ if (ak.length !== bk.length) return false;
5358
+ for (const k of ak) {
5359
+ if (!deepEqual(
5360
+ a[k],
5361
+ b[k]
5362
+ ))
5363
+ return false;
5364
+ }
5365
+ return true;
4761
5366
  }
4762
5367
 
4763
5368
  // src/commands/publish.ts
4764
5369
  async function publishCommand(options) {
5370
+ const env = options.env ?? "dev";
4765
5371
  logger.header("OneX Theme Publish");
4766
- const tokens = await getValidTokens();
5372
+ logger.info(`Environment: ${env} (${getApiUrl(env)})`);
5373
+ logger.newLine();
5374
+ const tokens = await getValidTokens(env);
4767
5375
  if (!tokens) {
4768
- logger.error("Not logged in. Run: onexthm login");
5376
+ logger.error(
5377
+ `Not logged in to ${env} environment. Run: onexthm login --env ${env}`
5378
+ );
4769
5379
  process.exit(1);
4770
5380
  }
4771
5381
  logger.info(`Logged in as: ${tokens.user.email}`);
4772
5382
  let themePath;
4773
5383
  if (options.theme) {
4774
- themePath = path9__default.default.resolve(options.theme);
5384
+ themePath = path11__default.default.resolve(options.theme);
4775
5385
  } else {
4776
5386
  const isThemeDir2 = [
4777
5387
  "theme.config.ts",
4778
5388
  "bundle-entry.ts",
4779
5389
  "manifest.ts"
4780
- ].some((f) => fs__default.default.existsSync(path9__default.default.join(process.cwd(), f)));
5390
+ ].some((f) => fs__default.default.existsSync(path11__default.default.join(process.cwd(), f)));
4781
5391
  if (isThemeDir2) {
4782
5392
  themePath = process.cwd();
4783
5393
  } else {
@@ -4787,13 +5397,13 @@ async function publishCommand(options) {
4787
5397
  process.exit(1);
4788
5398
  }
4789
5399
  }
4790
- const pkgPath = path9__default.default.join(themePath, "package.json");
5400
+ const pkgPath = path11__default.default.join(themePath, "package.json");
4791
5401
  if (!fs__default.default.existsSync(pkgPath)) {
4792
5402
  logger.error("No package.json found in theme directory");
4793
5403
  process.exit(1);
4794
5404
  }
4795
5405
  const pkg = fs__default.default.readJsonSync(pkgPath);
4796
- const themeId = pkg.name?.replace("@onex-themes/", "") || path9__default.default.basename(themePath);
5406
+ const themeId = pkg.name?.replace("@onex-themes/", "") || path11__default.default.basename(themePath);
4797
5407
  if (options.bump) {
4798
5408
  const currentVersion = pkg.version || "1.0.0";
4799
5409
  const newVersion = semver__default.default.inc(currentVersion, options.bump);
@@ -4816,56 +5426,63 @@ async function publishCommand(options) {
4816
5426
  logger.info(`Theme: ${themeId}`);
4817
5427
  logger.info(`Version: ${version2}`);
4818
5428
  logger.newLine();
4819
- const apiUrl = getApiUrl();
4820
- logger.startSpinner("Registering theme...");
4821
- try {
4822
- const regResponse = await authenticatedFetch(
4823
- `${apiUrl}/website-api/themes/register`,
4824
- {
4825
- method: "POST",
4826
- body: JSON.stringify({
4827
- themeId,
4828
- name: pkg.displayName || themeId,
4829
- description: pkg.description || "",
4830
- email: tokens.user.email,
4831
- author: typeof pkg.author === "string" ? pkg.author : pkg.author?.name || tokens.user.name || "",
4832
- category: pkg.onex?.category || "MINIMAL",
4833
- tags: pkg.keywords || [],
4834
- thumbnail_url: pkg.onex?.thumbnail || ""
4835
- })
4836
- }
4837
- );
4838
- const regData = await regResponse.json();
4839
- const regBody = regData.statusCode ? regData.body : regData;
4840
- if (!regResponse.ok) {
4841
- const errMsg = regBody.error || regBody.message || "Registration failed";
4842
- if (!errMsg.includes("already registered")) {
4843
- logger.stopSpinner(false, "Registration failed");
4844
- logger.error(errMsg);
4845
- process.exit(1);
5429
+ const apiUrl = getApiUrl(env);
5430
+ if (!options.dryRun) {
5431
+ logger.startSpinner("Registering theme...");
5432
+ try {
5433
+ const regResponse = await authenticatedFetch(
5434
+ `${apiUrl}/website-api/themes/register`,
5435
+ {
5436
+ method: "POST",
5437
+ body: JSON.stringify({
5438
+ themeId,
5439
+ name: pkg.displayName || themeId,
5440
+ description: pkg.description || "",
5441
+ email: tokens.user.email,
5442
+ author: typeof pkg.author === "string" ? pkg.author : pkg.author?.name || tokens.user.name || "",
5443
+ category: pkg.onex?.category || "MINIMAL",
5444
+ tags: pkg.keywords || [],
5445
+ thumbnail_url: pkg.onex?.thumbnail || ""
5446
+ })
5447
+ },
5448
+ env
5449
+ );
5450
+ const regData = await regResponse.json();
5451
+ const regBody = regData.statusCode ? regData.body : regData;
5452
+ if (!regResponse.ok) {
5453
+ const errMsg = regBody.error || regBody.message || "Registration failed";
5454
+ if (!errMsg.includes("already registered")) {
5455
+ logger.stopSpinner(false, "Registration failed");
5456
+ logger.error(errMsg);
5457
+ process.exit(1);
5458
+ }
4846
5459
  }
5460
+ logger.stopSpinner(true, regBody.message || "Theme registered");
5461
+ } catch (error) {
5462
+ logger.stopSpinner(false, "Registration failed");
5463
+ logger.error(
5464
+ error instanceof Error ? error.message : "Connection failed"
5465
+ );
5466
+ process.exit(1);
4847
5467
  }
4848
- logger.stopSpinner(true, regBody.message || "Theme registered");
4849
- } catch (error) {
4850
- logger.stopSpinner(false, "Registration failed");
4851
- logger.error(error instanceof Error ? error.message : "Connection failed");
4852
- process.exit(1);
4853
5468
  }
4854
- logger.startSpinner("Checking version availability...");
4855
- try {
4856
- const checkResponse = await authenticatedFetch(
4857
- `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(version2)}/exists`,
4858
- { method: "GET" }
4859
- );
4860
- const checkData = await checkResponse.json();
4861
- const checkBody = checkData.statusCode ? checkData.body : checkData;
4862
- if (checkBody.exists) {
4863
- logger.stopSpinner(false, "Version already published");
4864
- const patchVer = semver__default.default.inc(version2, "patch") || "?";
4865
- const minorVer = semver__default.default.inc(version2, "minor") || "?";
4866
- const majorVer = semver__default.default.inc(version2, "major") || "?";
4867
- logger.error(
4868
- `
5469
+ if (!options.dryRun) {
5470
+ logger.startSpinner("Checking version availability...");
5471
+ try {
5472
+ const checkResponse = await authenticatedFetch(
5473
+ `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(version2)}/exists`,
5474
+ { method: "GET" },
5475
+ env
5476
+ );
5477
+ const checkData = await checkResponse.json();
5478
+ const checkBody = checkData.statusCode ? checkData.body : checkData;
5479
+ if (checkBody.exists) {
5480
+ logger.stopSpinner(false, "Version already published");
5481
+ const patchVer = semver__default.default.inc(version2, "patch") || "?";
5482
+ const minorVer = semver__default.default.inc(version2, "minor") || "?";
5483
+ const majorVer = semver__default.default.inc(version2, "major") || "?";
5484
+ logger.error(
5485
+ `
4869
5486
  Version ${version2} of "${themeId}" is already published and cannot be overwritten.
4870
5487
 
4871
5488
  To publish a new version:
@@ -4876,12 +5493,16 @@ Or use the --bump flag:
4876
5493
  onexthm publish --bump patch (${version2} -> ${patchVer})
4877
5494
  onexthm publish --bump minor (${version2} -> ${minorVer})
4878
5495
  onexthm publish --bump major (${version2} -> ${majorVer})`
5496
+ );
5497
+ process.exit(1);
5498
+ }
5499
+ logger.stopSpinner(true, `Version ${version2} is available`);
5500
+ } catch (error) {
5501
+ logger.stopSpinner(
5502
+ true,
5503
+ "Version check skipped (endpoint not available)"
4879
5504
  );
4880
- process.exit(1);
4881
5505
  }
4882
- logger.stopSpinner(true, `Version ${version2} is available`);
4883
- } catch (error) {
4884
- logger.stopSpinner(true, "Version check skipped (endpoint not available)");
4885
5506
  }
4886
5507
  logger.startSpinner("Building theme...");
4887
5508
  try {
@@ -4898,7 +5519,19 @@ Or use the --bump flag:
4898
5519
  logger.error(error instanceof Error ? error.message : "Build error");
4899
5520
  process.exit(1);
4900
5521
  }
4901
- const distDir = path9__default.default.join(themePath, "dist");
5522
+ const distDir = path11__default.default.join(themePath, "dist");
5523
+ const classification = await runSchemaDiffGate(
5524
+ themeId,
5525
+ distDir,
5526
+ env,
5527
+ options
5528
+ );
5529
+ if (options.dryRun) {
5530
+ const exitCode = classification?.highest === "breaking" || classification?.highest === "breaking-severe" || classification?.highest === "breaking-asset" ? 2 : 0;
5531
+ logger.newLine();
5532
+ logger.info(`Dry run complete (exit ${exitCode}). No files uploaded.`);
5533
+ process.exit(exitCode);
5534
+ }
4902
5535
  let assetEntries = [];
4903
5536
  try {
4904
5537
  assetEntries = await scanThemeAssets(distDir);
@@ -4920,7 +5553,7 @@ Or use the --bump flag:
4920
5553
  logger.startSpinner(`Uploading ${videoAssets.length} video(s)...`);
4921
5554
  try {
4922
5555
  for (const video of videoAssets) {
4923
- const url = await uploadVideoMultipart(apiUrl, themeId, video);
5556
+ const url = await uploadVideoMultipart(apiUrl, themeId, video, env);
4924
5557
  videoUrls[video.originalPath] = url;
4925
5558
  }
4926
5559
  logger.stopSpinner(true, `Uploaded ${videoAssets.length} video(s)`);
@@ -4935,7 +5568,7 @@ Or use the --bump flag:
4935
5568
  for (const [originalPath, url] of Object.entries(videoUrls)) {
4936
5569
  assetMap[originalPath] = url;
4937
5570
  }
4938
- const assetMapPath = path9__default.default.join(distDir, "asset-map.json");
5571
+ const assetMapPath = path11__default.default.join(distDir, "asset-map.json");
4939
5572
  await fs__default.default.writeFile(assetMapPath, JSON.stringify(assetMap, null, 2));
4940
5573
  } catch (error) {
4941
5574
  logger.error(
@@ -4962,7 +5595,8 @@ Or use the --bump flag:
4962
5595
  content_type: a.contentType
4963
5596
  }))
4964
5597
  })
4965
- }
5598
+ },
5599
+ env
4966
5600
  );
4967
5601
  const pubData = await pubResponse.json();
4968
5602
  const pubBody = pubData.statusCode ? pubData.body : pubData;
@@ -4988,6 +5622,12 @@ Or use the --bump flag:
4988
5622
  }
4989
5623
  if (assetUploads.length > 0) {
4990
5624
  logger.startSpinner(`Uploading ${assetUploads.length} asset(s) to S3...`);
5625
+ if (assetUploads[0]) {
5626
+ logger.log(
5627
+ ` [debug] sample presigned PUT URL: ${assetUploads[0].upload_url}`
5628
+ );
5629
+ logger.log(` [debug] sample s3_key: ${assetUploads[0].s3_key}`);
5630
+ }
4991
5631
  const CONCURRENCY = 8;
4992
5632
  const byHashedPath = new Map(regularAssets.map((a) => [a.hashedPath, a]));
4993
5633
  const queue = [...assetUploads];
@@ -5014,6 +5654,13 @@ Or use the --bump flag:
5014
5654
  body: buf
5015
5655
  });
5016
5656
  if (!res.ok) {
5657
+ if (failed === 0) {
5658
+ const errBody = await res.text().catch(() => "(unreadable)");
5659
+ logger.log(` [debug] PUT ${item.upload_url}`);
5660
+ logger.log(
5661
+ ` [debug] response ${res.status} ${res.statusText}: ${errBody.slice(0, 500)}`
5662
+ );
5663
+ }
5017
5664
  throw new Error(`HTTP ${res.status}`);
5018
5665
  }
5019
5666
  uploaded++;
@@ -5044,7 +5691,7 @@ Or use the --bump flag:
5044
5691
  logger.error("Build the theme first: onexthm build");
5045
5692
  process.exit(1);
5046
5693
  }
5047
- const bundleZipPath = path9__default.default.join(themePath, "dist", "bundle.zip");
5694
+ const bundleZipPath = path11__default.default.join(themePath, "dist", "bundle.zip");
5048
5695
  await createZip(distDir, bundleZipPath, [
5049
5696
  "bundle.zip",
5050
5697
  "source.zip",
@@ -5071,7 +5718,7 @@ Or use the --bump flag:
5071
5718
  }
5072
5719
  logger.startSpinner("Uploading source...");
5073
5720
  try {
5074
- const sourceZipPath = path9__default.default.join(themePath, "dist", "source.zip");
5721
+ const sourceZipPath = path11__default.default.join(themePath, "dist", "source.zip");
5075
5722
  await createZip(themePath, sourceZipPath, [
5076
5723
  "node_modules",
5077
5724
  "dist",
@@ -5098,7 +5745,8 @@ Or use the --bump flag:
5098
5745
  try {
5099
5746
  const confirmResponse = await authenticatedFetch(
5100
5747
  `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(version2)}/confirm`,
5101
- { method: "POST" }
5748
+ { method: "POST" },
5749
+ env
5102
5750
  );
5103
5751
  const confirmData = await confirmResponse.json();
5104
5752
  const confirmBody = confirmData.statusCode ? confirmData.body : confirmData;
@@ -5144,9 +5792,9 @@ Or use the --bump flag:
5144
5792
  }
5145
5793
  logger.newLine();
5146
5794
  logger.success(`\u2713 Theme "${themeId}" v${version2} published!`);
5147
- await uploadThumbnail(apiUrl, themeId, themePath, distDir);
5795
+ await uploadThumbnail(apiUrl, themeId, themePath, distDir, env);
5148
5796
  }
5149
- async function uploadThumbnail(apiUrl, themeId, themePath, distDir) {
5797
+ async function uploadThumbnail(apiUrl, themeId, themePath, distDir, env = "dev") {
5150
5798
  const THUMBNAIL_CANDIDATES = [
5151
5799
  { file: "thumbnail.png", mime: "image/png" },
5152
5800
  { file: "thumbnail.jpg", mime: "image/jpeg" },
@@ -5156,7 +5804,7 @@ async function uploadThumbnail(apiUrl, themeId, themePath, distDir) {
5156
5804
  let imageBase64 = null;
5157
5805
  let mimeType = "image/png";
5158
5806
  for (const { file, mime } of THUMBNAIL_CANDIDATES) {
5159
- const candidate = path9__default.default.join(themePath, file);
5807
+ const candidate = path11__default.default.join(themePath, file);
5160
5808
  if (fs__default.default.existsSync(candidate)) {
5161
5809
  const buf = fs__default.default.readFileSync(candidate);
5162
5810
  imageBase64 = `data:${mime};base64,${buf.toString("base64")}`;
@@ -5181,9 +5829,19 @@ async function uploadThumbnail(apiUrl, themeId, themePath, distDir) {
5181
5829
  }
5182
5830
  }
5183
5831
  logger.startSpinner("Uploading thumbnail...");
5832
+ const imageUploadUrl = `${apiUrl}/media/images/upload`;
5833
+ const imageRequestBody = {
5834
+ prefix: `themes/${themeId}`,
5835
+ image: imageBase64 ? `${imageBase64.slice(0, 60)}... [${Math.round(imageBase64.length * 3 / 4 / 1024)} KB]` : null,
5836
+ name: "thumbnail.png"
5837
+ };
5838
+ logger.log(` \u2192 POST ${imageUploadUrl}`);
5839
+ logger.log(
5840
+ ` \u2192 body: ${JSON.stringify({ ...imageRequestBody, image: imageBase64 ? `<base64 ${mimeType} truncated>` : null })}`
5841
+ );
5184
5842
  try {
5185
5843
  const uploadRes = await authenticatedFetch(
5186
- `${apiUrl}/media/images/upload`,
5844
+ imageUploadUrl,
5187
5845
  {
5188
5846
  method: "POST",
5189
5847
  body: JSON.stringify({
@@ -5191,30 +5849,56 @@ async function uploadThumbnail(apiUrl, themeId, themePath, distDir) {
5191
5849
  image: imageBase64,
5192
5850
  name: "thumbnail.png"
5193
5851
  })
5194
- }
5852
+ },
5853
+ env
5195
5854
  );
5196
- const uploadData = await uploadRes.json();
5855
+ logger.log(` \u2190 HTTP ${uploadRes.status} ${uploadRes.statusText}`);
5856
+ const uploadRawText = await uploadRes.text();
5857
+ logger.log(` \u2190 raw response: ${uploadRawText.slice(0, 500)}`);
5858
+ let uploadData;
5859
+ try {
5860
+ uploadData = JSON.parse(uploadRawText);
5861
+ } catch {
5862
+ throw new Error(
5863
+ `Image upload returned non-JSON (HTTP ${uploadRes.status}): ${uploadRawText.slice(0, 200)}`
5864
+ );
5865
+ }
5197
5866
  const uploadBody = uploadData.statusCode ? uploadData.body : uploadData;
5198
5867
  if (!uploadRes.ok || !uploadBody.url) {
5199
- throw new Error(uploadBody.error || "Upload failed");
5868
+ throw new Error(
5869
+ `Image upload failed \u2014 HTTP ${uploadRes.status}: ${uploadBody.error || uploadBody.message || JSON.stringify(uploadBody).slice(0, 300)}`
5870
+ );
5200
5871
  }
5872
+ const patchUrl = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}`;
5873
+ logger.log(` \u2192 PATCH ${patchUrl}`);
5201
5874
  const patchRes = await authenticatedFetch(
5202
- `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}`,
5875
+ patchUrl,
5203
5876
  {
5204
5877
  method: "PATCH",
5205
5878
  body: JSON.stringify({ thumbnail_url: uploadBody.url })
5206
- }
5879
+ },
5880
+ env
5207
5881
  );
5882
+ logger.log(` \u2190 HTTP ${patchRes.status} ${patchRes.statusText}`);
5208
5883
  if (!patchRes.ok) {
5209
- const patchData = await patchRes.json();
5210
- const patchBody = patchData.statusCode ? patchData.body : patchData;
5211
- throw new Error(patchBody.error || "Failed to set thumbnail");
5884
+ const patchRawText = await patchRes.text();
5885
+ logger.log(` \u2190 raw response: ${patchRawText.slice(0, 500)}`);
5886
+ let patchBody = {};
5887
+ try {
5888
+ patchBody = JSON.parse(patchRawText);
5889
+ } catch {
5890
+ }
5891
+ patchBody = patchBody.statusCode ? patchBody.body : patchBody;
5892
+ throw new Error(
5893
+ `Thumbnail patch failed \u2014 HTTP ${patchRes.status}: ${patchBody.error || patchBody.message || patchRawText.slice(0, 200)}`
5894
+ );
5212
5895
  }
5213
5896
  logger.stopSpinner(true, "Thumbnail set");
5214
5897
  } catch (err) {
5215
- logger.stopSpinner(false, "Thumbnail upload skipped");
5898
+ logger.stopSpinner(false, "Thumbnail upload failed");
5899
+ logger.error(err instanceof Error ? err.message : String(err));
5216
5900
  logger.info(
5217
- `Theme published successfully. Thumbnail can be updated later.`
5901
+ "Theme published successfully. Thumbnail can be updated later."
5218
5902
  );
5219
5903
  }
5220
5904
  }
@@ -5222,7 +5906,7 @@ async function screenshotHomePage(themePath, distDir) {
5222
5906
  const { compilePreviewRuntime: compilePreviewRuntime2 } = await Promise.resolve().then(() => (init_compile_theme(), compile_theme_exports));
5223
5907
  const { createDevServer: createDevServer2 } = await Promise.resolve().then(() => (init_dev_server(), dev_server_exports));
5224
5908
  const previewRuntimePath = await compilePreviewRuntime2(themePath);
5225
- const themeName = path9__default.default.basename(themePath);
5909
+ const themeName = path11__default.default.basename(themePath);
5226
5910
  const port = await findFreePort(4500);
5227
5911
  const server = createDevServer2({
5228
5912
  port,
@@ -5278,25 +5962,40 @@ async function findFreePort(start) {
5278
5962
  srv.on("error", () => resolve(findFreePort(start + 1)));
5279
5963
  });
5280
5964
  }
5281
- async function uploadVideoMultipart(apiUrl, themeId, video) {
5282
- const fileName = path9__default.default.basename(video.originalPath);
5965
+ async function uploadVideoMultipart(apiUrl, themeId, video, env = "dev") {
5966
+ const fileName = path11__default.default.basename(video.originalPath);
5967
+ const videoInitUrl = `${apiUrl}/media/videos/multipart/init`;
5968
+ const videoInitBody = {
5969
+ file_name: fileName,
5970
+ content_type: video.contentType,
5971
+ file_size: video.size,
5972
+ prefix: `themes/${themeId}/assets`
5973
+ };
5974
+ logger.log(` \u2192 POST ${videoInitUrl}`);
5975
+ logger.log(` \u2192 body: ${JSON.stringify(videoInitBody)}`);
5283
5976
  const initRes = await authenticatedFetch(
5284
- `${apiUrl}/media/videos/multipart/init`,
5977
+ videoInitUrl,
5285
5978
  {
5286
5979
  method: "POST",
5287
- body: JSON.stringify({
5288
- file_name: fileName,
5289
- content_type: video.contentType,
5290
- file_size: video.size,
5291
- prefix: `themes/${themeId}/assets`
5292
- })
5293
- }
5980
+ body: JSON.stringify(videoInitBody)
5981
+ },
5982
+ env
5294
5983
  );
5295
- const initData = await initRes.json();
5984
+ logger.log(` \u2190 HTTP ${initRes.status} ${initRes.statusText}`);
5985
+ const initRawText = await initRes.text();
5986
+ logger.log(` \u2190 raw response: ${initRawText.slice(0, 500)}`);
5987
+ let initData;
5988
+ try {
5989
+ initData = JSON.parse(initRawText);
5990
+ } catch {
5991
+ throw new Error(
5992
+ `Video init returned non-JSON (HTTP ${initRes.status}): ${initRawText.slice(0, 200)}`
5993
+ );
5994
+ }
5296
5995
  const initBody = initData.statusCode ? initData.body : initData;
5297
5996
  if (!initRes.ok || !initBody.upload_id) {
5298
5997
  throw new Error(
5299
- `Init multipart failed for ${fileName}: ${initBody.error || initRes.status}`
5998
+ `Init multipart failed for ${fileName} \u2014 HTTP ${initRes.status}: ${initBody.error || initBody.message || JSON.stringify(initBody).slice(0, 300)}`
5300
5999
  );
5301
6000
  }
5302
6001
  const { upload_id, file_key, chunk_size, chunk_urls } = initBody;
@@ -5331,25 +6030,47 @@ async function uploadVideoMultipart(apiUrl, themeId, video) {
5331
6030
  )
5332
6031
  );
5333
6032
  parts.sort((a, b) => a.part_number - b.part_number);
6033
+ const videoCompleteUrl = `${apiUrl}/media/videos/multipart/complete`;
6034
+ logger.log(` \u2192 POST ${videoCompleteUrl}`);
6035
+ logger.log(
6036
+ ` \u2192 body: ${JSON.stringify({ upload_id, file_key, parts: `[${parts.length} parts]` })}`
6037
+ );
5334
6038
  const completeRes = await authenticatedFetch(
5335
- `${apiUrl}/media/videos/multipart/complete`,
6039
+ videoCompleteUrl,
5336
6040
  {
5337
6041
  method: "POST",
5338
6042
  body: JSON.stringify({ upload_id, file_key, parts })
5339
- }
6043
+ },
6044
+ env
5340
6045
  );
5341
- const completeData = await completeRes.json();
6046
+ logger.log(` \u2190 HTTP ${completeRes.status} ${completeRes.statusText}`);
6047
+ const completeRawText = await completeRes.text();
6048
+ logger.log(` \u2190 raw response: ${completeRawText.slice(0, 500)}`);
6049
+ let completeData;
6050
+ try {
6051
+ completeData = JSON.parse(completeRawText);
6052
+ } catch {
6053
+ throw new Error(
6054
+ `Video complete returned non-JSON (HTTP ${completeRes.status}): ${completeRawText.slice(0, 200)}`
6055
+ );
6056
+ }
5342
6057
  const completeBody = completeData.statusCode ? completeData.body : completeData;
5343
6058
  if (!completeRes.ok || !completeBody.url) {
5344
6059
  try {
5345
- await authenticatedFetch(`${apiUrl}/media/videos/multipart/abort`, {
5346
- method: "POST",
5347
- body: JSON.stringify({ upload_id, file_key })
5348
- });
6060
+ const abortUrl = `${apiUrl}/media/videos/multipart/abort`;
6061
+ logger.log(` \u2192 POST ${abortUrl} (cleanup)`);
6062
+ await authenticatedFetch(
6063
+ abortUrl,
6064
+ {
6065
+ method: "POST",
6066
+ body: JSON.stringify({ upload_id, file_key })
6067
+ },
6068
+ env
6069
+ );
5349
6070
  } catch {
5350
6071
  }
5351
6072
  throw new Error(
5352
- `Complete multipart failed for ${fileName}: ${completeBody.error || completeRes.status}`
6073
+ `Complete multipart failed for ${fileName} \u2014 HTTP ${completeRes.status}: ${completeBody.error || completeBody.message || JSON.stringify(completeBody).slice(0, 300)}`
5353
6074
  );
5354
6075
  }
5355
6076
  return completeBody.url;
@@ -5371,6 +6092,91 @@ async function createZip(sourceDir, outputPath, exclude) {
5371
6092
  archive.finalize();
5372
6093
  });
5373
6094
  }
6095
+ async function runSchemaDiffGate(themeId, distDir, env, options) {
6096
+ logger.startSpinner("Fetching prior version for diff...");
6097
+ const { result: prior, reason } = await fetchPriorGateManifests(themeId, env);
6098
+ if (!prior) {
6099
+ if (reason === "no-prior") {
6100
+ logger.stopSpinner(true, "First publish \u2014 no prior version to diff");
6101
+ } else {
6102
+ logger.stopSpinner(true, `Gate skipped (${reason})`);
6103
+ }
6104
+ return null;
6105
+ }
6106
+ logger.stopSpinner(true, `Fetched prior version ${prior.version}`);
6107
+ let currentSchemas;
6108
+ let currentAssets;
6109
+ try {
6110
+ currentSchemas = JSON.parse(
6111
+ await fs__default.default.readFile(path11__default.default.join(distDir, "schemas.json"), "utf-8")
6112
+ );
6113
+ } catch (err) {
6114
+ logger.warning(
6115
+ `Gate skipped: dist/schemas.json missing or unreadable (${err instanceof Error ? err.message : "unknown"})`
6116
+ );
6117
+ return null;
6118
+ }
6119
+ try {
6120
+ currentAssets = JSON.parse(
6121
+ await fs__default.default.readFile(path11__default.default.join(distDir, "asset-manifest.json"), "utf-8")
6122
+ );
6123
+ } catch {
6124
+ currentAssets = { manifestVersion: 1, assets: [] };
6125
+ }
6126
+ const changes = diffManifests(
6127
+ { schemas: prior.schemas, assets: prior.assets },
6128
+ { schemas: currentSchemas, assets: currentAssets }
6129
+ );
6130
+ const classification = classify(changes);
6131
+ printGateReport(prior.version, classification);
6132
+ if (options.dryRun) return classification;
6133
+ const isBreaking = classification.highest === "breaking" || classification.highest === "breaking-severe" || classification.highest === "breaking-asset";
6134
+ if (isBreaking && !options.force) {
6135
+ logger.error(
6136
+ "\nPublish blocked: breaking changes detected.\n \u2022 Bump major version and ship migration notes, OR\n \u2022 Re-run with --force to override (logged in the audit trail)."
6137
+ );
6138
+ process.exit(1);
6139
+ }
6140
+ if (classification.highest === "defaults-only" && !options.confirmDefaults) {
6141
+ logger.error(
6142
+ "\nPublish blocked: default values changed.\nThese defaults will propagate to every customer site that hasn't overridden\nthe field. Re-run with --confirm-defaults to acknowledge the change, or\nrevert the default if it wasn't intentional."
6143
+ );
6144
+ process.exit(1);
6145
+ }
6146
+ return classification;
6147
+ }
6148
+ function printGateReport(priorVersion, classification) {
6149
+ logger.newLine();
6150
+ logger.info(`Schema diff vs. v${priorVersion}:`);
6151
+ if (classification.changes.length === 0) {
6152
+ logger.log(" \u2713 Safe \u2014 no schema changes detected");
6153
+ return;
6154
+ }
6155
+ for (const change of classification.changes) {
6156
+ const icon = iconFor(change.kind);
6157
+ logger.log(` ${icon} [${change.kind}] ${change.path} \u2014 ${change.detail}`);
6158
+ }
6159
+ logger.log(
6160
+ `
6161
+ \u2192 Classification: ${classification.highest}. Suggested bump: ${classification.bump}.`
6162
+ );
6163
+ }
6164
+ function iconFor(kind) {
6165
+ switch (kind) {
6166
+ case "safe":
6167
+ case "safe-rename":
6168
+ return "\u2713";
6169
+ case "additive":
6170
+ return "+";
6171
+ case "defaults-only":
6172
+ return "\u26A0";
6173
+ case "breaking":
6174
+ case "breaking-asset":
6175
+ return "\u2717";
6176
+ case "breaking-severe":
6177
+ return "\u2717\u2717";
6178
+ }
6179
+ }
5374
6180
 
5375
6181
  // src/commands/mcp.ts
5376
6182
  init_logger();
@@ -5382,18 +6188,18 @@ var AI_CONTEXT_FILES = [
5382
6188
  ".mcp.json"
5383
6189
  ];
5384
6190
  function resolveTargetDir(opts) {
5385
- return path9__default.default.resolve(opts.cwd ?? process.cwd());
6191
+ return path11__default.default.resolve(opts.cwd ?? process.cwd());
5386
6192
  }
5387
6193
  function resolveDefaultTemplateDir() {
5388
- return path9__default.default.join(getTemplatesDir(), "default");
6194
+ return path11__default.default.join(getTemplatesDir(), "default");
5389
6195
  }
5390
6196
  function isThemeDir(dir) {
5391
- return fs__default.default.existsSync(path9__default.default.join(dir, "theme.config.ts")) || fs__default.default.existsSync(path9__default.default.join(dir, "theme.config.js"));
6197
+ return fs__default.default.existsSync(path11__default.default.join(dir, "theme.config.ts")) || fs__default.default.existsSync(path11__default.default.join(dir, "theme.config.js"));
5392
6198
  }
5393
6199
  function inspectFiles(templateDir, targetDir) {
5394
6200
  return AI_CONTEXT_FILES.map((name) => {
5395
- const templatePath = path9__default.default.join(templateDir, name);
5396
- const targetPath = path9__default.default.join(targetDir, name);
6201
+ const templatePath = path11__default.default.join(templateDir, name);
6202
+ const targetPath = path11__default.default.join(targetDir, name);
5397
6203
  const exists = fs__default.default.existsSync(targetPath);
5398
6204
  let identical = false;
5399
6205
  if (exists && fs__default.default.existsSync(templatePath)) {
@@ -5536,7 +6342,7 @@ async function mcpDoctorCommand(options = {}) {
5536
6342
  return;
5537
6343
  }
5538
6344
  logger.success("theme.config.ts present");
5539
- const mcpJsonPath = path9__default.default.join(targetDir, ".mcp.json");
6345
+ const mcpJsonPath = path11__default.default.join(targetDir, ".mcp.json");
5540
6346
  if (!fs__default.default.existsSync(mcpJsonPath)) {
5541
6347
  logger.error(".mcp.json missing \u2014 run `onexthm mcp setup`");
5542
6348
  } else {
@@ -5574,7 +6380,7 @@ async function mcpDoctorCommand(options = {}) {
5574
6380
  logger.success(`${s.name} up to date`);
5575
6381
  }
5576
6382
  }
5577
- const registryPath = path9__default.default.join(targetDir, "sections-registry.ts");
6383
+ const registryPath = path11__default.default.join(targetDir, "sections-registry.ts");
5578
6384
  if (fs__default.default.existsSync(registryPath)) {
5579
6385
  logger.success("sections-registry.ts present");
5580
6386
  } else {
@@ -5586,22 +6392,22 @@ async function mcpDoctorCommand(options = {}) {
5586
6392
 
5587
6393
  // src/cli.ts
5588
6394
  dotenv__default.default.config({
5589
- path: path9__default.default.join(process.cwd(), ".env.local"),
6395
+ path: path11__default.default.join(process.cwd(), ".env.local"),
5590
6396
  override: true
5591
6397
  });
5592
- dotenv__default.default.config({ path: path9__default.default.join(process.cwd(), ".env") });
6398
+ dotenv__default.default.config({ path: path11__default.default.join(process.cwd(), ".env") });
5593
6399
  try {
5594
6400
  const projectRoot = getProjectRoot();
5595
- if (path9__default.default.resolve(projectRoot) !== path9__default.default.resolve(process.cwd())) {
6401
+ if (path11__default.default.resolve(projectRoot) !== path11__default.default.resolve(process.cwd())) {
5596
6402
  dotenv__default.default.config({
5597
- path: path9__default.default.join(projectRoot, ".env.local")
6403
+ path: path11__default.default.join(projectRoot, ".env.local")
5598
6404
  });
5599
- dotenv__default.default.config({ path: path9__default.default.join(projectRoot, ".env") });
6405
+ dotenv__default.default.config({ path: path11__default.default.join(projectRoot, ".env") });
5600
6406
  }
5601
6407
  } catch {
5602
6408
  }
5603
6409
  dotenv__default.default.config({
5604
- path: path9__default.default.join(os__default.default.homedir(), ".onexthm", ".env"),
6410
+ path: path11__default.default.join(os__default.default.homedir(), ".onexthm", ".env"),
5605
6411
  quiet: true
5606
6412
  });
5607
6413
  var require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.js', document.baseURI).href)));
@@ -5612,7 +6418,11 @@ program.command("init").description("Create a new OneX theme project").argument(
5612
6418
  "-t, --template <template>",
5613
6419
  "Template to use (default, minimal)",
5614
6420
  "default"
5615
- ).option("--no-install", "Skip installing dependencies").option("--git", "Initialize git repository").option("-y, --yes", "Skip prompts and use defaults").action(initCommand);
6421
+ ).option("--no-install", "Skip installing dependencies").option("--git", "Initialize git repository").option("-y, --yes", "Skip prompts and use defaults").option(
6422
+ "--env <env>",
6423
+ "Target environment: dev, staging, or prod (default: dev)",
6424
+ "dev"
6425
+ ).action(initCommand);
5616
6426
  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(
5617
6427
  "-c, --category <category>",
5618
6428
  "Section category (headers, content, footers)"
@@ -5639,16 +6449,36 @@ program.command("download").description("Download a published theme via the webs
5639
6449
  "-v, --version <version>",
5640
6450
  "Theme version (default: latest)",
5641
6451
  "latest"
5642
- ).option("-b, --bucket <name>", "[deprecated] ignored").option("-e, --environment <env>", "[deprecated] ignored").option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
6452
+ ).option(
6453
+ "--env <env>",
6454
+ "Target environment: dev, staging, or prod (default: dev)",
6455
+ "dev"
6456
+ ).option("-b, --bucket <name>", "[deprecated] ignored").option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
5643
6457
  program.command("clone").description("Clone theme source code via the website-api").argument("<theme-name>", "Theme to clone").option(
5644
6458
  "-v, --version <version>",
5645
6459
  "Theme version (default: latest)",
5646
6460
  "latest"
5647
- ).option("-n, --name <name>", "New theme name (skips interactive prompt)").option("-o, --output <dir>", "Output directory").option("-b, --bucket <name>", "[deprecated] ignored").option("-e, --environment <env>", "[deprecated] ignored").option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
6461
+ ).option("-n, --name <name>", "New theme name (skips interactive prompt)").option("-o, --output <dir>", "Output directory").option(
6462
+ "--env <env>",
6463
+ "Target environment: dev, staging, or prod (default: dev)",
6464
+ "dev"
6465
+ ).option("-b, --bucket <name>", "[deprecated] ignored").option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
5648
6466
  program.command("config").description("Configure OneX CLI credentials (AWS, API keys)").action(configCommand);
5649
- program.command("login").description("Login to OneX platform").action(loginCommand);
5650
- program.command("logout").description("Logout from OneX platform").action(logoutCommand);
5651
- program.command("whoami").description("Show current logged-in developer").action(whoamiCommand);
6467
+ program.command("login").description("Login to OneX platform").option(
6468
+ "--env <env>",
6469
+ "Target environment: dev, staging, or prod (default: dev)",
6470
+ "dev"
6471
+ ).action(loginCommand);
6472
+ program.command("logout").description("Logout from OneX platform").option(
6473
+ "--env <env>",
6474
+ "Target environment: dev, staging, or prod (default: dev)",
6475
+ "dev"
6476
+ ).action(logoutCommand);
6477
+ program.command("whoami").description("Show current logged-in developer").option(
6478
+ "--env <env>",
6479
+ "Target environment: dev, staging, or prod (default: dev)",
6480
+ "dev"
6481
+ ).action(whoamiCommand);
5652
6482
  var mcpCmd = program.command("mcp").description("Manage MCP server registration and AI-context files");
5653
6483
  mcpCmd.command("setup").description(
5654
6484
  "Install .mcp.json + CLAUDE.md + AGENTS.md + .cursorrules into the current theme"
@@ -5660,6 +6490,19 @@ mcpCmd.command("doctor").description("Diagnose MCP setup in the current theme di
5660
6490
  program.command("publish").description("Build, scan, and publish theme to marketplace (requires login)").option("-t, --theme <path>", "Theme directory path").option(
5661
6491
  "--bump <type>",
5662
6492
  "Auto-bump version before publish (patch|minor|major)"
6493
+ ).option(
6494
+ "--env <env>",
6495
+ "Target environment: dev, staging, or prod (default: dev)",
6496
+ "dev"
6497
+ ).option(
6498
+ "--dry-run",
6499
+ "Build locally and print the schema-diff classification without publishing"
6500
+ ).option(
6501
+ "--confirm-defaults",
6502
+ "Confirm that changed section/block defaults should propagate to live sites"
6503
+ ).option(
6504
+ "--force",
6505
+ "Publish even when the diff gate detects a breaking change"
5663
6506
  ).action(publishCommand);
5664
6507
  program.configureOutput({
5665
6508
  writeErr: (str) => process.stderr.write(chalk4__default.default.red(str))