@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.mjs CHANGED
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import chalk4 from 'chalk';
3
3
  import ora from 'ora';
4
- import * as esbuild from 'esbuild';
5
- import path9 from 'path';
6
- import fs8 from 'fs/promises';
7
- import crypto from 'crypto';
4
+ import path11 from 'path';
8
5
  import { glob } from 'glob';
6
+ import fs from 'fs-extra';
7
+ import crypto from 'crypto';
8
+ import * as esbuild from 'esbuild';
9
+ import fs9 from 'fs/promises';
9
10
  import { createRequire } from 'module';
10
11
  import http from 'http';
11
12
  import fs3 from 'fs';
12
13
  import { WebSocketServer, WebSocket } from 'ws';
13
14
  import os from 'os';
14
15
  import dotenv from 'dotenv';
15
- import fs from 'fs-extra';
16
16
  import ejs from 'ejs';
17
17
  import { execSync, spawn } from 'child_process';
18
18
  import { Command } from 'commander';
@@ -89,6 +89,206 @@ var init_logger = __esm({
89
89
  logger = new Logger();
90
90
  }
91
91
  });
92
+ function sortedCopy(value) {
93
+ if (Array.isArray(value)) {
94
+ return value.map((v) => sortedCopy(v));
95
+ }
96
+ if (value && typeof value === "object") {
97
+ const sorted = {};
98
+ for (const key of Object.keys(value).sort()) {
99
+ sorted[key] = sortedCopy(value[key]);
100
+ }
101
+ return sorted;
102
+ }
103
+ return value;
104
+ }
105
+ function normalizeField(raw) {
106
+ const out = {
107
+ id: String(raw.id),
108
+ type: String(raw.type)
109
+ };
110
+ if (raw.required === true) out.required = true;
111
+ if (raw.default !== void 0) out.default = raw.default;
112
+ if (Array.isArray(raw.aliases) && raw.aliases.length > 0) {
113
+ out.aliases = [...raw.aliases].map(String).sort();
114
+ }
115
+ if (typeof raw.maxLength === "number") out.maxLength = raw.maxLength;
116
+ if (typeof raw.min === "number") out.min = raw.min;
117
+ if (typeof raw.max === "number") out.max = raw.max;
118
+ if (typeof raw.step === "number") out.step = raw.step;
119
+ if (Array.isArray(raw.accept)) {
120
+ out.accept = [...raw.accept].map(String).sort();
121
+ }
122
+ if (Array.isArray(raw.options)) {
123
+ out.options = raw.options.map((o) => String(o?.value ?? o)).sort();
124
+ }
125
+ return out;
126
+ }
127
+ function normalizeBlock(raw) {
128
+ return {
129
+ type: String(raw.type),
130
+ settings: Array.isArray(raw.settings) ? raw.settings.map(normalizeField).sort(sortFieldsById) : [],
131
+ defaults: raw.defaults && typeof raw.defaults === "object" ? sortedCopy(raw.defaults) : {},
132
+ ...typeof raw.limit === "number" ? { limit: raw.limit } : {},
133
+ ...typeof raw.min === "number" ? { min: raw.min } : {},
134
+ ...raw.sortable === true ? { sortable: true } : {},
135
+ ...raw.baseType ? { baseType: String(raw.baseType) } : {}
136
+ };
137
+ }
138
+ function normalizeTemplate(raw) {
139
+ const out = { id: String(raw.id) };
140
+ if (raw.isDefault === true) out.isDefault = true;
141
+ if (Array.isArray(raw.settings)) {
142
+ out.settings = raw.settings.map(normalizeField).sort(sortFieldsById);
143
+ }
144
+ if (raw.defaults && typeof raw.defaults === "object") {
145
+ out.defaults = sortedCopy(raw.defaults);
146
+ }
147
+ return out;
148
+ }
149
+ function sortFieldsById(a, b) {
150
+ return a.id.localeCompare(b.id);
151
+ }
152
+ function sortByType(a, b) {
153
+ return a.type.localeCompare(b.type);
154
+ }
155
+ function normalizeSection(raw) {
156
+ return {
157
+ type: String(raw.type),
158
+ settings: Array.isArray(raw.settings) ? raw.settings.map(normalizeField).sort(sortFieldsById) : [],
159
+ defaults: raw.defaults && typeof raw.defaults === "object" ? sortedCopy(raw.defaults) : {},
160
+ blocks: Array.isArray(raw.blocks) ? raw.blocks.map(normalizeBlock).sort(sortByType) : [],
161
+ templates: Array.isArray(raw.templates) ? raw.templates.map(normalizeTemplate).sort(sortFieldsById) : [],
162
+ dataRequirements: raw.dataRequirements && typeof raw.dataRequirements === "object" ? sortedCopy(raw.dataRequirements) : null,
163
+ ...raw.global === true ? { global: true } : {},
164
+ ...typeof raw.maxBlocks === "number" ? { maxBlocks: raw.maxBlocks } : {}
165
+ };
166
+ }
167
+ async function extractSchemas(themePath) {
168
+ const { createJiti } = await import('jiti');
169
+ const jiti = createJiti(import.meta.url);
170
+ const schemaFiles = await glob("sections/**/*.schema.ts", { cwd: themePath });
171
+ const sections = {};
172
+ for (const file of schemaFiles) {
173
+ try {
174
+ const mod = await jiti.import(path11.join(themePath, file));
175
+ const exports$1 = mod;
176
+ for (const value of Object.values(exports$1)) {
177
+ if (value && typeof value === "object" && typeof value.type === "string" && Array.isArray(value.settings)) {
178
+ const section = normalizeSection(value);
179
+ sections[section.type] = section;
180
+ }
181
+ }
182
+ } catch {
183
+ }
184
+ }
185
+ const manifest = {
186
+ manifestVersion: 1,
187
+ sections: {}
188
+ };
189
+ for (const type of Object.keys(sections).sort()) {
190
+ manifest.sections[type] = sections[type];
191
+ }
192
+ return manifest;
193
+ }
194
+ function serializeManifest(manifest) {
195
+ return JSON.stringify(sortedCopy(manifest), null, 2);
196
+ }
197
+ var init_extract_schemas = __esm({
198
+ "src/utils/extract-schemas.ts"() {
199
+ }
200
+ });
201
+ function isVideoAsset(filePath) {
202
+ const lower = filePath.toLowerCase();
203
+ return VIDEO_EXTENSIONS.some((ext) => lower.endsWith(ext));
204
+ }
205
+ function mimeFor(filename) {
206
+ const ext = path11.extname(filename).toLowerCase();
207
+ return MIME_MAP[ext] || "application/octet-stream";
208
+ }
209
+ async function sha256Prefix(absPath, len) {
210
+ const buf = await fs.readFile(absPath);
211
+ return crypto.createHash("sha256").update(buf).digest("hex").slice(0, len);
212
+ }
213
+ function insertHashIntoName(relPath, hash) {
214
+ const dir = path11.posix.dirname(relPath);
215
+ const base = path11.posix.basename(relPath);
216
+ const ext = path11.posix.extname(base);
217
+ const stem = ext ? base.slice(0, -ext.length) : base;
218
+ const hashed = `${stem}-${hash}${ext}`;
219
+ return dir === "." ? hashed : `${dir}/${hashed}`;
220
+ }
221
+ async function scanThemeAssets(distDir) {
222
+ const assetsDir = path11.join(distDir, "theme-assets");
223
+ if (!await fs.pathExists(assetsDir)) return [];
224
+ const files = await glob("**/*", {
225
+ cwd: assetsDir,
226
+ nodir: true,
227
+ dot: false
228
+ });
229
+ const results = [];
230
+ for (const rel of files) {
231
+ const absPath = path11.join(assetsDir, rel);
232
+ const stat = await fs.stat(absPath);
233
+ if (!stat.isFile()) continue;
234
+ const originalPath = rel.split(path11.sep).join("/");
235
+ const hash = await sha256Prefix(absPath, HASH_LEN);
236
+ const hashedPath = insertHashIntoName(originalPath, hash);
237
+ const contentType = mimeFor(rel);
238
+ results.push({
239
+ originalPath,
240
+ hashedPath,
241
+ hash,
242
+ size: stat.size,
243
+ contentType,
244
+ absPath
245
+ });
246
+ }
247
+ results.sort((a, b) => a.originalPath.localeCompare(b.originalPath));
248
+ return results;
249
+ }
250
+ function buildAssetMap(entries) {
251
+ const map = {};
252
+ for (const e of entries) {
253
+ map[e.originalPath] = e.hashedPath;
254
+ }
255
+ return map;
256
+ }
257
+ var MIME_MAP, HASH_LEN, VIDEO_EXTENSIONS;
258
+ var init_scan_theme_assets = __esm({
259
+ "src/utils/scan-theme-assets.ts"() {
260
+ MIME_MAP = {
261
+ ".png": "image/png",
262
+ ".jpg": "image/jpeg",
263
+ ".jpeg": "image/jpeg",
264
+ ".gif": "image/gif",
265
+ ".webp": "image/webp",
266
+ ".avif": "image/avif",
267
+ ".svg": "image/svg+xml",
268
+ ".ico": "image/x-icon",
269
+ ".bmp": "image/bmp",
270
+ ".woff": "font/woff",
271
+ ".woff2": "font/woff2",
272
+ ".ttf": "font/ttf",
273
+ ".otf": "font/otf",
274
+ ".eot": "application/vnd.ms-fontobject",
275
+ ".mp4": "video/mp4",
276
+ ".webm": "video/webm",
277
+ ".mov": "video/quicktime",
278
+ ".ogg": "video/ogg",
279
+ ".json": "application/json"
280
+ };
281
+ HASH_LEN = 8;
282
+ VIDEO_EXTENSIONS = [
283
+ ".mp4",
284
+ ".webm",
285
+ ".ogg",
286
+ ".mov",
287
+ ".avi",
288
+ ".mkv"
289
+ ];
290
+ }
291
+ });
92
292
 
93
293
  // src/utils/compile-theme.ts
94
294
  var compile_theme_exports = {};
@@ -104,8 +304,8 @@ async function generateThemeCSS(themePath, outDir) {
104
304
  const tailwindcss = (await import('tailwindcss')).default;
105
305
  const tailwindConfig = {
106
306
  content: [
107
- path9.join(themePath, "sections/**/*.{ts,tsx}"),
108
- path9.join(themePath, "components/**/*.{ts,tsx}")
307
+ path11.join(themePath, "sections/**/*.{ts,tsx}"),
308
+ path11.join(themePath, "components/**/*.{ts,tsx}")
109
309
  ],
110
310
  theme: { extend: {} },
111
311
  plugins: []
@@ -115,7 +315,7 @@ async function generateThemeCSS(themePath, outDir) {
115
315
  inputCSS,
116
316
  { from: void 0 }
117
317
  );
118
- await fs8.writeFile(path9.join(outDir, "bundle.css"), result.css);
318
+ await fs9.writeFile(path11.join(outDir, "bundle.css"), result.css);
119
319
  logger.info("Generated bundle.css");
120
320
  } catch (err) {
121
321
  logger.warning(
@@ -126,12 +326,12 @@ async function generateThemeCSS(themePath, outDir) {
126
326
  async function resolveNodeModulesFile(startDir, relativePath) {
127
327
  let dir = startDir;
128
328
  while (true) {
129
- const candidate = path9.join(dir, "node_modules", relativePath);
329
+ const candidate = path11.join(dir, "node_modules", relativePath);
130
330
  try {
131
- await fs8.access(candidate);
331
+ await fs9.access(candidate);
132
332
  return candidate;
133
333
  } catch {
134
- const parent = path9.dirname(dir);
334
+ const parent = path11.dirname(dir);
135
335
  if (parent === dir) break;
136
336
  dir = parent;
137
337
  }
@@ -155,7 +355,7 @@ async function scanImportsFromPackage(sourceDir, packageName) {
155
355
  });
156
356
  for (const file of sourceFiles) {
157
357
  try {
158
- const content = await fs8.readFile(path9.join(sourceDir, file), "utf-8");
358
+ const content = await fs9.readFile(path11.join(sourceDir, file), "utf-8");
159
359
  for (const match of content.matchAll(namespaceImportRegex)) {
160
360
  const subpath = match[1] ? match[1].slice(1) : "";
161
361
  if (!result[subpath]) result[subpath] = /* @__PURE__ */ new Set();
@@ -209,17 +409,17 @@ function createCoreGlobalPlugin(themePath) {
209
409
  const distFileName = subpath ? `${subpath}.mjs` : "index.mjs";
210
410
  let distPath = await resolveNodeModulesFile(
211
411
  themePath,
212
- path9.join("@onexapis", "core", "dist", distFileName)
412
+ path11.join("@onexapis", "core", "dist", distFileName)
213
413
  );
214
414
  if (!distPath) {
215
415
  distPath = await resolveNodeModulesFile(
216
416
  __dirname,
217
- path9.join("@onexapis", "core", "dist", distFileName)
417
+ path11.join("@onexapis", "core", "dist", distFileName)
218
418
  );
219
419
  }
220
420
  try {
221
421
  if (!distPath) throw new Error("not found");
222
- const distContent = await fs8.readFile(distPath, "utf-8");
422
+ const distContent = await fs9.readFile(distPath, "utf-8");
223
423
  const exportMatches = distContent.matchAll(/export\s*\{([^}]+)\}/g);
224
424
  for (const m of exportMatches) {
225
425
  const names = m[1].split(",").map((n) => {
@@ -448,7 +648,7 @@ async function generateThemeData(themePath, outputDir, themeId) {
448
648
  const pages = {};
449
649
  for (const ext of [".ts", ".js"]) {
450
650
  try {
451
- const mod = await jiti.import(path9.join(themePath, `theme.config${ext}`));
651
+ const mod = await jiti.import(path11.join(themePath, `theme.config${ext}`));
452
652
  themeConfig = mod.default || mod;
453
653
  break;
454
654
  } catch {
@@ -456,20 +656,20 @@ async function generateThemeData(themePath, outputDir, themeId) {
456
656
  }
457
657
  for (const ext of [".ts", ".js"]) {
458
658
  try {
459
- const mod = await jiti.import(path9.join(themePath, `theme.layout${ext}`));
659
+ const mod = await jiti.import(path11.join(themePath, `theme.layout${ext}`));
460
660
  layoutConfig = mod.default || mod;
461
661
  break;
462
662
  } catch {
463
663
  }
464
664
  }
465
665
  const schemas = {};
466
- const sectionsDir = path9.join(themePath, "sections");
666
+ const sectionsDir = path11.join(themePath, "sections");
467
667
  try {
468
- const sectionDirs = await fs8.readdir(sectionsDir);
668
+ const sectionDirs = await fs9.readdir(sectionsDir);
469
669
  for (const dir of sectionDirs) {
470
- const schemaFile = path9.join(sectionsDir, dir, `${dir}.schema.ts`);
670
+ const schemaFile = path11.join(sectionsDir, dir, `${dir}.schema.ts`);
471
671
  try {
472
- await fs8.access(schemaFile);
672
+ await fs9.access(schemaFile);
473
673
  const mod = await jiti.import(schemaFile);
474
674
  for (const [key, value] of Object.entries(mod)) {
475
675
  if (key.endsWith("Schema") && value && typeof value === "object" && value.type) {
@@ -481,14 +681,14 @@ async function generateThemeData(themePath, outputDir, themeId) {
481
681
  }
482
682
  } catch {
483
683
  }
484
- const pagesDir = path9.join(themePath, "pages");
684
+ const pagesDir = path11.join(themePath, "pages");
485
685
  try {
486
- const files = await fs8.readdir(pagesDir);
686
+ const files = await fs9.readdir(pagesDir);
487
687
  for (const file of files) {
488
688
  if (!file.match(/\.(ts|js)$/)) continue;
489
689
  const name = file.replace(/\.(ts|js)$/, "");
490
690
  try {
491
- const mod = await jiti.import(path9.join(pagesDir, file));
691
+ const mod = await jiti.import(path11.join(pagesDir, file));
492
692
  const config = mod.default || mod;
493
693
  const sections = (config.sections || []).map((section) => {
494
694
  const schema = schemas[section.type];
@@ -516,8 +716,8 @@ async function generateThemeData(themePath, outputDir, themeId) {
516
716
  }
517
717
  } catch {
518
718
  }
519
- await fs8.writeFile(
520
- path9.join(outputDir, "theme-data.json"),
719
+ await fs9.writeFile(
720
+ path11.join(outputDir, "theme-data.json"),
521
721
  JSON.stringify(
522
722
  {
523
723
  themeId,
@@ -540,22 +740,22 @@ async function generateThemeData(themePath, outputDir, themeId) {
540
740
  logger.info(`Generated theme-data.json (${Object.keys(pages).length} pages)`);
541
741
  }
542
742
  async function contentHashEntry(outputDir) {
543
- const entryPath = path9.join(outputDir, "bundle-entry.js");
544
- const mapPath = path9.join(outputDir, "bundle-entry.js.map");
743
+ const entryPath = path11.join(outputDir, "bundle-entry.js");
744
+ const mapPath = path11.join(outputDir, "bundle-entry.js.map");
545
745
  let entryContent;
546
746
  try {
547
- entryContent = await fs8.readFile(entryPath, "utf-8");
747
+ entryContent = await fs9.readFile(entryPath, "utf-8");
548
748
  } catch {
549
- const indexPath = path9.join(outputDir, "index.js");
749
+ const indexPath = path11.join(outputDir, "index.js");
550
750
  try {
551
- entryContent = await fs8.readFile(indexPath, "utf-8");
751
+ entryContent = await fs9.readFile(indexPath, "utf-8");
552
752
  } catch {
553
753
  logger.warning("No entry file found in output, skipping content hash");
554
754
  return;
555
755
  }
556
756
  const hash2 = crypto.createHash("sha256").update(entryContent).digest("hex").slice(0, 8);
557
757
  const hashedName2 = `bundle-entry-${hash2}.js`;
558
- const indexMapPath = path9.join(outputDir, "index.js.map");
758
+ const indexMapPath = path11.join(outputDir, "index.js.map");
559
759
  const hashedMapName2 = `bundle-entry-${hash2}.js.map`;
560
760
  entryContent = entryContent.replace(
561
761
  /\/\/# sourceMappingURL=index\.js\.map/,
@@ -563,18 +763,18 @@ async function contentHashEntry(outputDir) {
563
763
  );
564
764
  const oldFiles2 = await glob("bundle-entry-*.js*", { cwd: outputDir });
565
765
  for (const f of oldFiles2) {
566
- await fs8.unlink(path9.join(outputDir, f));
766
+ await fs9.unlink(path11.join(outputDir, f));
567
767
  }
568
- await fs8.writeFile(path9.join(outputDir, hashedName2), entryContent);
569
- await fs8.unlink(indexPath);
768
+ await fs9.writeFile(path11.join(outputDir, hashedName2), entryContent);
769
+ await fs9.unlink(indexPath);
570
770
  try {
571
- await fs8.unlink(entryPath);
771
+ await fs9.unlink(entryPath);
572
772
  } catch {
573
773
  }
574
- await fs8.writeFile(entryPath, entryContent);
774
+ await fs9.writeFile(entryPath, entryContent);
575
775
  try {
576
- await fs8.access(indexMapPath);
577
- await fs8.rename(indexMapPath, path9.join(outputDir, hashedMapName2));
776
+ await fs9.access(indexMapPath);
777
+ await fs9.rename(indexMapPath, path11.join(outputDir, hashedMapName2));
578
778
  } catch {
579
779
  }
580
780
  logger.info(`Entry hashed: ${hashedName2}`);
@@ -589,17 +789,17 @@ async function contentHashEntry(outputDir) {
589
789
  );
590
790
  const oldFiles = await glob("bundle-entry-*.js*", { cwd: outputDir });
591
791
  for (const f of oldFiles) {
592
- await fs8.unlink(path9.join(outputDir, f));
792
+ await fs9.unlink(path11.join(outputDir, f));
593
793
  }
594
- await fs8.writeFile(path9.join(outputDir, hashedName), entryContent);
794
+ await fs9.writeFile(path11.join(outputDir, hashedName), entryContent);
595
795
  try {
596
- await fs8.unlink(entryPath);
796
+ await fs9.unlink(entryPath);
597
797
  } catch {
598
798
  }
599
- await fs8.writeFile(entryPath, entryContent);
799
+ await fs9.writeFile(entryPath, entryContent);
600
800
  try {
601
- await fs8.access(mapPath);
602
- await fs8.rename(mapPath, path9.join(outputDir, hashedMapName));
801
+ await fs9.access(mapPath);
802
+ await fs9.rename(mapPath, path11.join(outputDir, hashedMapName));
603
803
  } catch {
604
804
  }
605
805
  logger.info(`Entry hashed: ${hashedName}`);
@@ -611,7 +811,7 @@ async function extractDataRequirements(themePath) {
611
811
  const requirements = {};
612
812
  for (const file of schemaFiles) {
613
813
  try {
614
- const mod = await jiti.import(path9.join(themePath, file));
814
+ const mod = await jiti.import(path11.join(themePath, file));
615
815
  const exports$1 = mod;
616
816
  for (const value of Object.values(exports$1)) {
617
817
  if (value && typeof value === "object" && typeof value.type === "string" && value.dataRequirements && typeof value.dataRequirements === "object") {
@@ -626,12 +826,46 @@ async function extractDataRequirements(themePath) {
626
826
  }
627
827
  return requirements;
628
828
  }
829
+ async function writeGateManifests(themePath, outputDir) {
830
+ try {
831
+ const schemas = await extractSchemas(themePath);
832
+ await fs9.writeFile(
833
+ path11.join(outputDir, "schemas.json"),
834
+ serializeManifest(schemas)
835
+ );
836
+ logger.info(
837
+ `Generated schemas.json (${Object.keys(schemas.sections).length} sections)`
838
+ );
839
+ } catch (err) {
840
+ logger.warning(
841
+ `schemas.json not written: ${err instanceof Error ? err.message : String(err)}`
842
+ );
843
+ }
844
+ try {
845
+ const entries = await scanThemeAssets(outputDir);
846
+ const assets = entries.map((e) => ({
847
+ path: e.originalPath,
848
+ hash: e.hash,
849
+ size: e.size,
850
+ contentType: e.contentType
851
+ }));
852
+ await fs9.writeFile(
853
+ path11.join(outputDir, "asset-manifest.json"),
854
+ JSON.stringify({ manifestVersion: 1, assets }, null, 2)
855
+ );
856
+ logger.info(`Generated asset-manifest.json (${assets.length} assets)`);
857
+ } catch (err) {
858
+ logger.warning(
859
+ `asset-manifest.json not written: ${err instanceof Error ? err.message : String(err)}`
860
+ );
861
+ }
862
+ }
629
863
  async function generateManifest(themeName, themePath, outputDir) {
630
864
  let version2 = "1.0.0";
631
865
  let themeId = themeName;
632
866
  try {
633
- const pkgContent = await fs8.readFile(
634
- path9.join(themePath, "package.json"),
867
+ const pkgContent = await fs9.readFile(
868
+ path11.join(themePath, "package.json"),
635
869
  "utf-8"
636
870
  );
637
871
  const pkg = JSON.parse(pkgContent);
@@ -649,7 +883,7 @@ async function generateManifest(themeName, themePath, outputDir) {
649
883
  const dataRequirements = await extractDataRequirements(themePath);
650
884
  let hasThemeConfig = false;
651
885
  try {
652
- await fs8.access(path9.join(themePath, "theme.config.ts"));
886
+ await fs9.access(path11.join(themePath, "theme.config.ts"));
653
887
  hasThemeConfig = true;
654
888
  } catch {
655
889
  }
@@ -690,24 +924,24 @@ async function generateManifest(themeName, themePath, outputDir) {
690
924
  // Section data requirements for server-side prefetching (keyed by section type)
691
925
  dataRequirements
692
926
  };
693
- await fs8.writeFile(
694
- path9.join(outputDir, "manifest.json"),
927
+ await fs9.writeFile(
928
+ path11.join(outputDir, "manifest.json"),
695
929
  JSON.stringify(manifest, null, 2)
696
930
  );
697
931
  }
698
932
  async function compileStandaloneTheme(themePath, themeName) {
699
- const outputDir = path9.join(themePath, "dist");
700
- const bundleEntry = path9.join(themePath, "bundle-entry.ts");
701
- const indexEntry = path9.join(themePath, "index.ts");
933
+ const outputDir = path11.join(themePath, "dist");
934
+ const bundleEntry = path11.join(themePath, "bundle-entry.ts");
935
+ const indexEntry = path11.join(themePath, "index.ts");
702
936
  let entryPoint = indexEntry;
703
937
  try {
704
- await fs8.access(bundleEntry);
938
+ await fs9.access(bundleEntry);
705
939
  entryPoint = bundleEntry;
706
940
  } catch {
707
941
  }
708
- const shimPath = path9.join(outputDir, ".process-shim.js");
709
- await fs8.mkdir(outputDir, { recursive: true });
710
- await fs8.writeFile(shimPath, PROCESS_SHIM);
942
+ const shimPath = path11.join(outputDir, ".process-shim.js");
943
+ await fs9.mkdir(outputDir, { recursive: true });
944
+ await fs9.writeFile(shimPath, PROCESS_SHIM);
711
945
  const buildOptions = {
712
946
  entryPoints: [entryPoint],
713
947
  bundle: true,
@@ -758,19 +992,20 @@ async function compileStandaloneTheme(themePath, themeName) {
758
992
  try {
759
993
  const result = await esbuild.build(buildOptions);
760
994
  try {
761
- await fs8.unlink(shimPath);
995
+ await fs9.unlink(shimPath);
762
996
  } catch {
763
997
  }
764
998
  await contentHashEntry(outputDir);
765
- const themeAssetsDir = path9.join(themePath, "assets");
766
- const distThemeAssets = path9.join(outputDir, "theme-assets");
999
+ const themeAssetsDir = path11.join(themePath, "assets");
1000
+ const distThemeAssets = path11.join(outputDir, "theme-assets");
767
1001
  try {
768
- await fs8.access(themeAssetsDir);
769
- await fs8.cp(themeAssetsDir, distThemeAssets, { recursive: true });
1002
+ await fs9.access(themeAssetsDir);
1003
+ await fs9.cp(themeAssetsDir, distThemeAssets, { recursive: true });
770
1004
  logger.info("Copied static assets to dist/theme-assets/");
771
1005
  } catch {
772
1006
  }
773
1007
  await generateManifest(themeName, themePath, outputDir);
1008
+ await writeGateManifests(themePath, outputDir);
774
1009
  await generateThemeData(themePath, outputDir, themeName);
775
1010
  await generateThemeCSS(themePath, outputDir);
776
1011
  if (result.metafile) {
@@ -785,7 +1020,7 @@ async function compileStandaloneTheme(themePath, themeName) {
785
1020
  return true;
786
1021
  } catch (error) {
787
1022
  try {
788
- await fs8.unlink(shimPath);
1023
+ await fs9.unlink(shimPath);
789
1024
  } catch {
790
1025
  }
791
1026
  logger.error(`esbuild compilation failed: ${error}`);
@@ -793,18 +1028,18 @@ async function compileStandaloneTheme(themePath, themeName) {
793
1028
  }
794
1029
  }
795
1030
  async function compileStandaloneThemeDev(themePath, themeName) {
796
- const outputDir = path9.join(themePath, "dist");
797
- const bundleEntry = path9.join(themePath, "bundle-entry.ts");
798
- const indexEntry = path9.join(themePath, "index.ts");
1031
+ const outputDir = path11.join(themePath, "dist");
1032
+ const bundleEntry = path11.join(themePath, "bundle-entry.ts");
1033
+ const indexEntry = path11.join(themePath, "index.ts");
799
1034
  let entryPoint = indexEntry;
800
1035
  try {
801
- await fs8.access(bundleEntry);
1036
+ await fs9.access(bundleEntry);
802
1037
  entryPoint = bundleEntry;
803
1038
  } catch {
804
1039
  }
805
- const shimPath = path9.join(outputDir, ".process-shim.js");
806
- await fs8.mkdir(outputDir, { recursive: true });
807
- await fs8.writeFile(shimPath, PROCESS_SHIM);
1040
+ const shimPath = path11.join(outputDir, ".process-shim.js");
1041
+ await fs9.mkdir(outputDir, { recursive: true });
1042
+ await fs9.writeFile(shimPath, PROCESS_SHIM);
808
1043
  const buildOptions = {
809
1044
  entryPoints: [entryPoint],
810
1045
  bundle: true,
@@ -858,18 +1093,18 @@ async function compileStandaloneThemeDev(themePath, themeName) {
858
1093
  return { context: context2, outputDir };
859
1094
  }
860
1095
  async function compilePreviewRuntime(themePath) {
861
- const outputDir = path9.join(themePath, "dist");
862
- await fs8.mkdir(outputDir, { recursive: true });
863
- const outputPath = path9.join(outputDir, "preview-runtime.js");
1096
+ const outputDir = path11.join(themePath, "dist");
1097
+ await fs9.mkdir(outputDir, { recursive: true });
1098
+ const outputPath = path11.join(outputDir, "preview-runtime.js");
864
1099
  const locations = [
865
- path9.join(__dirname, "..", "preview", "preview-app.tsx"),
866
- path9.join(__dirname, "preview", "preview-app.tsx"),
867
- path9.join(__dirname, "..", "..", "src", "preview", "preview-app.tsx")
1100
+ path11.join(__dirname, "..", "preview", "preview-app.tsx"),
1101
+ path11.join(__dirname, "preview", "preview-app.tsx"),
1102
+ path11.join(__dirname, "..", "..", "src", "preview", "preview-app.tsx")
868
1103
  ];
869
1104
  let previewEntryPath = null;
870
1105
  for (const loc of locations) {
871
1106
  try {
872
- await fs8.access(loc);
1107
+ await fs9.access(loc);
873
1108
  previewEntryPath = loc;
874
1109
  break;
875
1110
  } catch {
@@ -952,10 +1187,10 @@ ${locations.join("\n")}`
952
1187
  if (!lucideScanned) {
953
1188
  lucideScanned = true;
954
1189
  const coreSrcCandidates = [
955
- path9.join(themePath, "node_modules", "@onexapis", "core", "src"),
956
- path9.join(themePath, "..", "..", "packages", "core", "src"),
1190
+ path11.join(themePath, "node_modules", "@onexapis", "core", "src"),
1191
+ path11.join(themePath, "..", "..", "packages", "core", "src"),
957
1192
  // monorepo sibling
958
- path9.join(
1193
+ path11.join(
959
1194
  __dirname,
960
1195
  "..",
961
1196
  "..",
@@ -970,7 +1205,7 @@ ${locations.join("\n")}`
970
1205
  let coreSourceDir = null;
971
1206
  for (const candidate of coreSrcCandidates) {
972
1207
  try {
973
- await fs8.access(candidate);
1208
+ await fs9.access(candidate);
974
1209
  coreSourceDir = candidate;
975
1210
  break;
976
1211
  } catch {
@@ -989,21 +1224,21 @@ ${locations.join("\n")}`
989
1224
  }
990
1225
  } else {
991
1226
  const coreDistCandidates = [
992
- path9.join(themePath, "node_modules", "@onexapis", "core", "dist")
1227
+ path11.join(themePath, "node_modules", "@onexapis", "core", "dist")
993
1228
  ];
994
1229
  const resolvedDist = await resolveNodeModulesFile(
995
1230
  __dirname,
996
- path9.join("@onexapis", "core", "dist")
1231
+ path11.join("@onexapis", "core", "dist")
997
1232
  );
998
1233
  if (resolvedDist) coreDistCandidates.push(resolvedDist);
999
1234
  for (const candidate of coreDistCandidates) {
1000
1235
  try {
1001
- await fs8.access(candidate);
1236
+ await fs9.access(candidate);
1002
1237
  const mjsFiles = await glob("*.mjs", { cwd: candidate });
1003
1238
  const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']lucide-react["']/g;
1004
1239
  for (const file of mjsFiles) {
1005
- const content = await fs8.readFile(
1006
- path9.join(candidate, file),
1240
+ const content = await fs9.readFile(
1241
+ path11.join(candidate, file),
1007
1242
  "utf-8"
1008
1243
  );
1009
1244
  for (const match of content.matchAll(importRegex)) {
@@ -1058,7 +1293,7 @@ export default new Proxy({}, { get: (_, name) => name === '__esModule' ? true :
1058
1293
  const req = createRequire(import.meta.url || __filename);
1059
1294
  const cjsPath = req.resolve("framer-motion");
1060
1295
  const pkgDir = cjsPath.replace(/[/\\]dist[/\\].*$/, "");
1061
- const esmEntry = path9.join(pkgDir, "dist", "es", "index.mjs");
1296
+ const esmEntry = path11.join(pkgDir, "dist", "es", "index.mjs");
1062
1297
  const { existsSync } = await import('fs');
1063
1298
  if (existsSync(esmEntry)) {
1064
1299
  return { path: esmEntry, namespace: "file" };
@@ -1157,8 +1392,8 @@ export function headers() { return new Headers(); }
1157
1392
  });
1158
1393
  }
1159
1394
  };
1160
- const shimPath = path9.join(outputDir, ".process-shim-preview.js");
1161
- await fs8.writeFile(shimPath, PROCESS_SHIM);
1395
+ const shimPath = path11.join(outputDir, ".process-shim-preview.js");
1396
+ await fs9.writeFile(shimPath, PROCESS_SHIM);
1162
1397
  await esbuild.build({
1163
1398
  entryPoints: [previewEntryPath],
1164
1399
  bundle: true,
@@ -1193,7 +1428,7 @@ export function headers() { return new Headers(); }
1193
1428
  }
1194
1429
  });
1195
1430
  try {
1196
- await fs8.unlink(shimPath);
1431
+ await fs9.unlink(shimPath);
1197
1432
  } catch {
1198
1433
  }
1199
1434
  return outputPath;
@@ -1202,6 +1437,8 @@ var PROCESS_SHIM, reactGlobalPlugin, reactQueryGlobalPlugin;
1202
1437
  var init_compile_theme = __esm({
1203
1438
  "src/utils/compile-theme.ts"() {
1204
1439
  init_logger();
1440
+ init_extract_schemas();
1441
+ init_scan_theme_assets();
1205
1442
  PROCESS_SHIM = `
1206
1443
  if (typeof process === "undefined") {
1207
1444
  globalThis.process = {
@@ -1357,7 +1594,7 @@ __export(dev_server_exports, {
1357
1594
  });
1358
1595
  function createDevServer(options) {
1359
1596
  const clients = /* @__PURE__ */ new Set();
1360
- const themeDataPath = path9.join(options.distDir, "theme-data.json");
1597
+ const themeDataPath = path11.join(options.distDir, "theme-data.json");
1361
1598
  const server = http.createServer((req, res) => {
1362
1599
  res.setHeader("Access-Control-Allow-Origin", "*");
1363
1600
  res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
@@ -1383,8 +1620,8 @@ function createDevServer(options) {
1383
1620
  if (pathname.startsWith("/_assets/")) {
1384
1621
  const parts = pathname.replace(/^\/_assets\//, "").split("/");
1385
1622
  const assetSubpath = parts.slice(1).join("/");
1386
- const assetPath = path9.join(options.themePath, "assets", assetSubpath);
1387
- if (!assetPath.startsWith(path9.join(options.themePath, "assets"))) {
1623
+ const assetPath = path11.join(options.themePath, "assets", assetSubpath);
1624
+ if (!assetPath.startsWith(path11.join(options.themePath, "assets"))) {
1388
1625
  res.writeHead(403);
1389
1626
  res.end("Forbidden");
1390
1627
  return;
@@ -1395,8 +1632,8 @@ function createDevServer(options) {
1395
1632
  if (pathname.startsWith("/themes/")) {
1396
1633
  const match = pathname.match(/^\/themes\/[^/]+\/assets\/(.+)/);
1397
1634
  if (match) {
1398
- const assetPath = path9.join(options.themePath, "assets", match[1]);
1399
- if (!assetPath.startsWith(path9.join(options.themePath, "assets"))) {
1635
+ const assetPath = path11.join(options.themePath, "assets", match[1]);
1636
+ if (!assetPath.startsWith(path11.join(options.themePath, "assets"))) {
1400
1637
  res.writeHead(403);
1401
1638
  res.end("Forbidden");
1402
1639
  return;
@@ -1408,26 +1645,26 @@ function createDevServer(options) {
1408
1645
  if (pathname.startsWith("/assets/")) {
1409
1646
  const subpath = pathname.replace(/^\/assets\//, "");
1410
1647
  const segments = subpath.split("/");
1411
- const assetsBase = path9.join(options.themePath, "assets");
1648
+ const assetsBase = path11.join(options.themePath, "assets");
1412
1649
  let assetPath;
1413
1650
  if (segments[0] === options.themeName || segments[0] === options.themeName.replace(/^my-/, "")) {
1414
- assetPath = path9.join(assetsBase, segments.slice(1).join("/"));
1651
+ assetPath = path11.join(assetsBase, segments.slice(1).join("/"));
1415
1652
  } else {
1416
- assetPath = path9.join(assetsBase, subpath);
1653
+ assetPath = path11.join(assetsBase, subpath);
1417
1654
  }
1418
1655
  if (assetPath.startsWith(assetsBase) && fs3.existsSync(assetPath)) {
1419
1656
  serveFile(res, assetPath);
1420
1657
  return;
1421
1658
  }
1422
1659
  if (segments.length > 1) {
1423
- const fallbackPath = path9.join(assetsBase, segments.slice(1).join("/"));
1660
+ const fallbackPath = path11.join(assetsBase, segments.slice(1).join("/"));
1424
1661
  if (fallbackPath.startsWith(assetsBase) && fs3.existsSync(fallbackPath)) {
1425
1662
  serveFile(res, fallbackPath);
1426
1663
  return;
1427
1664
  }
1428
1665
  }
1429
1666
  }
1430
- const filePath = path9.join(options.distDir, pathname);
1667
+ const filePath = path11.join(options.distDir, pathname);
1431
1668
  if (!filePath.startsWith(options.distDir)) {
1432
1669
  res.writeHead(403);
1433
1670
  res.end("Forbidden");
@@ -1470,7 +1707,7 @@ function serveFile(res, filePath) {
1470
1707
  res.end("Not Found");
1471
1708
  return;
1472
1709
  }
1473
- const ext = path9.extname(filePath);
1710
+ const ext = path11.extname(filePath);
1474
1711
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
1475
1712
  const content = fs3.readFileSync(filePath);
1476
1713
  res.writeHead(200, { "Content-Type": contentType });
@@ -1591,18 +1828,18 @@ async function renderTemplate(templatePath, data) {
1591
1828
  return ejs.render(template, data);
1592
1829
  }
1593
1830
  async function writeFile(filePath, content) {
1594
- await fs.ensureDir(path9.dirname(filePath));
1831
+ await fs.ensureDir(path11.dirname(filePath));
1595
1832
  await fs.writeFile(filePath, content, "utf-8");
1596
1833
  }
1597
1834
  function getTemplatesDir() {
1598
1835
  const locations = [
1599
- path9.join(__dirname, "../../templates"),
1836
+ path11.join(__dirname, "../../templates"),
1600
1837
  // Development
1601
- path9.join(__dirname, "../templates"),
1838
+ path11.join(__dirname, "../templates"),
1602
1839
  // Production (dist/)
1603
- path9.join(process.cwd(), "templates"),
1840
+ path11.join(process.cwd(), "templates"),
1604
1841
  // Fallback
1605
- path9.join(process.cwd(), "packages/cli/templates")
1842
+ path11.join(process.cwd(), "packages/cli/templates")
1606
1843
  // Monorepo
1607
1844
  ];
1608
1845
  for (const location of locations) {
@@ -1614,7 +1851,7 @@ function getTemplatesDir() {
1614
1851
  }
1615
1852
  async function copyTemplate(templateName, targetDir, data) {
1616
1853
  const templatesDir = getTemplatesDir();
1617
- const templateDir = path9.join(templatesDir, templateName);
1854
+ const templateDir = path11.join(templatesDir, templateName);
1618
1855
  if (!fs.existsSync(templateDir)) {
1619
1856
  throw new Error(
1620
1857
  `Template "${templateName}" not found at ${templateDir}. Available templates: ${fs.readdirSync(templatesDir).join(", ")}`
@@ -1623,8 +1860,8 @@ async function copyTemplate(templateName, targetDir, data) {
1623
1860
  await fs.ensureDir(targetDir);
1624
1861
  const files = await fs.readdir(templateDir);
1625
1862
  for (const file of files) {
1626
- const templatePath = path9.join(templateDir, file);
1627
- const targetPath = path9.join(targetDir, file);
1863
+ const templatePath = path11.join(templateDir, file);
1864
+ const targetPath = path11.join(targetDir, file);
1628
1865
  const stat = await fs.stat(templatePath);
1629
1866
  if (stat.isDirectory()) {
1630
1867
  await copyTemplateDir(templatePath, targetPath, data);
@@ -1641,8 +1878,8 @@ async function copyTemplateDir(templateDir, targetDir, data) {
1641
1878
  await fs.ensureDir(targetDir);
1642
1879
  const files = await fs.readdir(templateDir);
1643
1880
  for (const file of files) {
1644
- const templatePath = path9.join(templateDir, file);
1645
- const targetPath = path9.join(targetDir, file);
1881
+ const templatePath = path11.join(templateDir, file);
1882
+ const targetPath = path11.join(targetDir, file);
1646
1883
  const stat = await fs.stat(templatePath);
1647
1884
  if (stat.isDirectory()) {
1648
1885
  await copyTemplateDir(templatePath, targetPath, data);
@@ -1657,32 +1894,32 @@ async function copyTemplateDir(templateDir, targetDir, data) {
1657
1894
  }
1658
1895
  function getProjectRoot() {
1659
1896
  let currentDir = process.cwd();
1660
- while (currentDir !== path9.parse(currentDir).root) {
1661
- const packageJsonPath = path9.join(currentDir, "package.json");
1897
+ while (currentDir !== path11.parse(currentDir).root) {
1898
+ const packageJsonPath = path11.join(currentDir, "package.json");
1662
1899
  if (fs.existsSync(packageJsonPath)) {
1663
1900
  const packageJson = fs.readJsonSync(packageJsonPath);
1664
- if (packageJson.workspaces || fs.existsSync(path9.join(currentDir, "src/themes")) || fs.existsSync(path9.join(currentDir, "themes"))) {
1901
+ if (packageJson.workspaces || fs.existsSync(path11.join(currentDir, "src/themes")) || fs.existsSync(path11.join(currentDir, "themes"))) {
1665
1902
  return currentDir;
1666
1903
  }
1667
1904
  }
1668
- currentDir = path9.dirname(currentDir);
1905
+ currentDir = path11.dirname(currentDir);
1669
1906
  }
1670
1907
  return process.cwd();
1671
1908
  }
1672
1909
  function getThemesDir() {
1673
1910
  const root = getProjectRoot();
1674
- if (fs.existsSync(path9.join(root, "themes")))
1675
- return path9.join(root, "themes");
1676
- if (fs.existsSync(path9.join(root, "src/themes")))
1677
- return path9.join(root, "src/themes");
1678
- return path9.dirname(root);
1911
+ if (fs.existsSync(path11.join(root, "themes")))
1912
+ return path11.join(root, "themes");
1913
+ if (fs.existsSync(path11.join(root, "src/themes")))
1914
+ return path11.join(root, "src/themes");
1915
+ return path11.dirname(root);
1679
1916
  }
1680
1917
  function getFeaturesDir() {
1681
- return path9.join(getProjectRoot(), "src/features");
1918
+ return path11.join(getProjectRoot(), "src/features");
1682
1919
  }
1683
1920
  function isOneXProject() {
1684
1921
  const root = getProjectRoot();
1685
- return fs.existsSync(path9.join(root, "themes")) || fs.existsSync(path9.join(root, "src/themes")) || fs.existsSync(path9.join(root, "theme.config.ts")) || fs.existsSync(path9.join(root, "bundle-entry.ts"));
1922
+ return fs.existsSync(path11.join(root, "themes")) || fs.existsSync(path11.join(root, "src/themes")) || fs.existsSync(path11.join(root, "theme.config.ts")) || fs.existsSync(path11.join(root, "bundle-entry.ts"));
1686
1923
  }
1687
1924
  function ensureOneXProject() {
1688
1925
  if (!isOneXProject()) {
@@ -1698,13 +1935,13 @@ function listThemes() {
1698
1935
  return [];
1699
1936
  }
1700
1937
  return fs.readdirSync(themesDir).filter((name) => {
1701
- const themePath = path9.join(themesDir, name);
1702
- return fs.statSync(themePath).isDirectory() && (fs.existsSync(path9.join(themePath, "theme.config.ts")) || fs.existsSync(path9.join(themePath, "bundle-entry.ts")) || fs.existsSync(path9.join(themePath, "manifest.ts")));
1938
+ const themePath = path11.join(themesDir, name);
1939
+ return fs.statSync(themePath).isDirectory() && (fs.existsSync(path11.join(themePath, "theme.config.ts")) || fs.existsSync(path11.join(themePath, "bundle-entry.ts")) || fs.existsSync(path11.join(themePath, "manifest.ts")));
1703
1940
  });
1704
1941
  }
1705
1942
  function themeExists(themeName) {
1706
- const themePath = path9.join(getThemesDir(), themeName);
1707
- return fs.existsSync(themePath) && (fs.existsSync(path9.join(themePath, "theme.config.ts")) || fs.existsSync(path9.join(themePath, "bundle-entry.ts")) || fs.existsSync(path9.join(themePath, "manifest.ts")));
1943
+ const themePath = path11.join(getThemesDir(), themeName);
1944
+ return fs.existsSync(themePath) && (fs.existsSync(path11.join(themePath, "theme.config.ts")) || fs.existsSync(path11.join(themePath, "bundle-entry.ts")) || fs.existsSync(path11.join(themePath, "manifest.ts")));
1708
1945
  }
1709
1946
  function detectPackageManager() {
1710
1947
  const userAgent = process.env.npm_config_user_agent || "";
@@ -1712,9 +1949,9 @@ function detectPackageManager() {
1712
1949
  if (userAgent.includes("yarn")) return "yarn";
1713
1950
  if (userAgent.includes("bun")) return "bun";
1714
1951
  const cwd = process.cwd();
1715
- if (fs.existsSync(path9.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
1716
- if (fs.existsSync(path9.join(cwd, "yarn.lock"))) return "yarn";
1717
- if (fs.existsSync(path9.join(cwd, "bun.lockb"))) return "bun";
1952
+ if (fs.existsSync(path11.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
1953
+ if (fs.existsSync(path11.join(cwd, "yarn.lock"))) return "yarn";
1954
+ if (fs.existsSync(path11.join(cwd, "bun.lockb"))) return "bun";
1718
1955
  return "npm";
1719
1956
  }
1720
1957
  async function installDependencies(projectPath, packageManager = "npm") {
@@ -1763,22 +2000,45 @@ function getValidCategories() {
1763
2000
  "contact"
1764
2001
  ];
1765
2002
  }
1766
- var AUTH_DIR = path9.join(os.homedir(), ".onexthm");
1767
- var AUTH_FILE = path9.join(AUTH_DIR, "auth.json");
1768
- function getApiUrl() {
1769
- return process.env.ONEXTHM_API_URL || "https://platform-dev.onexeos.com";
2003
+ var AUTH_DIR = path11.join(os.homedir(), ".onexthm");
2004
+ var ENV_URLS = {
2005
+ dev: "https://platform-dev.onexeos.com",
2006
+ staging: "https://platform-staging.onexeos.com",
2007
+ prod: "https://platform-apis.onexeos.com"
2008
+ };
2009
+ function getAuthFile(env = "dev") {
2010
+ const newFile = path11.join(AUTH_DIR, `auth-${env}.json`);
2011
+ if (env === "dev") {
2012
+ const legacyFile = path11.join(AUTH_DIR, "auth.json");
2013
+ if (fs.existsSync(legacyFile) && !fs.existsSync(newFile)) {
2014
+ try {
2015
+ fs.moveSync(legacyFile, newFile);
2016
+ } catch {
2017
+ try {
2018
+ fs.copySync(legacyFile, newFile);
2019
+ fs.removeSync(legacyFile);
2020
+ } catch {
2021
+ }
2022
+ }
2023
+ }
2024
+ }
2025
+ return newFile;
2026
+ }
2027
+ function getApiUrl(env = "dev") {
2028
+ return process.env.ONEXTHM_API_URL || ENV_URLS[env];
1770
2029
  }
1771
- async function saveAuthTokens(tokens) {
2030
+ async function saveAuthTokens(tokens, env = "dev") {
1772
2031
  await fs.ensureDir(AUTH_DIR);
1773
2032
  const key = getMachineKey();
1774
2033
  const data = JSON.stringify(tokens);
1775
2034
  const encrypted = encrypt(data, key);
1776
- await fs.writeFile(AUTH_FILE, encrypted, "utf-8");
2035
+ await fs.writeFile(getAuthFile(env), encrypted, "utf-8");
1777
2036
  }
1778
- function loadAuthTokens() {
2037
+ function loadAuthTokens(env = "dev") {
1779
2038
  try {
1780
- if (!fs.existsSync(AUTH_FILE)) return null;
1781
- const encrypted = fs.readFileSync(AUTH_FILE, "utf-8");
2039
+ const file = getAuthFile(env);
2040
+ if (!fs.existsSync(file)) return null;
2041
+ const encrypted = fs.readFileSync(file, "utf-8");
1782
2042
  const key = getMachineKey();
1783
2043
  const data = decrypt(encrypted, key);
1784
2044
  return JSON.parse(data);
@@ -1786,34 +2046,34 @@ function loadAuthTokens() {
1786
2046
  return null;
1787
2047
  }
1788
2048
  }
1789
- async function clearAuthTokens() {
2049
+ async function clearAuthTokens(env = "dev") {
1790
2050
  try {
1791
- await fs.remove(AUTH_FILE);
2051
+ await fs.remove(getAuthFile(env));
1792
2052
  } catch {
1793
2053
  }
1794
2054
  }
1795
2055
  function isTokenExpired(tokens) {
1796
2056
  return Date.now() / 1e3 > tokens.expiresAt - 300;
1797
2057
  }
1798
- async function getValidTokens() {
1799
- const tokens = loadAuthTokens();
2058
+ async function getValidTokens(env = "dev") {
2059
+ const tokens = loadAuthTokens(env);
1800
2060
  if (!tokens) return null;
1801
2061
  if (!isTokenExpired(tokens)) return tokens;
1802
2062
  try {
1803
- const apiUrl = getApiUrl();
2063
+ const apiUrl = getApiUrl(env);
1804
2064
  const response = await fetch(`${apiUrl}/auth/refresh`, {
1805
2065
  method: "POST",
1806
2066
  headers: { "Content-Type": "application/json" },
1807
2067
  body: JSON.stringify({ refresh_token: tokens.refreshToken })
1808
2068
  });
1809
2069
  if (!response.ok) {
1810
- await clearAuthTokens();
2070
+ await clearAuthTokens(env);
1811
2071
  return null;
1812
2072
  }
1813
2073
  const data = await response.json();
1814
2074
  const body = data.statusCode ? data.body : data;
1815
2075
  if (!body.IdToken) {
1816
- await clearAuthTokens();
2076
+ await clearAuthTokens(env);
1817
2077
  return null;
1818
2078
  }
1819
2079
  const refreshed = {
@@ -1822,17 +2082,19 @@ async function getValidTokens() {
1822
2082
  idToken: body.IdToken,
1823
2083
  expiresAt: Math.floor(Date.now() / 1e3) + (body.ExpiresIn || 3600)
1824
2084
  };
1825
- await saveAuthTokens(refreshed);
2085
+ await saveAuthTokens(refreshed, env);
1826
2086
  return refreshed;
1827
2087
  } catch {
1828
- await clearAuthTokens();
2088
+ await clearAuthTokens(env);
1829
2089
  return null;
1830
2090
  }
1831
2091
  }
1832
- async function authenticatedFetch(url, init) {
1833
- const tokens = await getValidTokens();
2092
+ async function authenticatedFetch(url, init, env = "dev") {
2093
+ const tokens = await getValidTokens(env);
1834
2094
  if (!tokens) {
1835
- throw new Error("Not logged in. Run: onexthm login");
2095
+ throw new Error(
2096
+ `Not logged in to ${env} environment. Run: onexthm login --env ${env}`
2097
+ );
1836
2098
  }
1837
2099
  const headers = new Headers(init?.headers);
1838
2100
  headers.set("Authorization", `Bearer ${tokens.idToken}`);
@@ -1898,7 +2160,7 @@ async function initCommand(projectName, options = {}) {
1898
2160
  if (!validateThemeName(kebabName)) {
1899
2161
  return "Invalid project name. Use lowercase letters, numbers, and hyphens only.";
1900
2162
  }
1901
- if (fs3.existsSync(path9.join(process.cwd(), kebabName))) {
2163
+ if (fs3.existsSync(path11.join(process.cwd(), kebabName))) {
1902
2164
  return `Directory "${kebabName}" already exists`;
1903
2165
  }
1904
2166
  return true;
@@ -1909,14 +2171,14 @@ async function initCommand(projectName, options = {}) {
1909
2171
  } else {
1910
2172
  name = toKebabCase(projectName);
1911
2173
  }
1912
- const projectPath = path9.join(process.cwd(), name);
2174
+ const projectPath = path11.join(process.cwd(), name);
1913
2175
  if (fs3.existsSync(projectPath)) {
1914
2176
  logger.error(`Directory "${name}" already exists.`);
1915
2177
  process.exit(1);
1916
2178
  }
1917
2179
  if (!options.yes) {
1918
2180
  try {
1919
- const apiUrl = getApiUrl();
2181
+ const apiUrl = getApiUrl(options.env ?? "dev");
1920
2182
  const controller = new AbortController();
1921
2183
  const timeout = setTimeout(() => controller.abort(), 3e3);
1922
2184
  const response = await fetch(
@@ -2025,7 +2287,7 @@ async function initCommand(projectName, options = {}) {
2025
2287
  description,
2026
2288
  author
2027
2289
  );
2028
- const mcpJsonPath = path9.join(projectPath, ".mcp.json");
2290
+ const mcpJsonPath = path11.join(projectPath, ".mcp.json");
2029
2291
  if (fs3.existsSync(mcpJsonPath)) {
2030
2292
  let mcpContent = fs3.readFileSync(mcpJsonPath, "utf-8");
2031
2293
  if (figmaApiKey) {
@@ -2105,7 +2367,7 @@ async function initCommand(projectName, options = {}) {
2105
2367
  }
2106
2368
  }
2107
2369
  async function renameThemeInFiles(projectPath, themeName, displayName, description, author) {
2108
- const configPath = path9.join(projectPath, "theme.config.ts");
2370
+ const configPath = path11.join(projectPath, "theme.config.ts");
2109
2371
  if (fs3.existsSync(configPath)) {
2110
2372
  let content = fs3.readFileSync(configPath, "utf-8");
2111
2373
  content = content.replace(
@@ -2118,7 +2380,7 @@ async function renameThemeInFiles(projectPath, themeName, displayName, descripti
2118
2380
  );
2119
2381
  fs3.writeFileSync(configPath, content, "utf-8");
2120
2382
  }
2121
- const pkgPath = path9.join(projectPath, "package.json");
2383
+ const pkgPath = path11.join(projectPath, "package.json");
2122
2384
  if (fs3.existsSync(pkgPath)) {
2123
2385
  let content = fs3.readFileSync(pkgPath, "utf-8");
2124
2386
  content = content.replace(
@@ -2140,10 +2402,10 @@ async function createSectionCommand(name, options) {
2140
2402
  ensureOneXProject();
2141
2403
  if (!options.theme) {
2142
2404
  const isStandaloneTheme = ["theme.config.ts", "bundle-entry.ts"].some(
2143
- (f) => fs.existsSync(path9.join(process.cwd(), f))
2405
+ (f) => fs.existsSync(path11.join(process.cwd(), f))
2144
2406
  );
2145
2407
  if (isStandaloneTheme) {
2146
- options.theme = path9.basename(process.cwd());
2408
+ options.theme = path11.basename(process.cwd());
2147
2409
  }
2148
2410
  }
2149
2411
  const sectionName = toKebabCase(name);
@@ -2206,35 +2468,35 @@ async function createSectionCommand(name, options) {
2206
2468
  };
2207
2469
  logger.startSpinner("Creating section files...");
2208
2470
  try {
2209
- const themePath = path9.join(getThemesDir(), themeName);
2210
- const sectionPath = path9.join(themePath, "sections", sectionName);
2471
+ const themePath = path11.join(getThemesDir(), themeName);
2472
+ const sectionPath = path11.join(themePath, "sections", sectionName);
2211
2473
  const schemaContent = generateSectionSchema(data);
2212
2474
  await writeFile(
2213
- path9.join(sectionPath, `${sectionName}.schema.ts`),
2475
+ path11.join(sectionPath, `${sectionName}.schema.ts`),
2214
2476
  schemaContent
2215
2477
  );
2216
2478
  if (createTemplate) {
2217
2479
  const templateContent = generateSectionTemplate(data);
2218
2480
  await writeFile(
2219
- path9.join(sectionPath, `${sectionName}-default.tsx`),
2481
+ path11.join(sectionPath, `${sectionName}-default.tsx`),
2220
2482
  templateContent
2221
2483
  );
2222
2484
  }
2223
2485
  const indexContent = generateSectionIndex(data, createTemplate);
2224
- await writeFile(path9.join(sectionPath, "index.ts"), indexContent);
2486
+ await writeFile(path11.join(sectionPath, "index.ts"), indexContent);
2225
2487
  logger.stopSpinner(true, "Section files created successfully!");
2226
2488
  logger.newLine();
2227
2489
  logger.section("Next steps:");
2228
2490
  logger.log(
2229
- ` 1. Edit schema: ${path9.relative(process.cwd(), path9.join(sectionPath, `${sectionName}.schema.ts`))}`
2491
+ ` 1. Edit schema: ${path11.relative(process.cwd(), path11.join(sectionPath, `${sectionName}.schema.ts`))}`
2230
2492
  );
2231
2493
  if (createTemplate) {
2232
2494
  logger.log(
2233
- ` 2. Edit template: ${path9.relative(process.cwd(), path9.join(sectionPath, `${sectionName}-default.tsx`))}`
2495
+ ` 2. Edit template: ${path11.relative(process.cwd(), path11.join(sectionPath, `${sectionName}-default.tsx`))}`
2234
2496
  );
2235
2497
  }
2236
2498
  logger.log(
2237
- ` 3. Add to theme manifest: ${path9.relative(process.cwd(), path9.join(themePath, "manifest.ts"))}`
2499
+ ` 3. Add to theme manifest: ${path11.relative(process.cwd(), path11.join(themePath, "manifest.ts"))}`
2238
2500
  );
2239
2501
  logger.newLine();
2240
2502
  logger.success("Section created successfully!");
@@ -2382,10 +2644,10 @@ async function createBlockCommand(name, options) {
2382
2644
  ensureOneXProject();
2383
2645
  if (!options.theme) {
2384
2646
  const isStandaloneTheme = ["theme.config.ts", "bundle-entry.ts"].some(
2385
- (f) => fs.existsSync(path9.join(process.cwd(), f))
2647
+ (f) => fs.existsSync(path11.join(process.cwd(), f))
2386
2648
  );
2387
2649
  if (isStandaloneTheme) {
2388
- options.theme = path9.basename(process.cwd());
2650
+ options.theme = path11.basename(process.cwd());
2389
2651
  }
2390
2652
  }
2391
2653
  const blockName = toKebabCase(name);
@@ -2460,24 +2722,24 @@ async function createBlockCommand(name, options) {
2460
2722
  };
2461
2723
  logger.startSpinner("Creating block files...");
2462
2724
  try {
2463
- const blockPath = scope === "shared" ? path9.join(getFeaturesDir(), "blocks", blockName) : path9.join(getThemesDir(), themeName, "blocks", blockName);
2725
+ const blockPath = scope === "shared" ? path11.join(getFeaturesDir(), "blocks", blockName) : path11.join(getThemesDir(), themeName, "blocks", blockName);
2464
2726
  const schemaContent = generateBlockSchema(data);
2465
2727
  await writeFile(
2466
- path9.join(blockPath, `${blockName}.schema.ts`),
2728
+ path11.join(blockPath, `${blockName}.schema.ts`),
2467
2729
  schemaContent
2468
2730
  );
2469
2731
  const componentContent = generateBlockComponent(data);
2470
- await writeFile(path9.join(blockPath, `${blockName}.tsx`), componentContent);
2732
+ await writeFile(path11.join(blockPath, `${blockName}.tsx`), componentContent);
2471
2733
  const indexContent = generateBlockIndex(data);
2472
- await writeFile(path9.join(blockPath, "index.ts"), indexContent);
2734
+ await writeFile(path11.join(blockPath, "index.ts"), indexContent);
2473
2735
  logger.stopSpinner(true, "Block files created successfully!");
2474
2736
  logger.newLine();
2475
2737
  logger.section("Next steps:");
2476
2738
  logger.log(
2477
- ` 1. Edit schema: ${path9.relative(process.cwd(), path9.join(blockPath, `${blockName}.schema.ts`))}`
2739
+ ` 1. Edit schema: ${path11.relative(process.cwd(), path11.join(blockPath, `${blockName}.schema.ts`))}`
2478
2740
  );
2479
2741
  logger.log(
2480
- ` 2. Edit component: ${path9.relative(process.cwd(), path9.join(blockPath, `${blockName}.tsx`))}`
2742
+ ` 2. Edit component: ${path11.relative(process.cwd(), path11.join(blockPath, `${blockName}.tsx`))}`
2481
2743
  );
2482
2744
  logger.log(
2483
2745
  ` 3. Register in block registry: src/lib/registry/block-registry.ts`
@@ -2655,31 +2917,31 @@ async function createComponentCommand(name, options) {
2655
2917
  };
2656
2918
  logger.startSpinner("Creating component files...");
2657
2919
  try {
2658
- const componentPath = path9.join(
2920
+ const componentPath = path11.join(
2659
2921
  getFeaturesDir(),
2660
2922
  "components",
2661
2923
  componentName
2662
2924
  );
2663
2925
  const schemaContent = generateComponentSchema(data);
2664
2926
  await writeFile(
2665
- path9.join(componentPath, `${componentName}.schema.ts`),
2927
+ path11.join(componentPath, `${componentName}.schema.ts`),
2666
2928
  schemaContent
2667
2929
  );
2668
2930
  const componentContent = generateComponent(data);
2669
2931
  await writeFile(
2670
- path9.join(componentPath, `${componentName}.tsx`),
2932
+ path11.join(componentPath, `${componentName}.tsx`),
2671
2933
  componentContent
2672
2934
  );
2673
2935
  const indexContent = generateComponentIndex(data);
2674
- await writeFile(path9.join(componentPath, "index.ts"), indexContent);
2936
+ await writeFile(path11.join(componentPath, "index.ts"), indexContent);
2675
2937
  logger.stopSpinner(true, "Component files created successfully!");
2676
2938
  logger.newLine();
2677
2939
  logger.section("Next steps:");
2678
2940
  logger.log(
2679
- ` 1. Edit schema: ${path9.relative(process.cwd(), path9.join(componentPath, `${componentName}.schema.ts`))}`
2941
+ ` 1. Edit schema: ${path11.relative(process.cwd(), path11.join(componentPath, `${componentName}.schema.ts`))}`
2680
2942
  );
2681
2943
  logger.log(
2682
- ` 2. Edit component: ${path9.relative(process.cwd(), path9.join(componentPath, `${componentName}.tsx`))}`
2944
+ ` 2. Edit component: ${path11.relative(process.cwd(), path11.join(componentPath, `${componentName}.tsx`))}`
2683
2945
  );
2684
2946
  logger.log(
2685
2947
  ` 3. Register in component registry: src/lib/registry/component-registry.ts`
@@ -2836,13 +3098,13 @@ async function listSections(themeFilter) {
2836
3098
  return;
2837
3099
  }
2838
3100
  for (const theme of themes) {
2839
- const sectionsDir = path9.join(getThemesDir(), theme, "sections");
3101
+ const sectionsDir = path11.join(getThemesDir(), theme, "sections");
2840
3102
  if (!fs.existsSync(sectionsDir)) {
2841
3103
  continue;
2842
3104
  }
2843
3105
  const sections = fs.readdirSync(sectionsDir).filter((name) => {
2844
- const sectionPath = path9.join(sectionsDir, name);
2845
- return fs.statSync(sectionPath).isDirectory() && fs.existsSync(path9.join(sectionPath, "index.ts"));
3106
+ const sectionPath = path11.join(sectionsDir, name);
3107
+ return fs.statSync(sectionPath).isDirectory() && fs.existsSync(path11.join(sectionPath, "index.ts"));
2846
3108
  });
2847
3109
  if (sections.length > 0) {
2848
3110
  logger.log(chalk4.cyan(`
@@ -2856,11 +3118,11 @@ async function listSections(themeFilter) {
2856
3118
  }
2857
3119
  async function listBlocks(themeFilter) {
2858
3120
  logger.section("\u{1F9F1} Blocks");
2859
- const sharedBlocksDir = path9.join(getFeaturesDir(), "blocks");
3121
+ const sharedBlocksDir = path11.join(getFeaturesDir(), "blocks");
2860
3122
  if (fs.existsSync(sharedBlocksDir)) {
2861
3123
  const sharedBlocks = fs.readdirSync(sharedBlocksDir).filter((name) => {
2862
- const blockPath = path9.join(sharedBlocksDir, name);
2863
- return fs.statSync(blockPath).isDirectory() && fs.existsSync(path9.join(blockPath, "index.ts"));
3124
+ const blockPath = path11.join(sharedBlocksDir, name);
3125
+ return fs.statSync(blockPath).isDirectory() && fs.existsSync(path11.join(blockPath, "index.ts"));
2864
3126
  });
2865
3127
  if (sharedBlocks.length > 0) {
2866
3128
  logger.log(chalk4.cyan("\n Shared:"));
@@ -2871,13 +3133,13 @@ async function listBlocks(themeFilter) {
2871
3133
  }
2872
3134
  const themes = themeFilter ? [themeFilter] : listThemes();
2873
3135
  for (const theme of themes) {
2874
- const blocksDir = path9.join(getThemesDir(), theme, "blocks");
3136
+ const blocksDir = path11.join(getThemesDir(), theme, "blocks");
2875
3137
  if (!fs.existsSync(blocksDir)) {
2876
3138
  continue;
2877
3139
  }
2878
3140
  const blocks = fs.readdirSync(blocksDir).filter((name) => {
2879
- const blockPath = path9.join(blocksDir, name);
2880
- return fs.statSync(blockPath).isDirectory() && fs.existsSync(path9.join(blockPath, "index.ts"));
3141
+ const blockPath = path11.join(blocksDir, name);
3142
+ return fs.statSync(blockPath).isDirectory() && fs.existsSync(path11.join(blockPath, "index.ts"));
2881
3143
  });
2882
3144
  if (blocks.length > 0) {
2883
3145
  logger.log(chalk4.cyan(`
@@ -2891,14 +3153,14 @@ async function listBlocks(themeFilter) {
2891
3153
  }
2892
3154
  async function listComponents() {
2893
3155
  logger.section("\u2699\uFE0F Components");
2894
- const componentsDir = path9.join(getFeaturesDir(), "components");
3156
+ const componentsDir = path11.join(getFeaturesDir(), "components");
2895
3157
  if (!fs.existsSync(componentsDir)) {
2896
3158
  logger.warning("No components directory found");
2897
3159
  return;
2898
3160
  }
2899
3161
  const components = fs.readdirSync(componentsDir).filter((name) => {
2900
- const componentPath = path9.join(componentsDir, name);
2901
- return fs.statSync(componentPath).isDirectory() && fs.existsSync(path9.join(componentPath, "index.ts"));
3162
+ const componentPath = path11.join(componentsDir, name);
3163
+ return fs.statSync(componentPath).isDirectory() && fs.existsSync(path11.join(componentPath, "index.ts"));
2902
3164
  });
2903
3165
  if (components.length === 0) {
2904
3166
  logger.warning("No components found");
@@ -2919,11 +3181,11 @@ async function listThemesInfo() {
2919
3181
  }
2920
3182
  logger.log("");
2921
3183
  for (const theme of themes) {
2922
- const themeDir = path9.join(getThemesDir(), theme);
3184
+ const themeDir = path11.join(getThemesDir(), theme);
2923
3185
  const candidates = ["theme.config.ts", "bundle-entry.ts", "manifest.ts"];
2924
3186
  let manifestContent = "";
2925
3187
  for (const candidate of candidates) {
2926
- const candidatePath = path9.join(themeDir, candidate);
3188
+ const candidatePath = path11.join(themeDir, candidate);
2927
3189
  if (fs.existsSync(candidatePath)) {
2928
3190
  manifestContent = fs.readFileSync(candidatePath, "utf-8");
2929
3191
  break;
@@ -2961,9 +3223,9 @@ async function validateCommand(options) {
2961
3223
  "theme.config.ts",
2962
3224
  "bundle-entry.ts",
2963
3225
  "manifest.ts"
2964
- ].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
3226
+ ].some((f) => fs.existsSync(path11.join(process.cwd(), f)));
2965
3227
  if (isThemeDir2) {
2966
- themeToValidate = path9.basename(process.cwd());
3228
+ themeToValidate = path11.basename(process.cwd());
2967
3229
  logger.info(`Validating current theme: ${themeToValidate}`);
2968
3230
  } else {
2969
3231
  logger.error(
@@ -2972,11 +3234,11 @@ async function validateCommand(options) {
2972
3234
  process.exit(1);
2973
3235
  }
2974
3236
  }
2975
- const themePath = path9.join(getThemesDir(), themeToValidate);
3237
+ const themePath = path11.join(getThemesDir(), themeToValidate);
2976
3238
  logger.startSpinner("Running validation checks...");
2977
3239
  const entryFiles = ["manifest.ts", "theme.config.ts", "bundle-entry.ts"];
2978
3240
  const foundEntry = entryFiles.find(
2979
- (f) => fs.existsSync(path9.join(themePath, f))
3241
+ (f) => fs.existsSync(path11.join(themePath, f))
2980
3242
  );
2981
3243
  if (!foundEntry) {
2982
3244
  issues.push({
@@ -2986,7 +3248,7 @@ async function validateCommand(options) {
2986
3248
  });
2987
3249
  } else if (foundEntry === "manifest.ts") {
2988
3250
  const manifestContent = fs.readFileSync(
2989
- path9.join(themePath, foundEntry),
3251
+ path11.join(themePath, foundEntry),
2990
3252
  "utf-8"
2991
3253
  );
2992
3254
  if (!manifestContent.includes("export const") && !manifestContent.includes("export default") && !manifestContent.includes("export interface")) {
@@ -2997,7 +3259,7 @@ async function validateCommand(options) {
2997
3259
  });
2998
3260
  }
2999
3261
  }
3000
- const configPath = path9.join(themePath, "theme.config.ts");
3262
+ const configPath = path11.join(themePath, "theme.config.ts");
3001
3263
  if (!fs.existsSync(configPath)) {
3002
3264
  issues.push({
3003
3265
  type: "warning",
@@ -3005,7 +3267,7 @@ async function validateCommand(options) {
3005
3267
  message: "Theme config file not found (recommended)"
3006
3268
  });
3007
3269
  }
3008
- const indexPath = path9.join(themePath, "index.ts");
3270
+ const indexPath = path11.join(themePath, "index.ts");
3009
3271
  if (!fs.existsSync(indexPath)) {
3010
3272
  issues.push({
3011
3273
  type: "warning",
@@ -3013,7 +3275,7 @@ async function validateCommand(options) {
3013
3275
  message: "Index file not found (recommended)"
3014
3276
  });
3015
3277
  }
3016
- const sectionsDir = path9.join(themePath, "sections");
3278
+ const sectionsDir = path11.join(themePath, "sections");
3017
3279
  if (!fs.existsSync(sectionsDir)) {
3018
3280
  issues.push({
3019
3281
  type: "warning",
@@ -3022,16 +3284,16 @@ async function validateCommand(options) {
3022
3284
  });
3023
3285
  } else {
3024
3286
  const sections = fs.readdirSync(sectionsDir).filter(
3025
- (name) => fs.statSync(path9.join(sectionsDir, name)).isDirectory()
3287
+ (name) => fs.statSync(path11.join(sectionsDir, name)).isDirectory()
3026
3288
  );
3027
3289
  for (const sectionName of sections) {
3028
- const sectionPath = path9.join(sectionsDir, sectionName);
3029
- const schemaFile = path9.join(sectionPath, `${sectionName}.schema.ts`);
3030
- const defaultTemplate = path9.join(
3290
+ const sectionPath = path11.join(sectionsDir, sectionName);
3291
+ const schemaFile = path11.join(sectionPath, `${sectionName}.schema.ts`);
3292
+ const defaultTemplate = path11.join(
3031
3293
  sectionPath,
3032
3294
  `${sectionName}-default.tsx`
3033
3295
  );
3034
- const indexFile = path9.join(sectionPath, "index.ts");
3296
+ const indexFile = path11.join(sectionPath, "index.ts");
3035
3297
  if (!fs.existsSync(schemaFile)) {
3036
3298
  issues.push({
3037
3299
  type: "error",
@@ -3055,14 +3317,14 @@ async function validateCommand(options) {
3055
3317
  }
3056
3318
  }
3057
3319
  }
3058
- const blocksDir = path9.join(themePath, "blocks");
3320
+ const blocksDir = path11.join(themePath, "blocks");
3059
3321
  if (fs.existsSync(blocksDir)) {
3060
- const blocks = fs.readdirSync(blocksDir).filter((name) => fs.statSync(path9.join(blocksDir, name)).isDirectory());
3322
+ const blocks = fs.readdirSync(blocksDir).filter((name) => fs.statSync(path11.join(blocksDir, name)).isDirectory());
3061
3323
  for (const blockName of blocks) {
3062
- const blockPath = path9.join(blocksDir, blockName);
3063
- const schemaFile = path9.join(blockPath, `${blockName}.schema.ts`);
3064
- const componentFile = path9.join(blockPath, `${blockName}.tsx`);
3065
- const indexFile = path9.join(blockPath, "index.ts");
3324
+ const blockPath = path11.join(blocksDir, blockName);
3325
+ const schemaFile = path11.join(blockPath, `${blockName}.schema.ts`);
3326
+ const componentFile = path11.join(blockPath, `${blockName}.tsx`);
3327
+ const indexFile = path11.join(blockPath, "index.ts");
3066
3328
  if (!fs.existsSync(schemaFile)) {
3067
3329
  issues.push({
3068
3330
  type: "error",
@@ -3088,13 +3350,13 @@ async function validateCommand(options) {
3088
3350
  }
3089
3351
  if (fs.existsSync(sectionsDir)) {
3090
3352
  const sections = fs.readdirSync(sectionsDir).filter(
3091
- (name) => fs.statSync(path9.join(sectionsDir, name)).isDirectory()
3353
+ (name) => fs.statSync(path11.join(sectionsDir, name)).isDirectory()
3092
3354
  );
3093
3355
  for (const sectionName of sections) {
3094
- const sectionPath = path9.join(sectionsDir, sectionName);
3356
+ const sectionPath = path11.join(sectionsDir, sectionName);
3095
3357
  const tsxFiles = fs.readdirSync(sectionPath).filter((f) => f.endsWith(".tsx") && !f.endsWith(".schema.ts"));
3096
3358
  for (const tsxFile of tsxFiles) {
3097
- const filePath = path9.join(sectionPath, tsxFile);
3359
+ const filePath = path11.join(sectionPath, tsxFile);
3098
3360
  const content = fs.readFileSync(filePath, "utf-8");
3099
3361
  const relPath = `sections/${sectionName}/${tsxFile}`;
3100
3362
  if (!content.includes('"use client"') && !content.includes("'use client'")) {
@@ -3142,12 +3404,12 @@ async function validateCommand(options) {
3142
3404
  }
3143
3405
  }
3144
3406
  }
3145
- const registryPath = path9.join(themePath, "sections-registry.ts");
3146
- const bundleEntryPath = path9.join(themePath, "bundle-entry.ts");
3407
+ const registryPath = path11.join(themePath, "sections-registry.ts");
3408
+ const bundleEntryPath = path11.join(themePath, "bundle-entry.ts");
3147
3409
  const registryContent = fs.existsSync(registryPath) ? fs.readFileSync(registryPath, "utf-8") : fs.existsSync(bundleEntryPath) ? fs.readFileSync(bundleEntryPath, "utf-8") : "";
3148
3410
  if (fs.existsSync(sectionsDir) && registryContent) {
3149
3411
  const sections = fs.readdirSync(sectionsDir).filter(
3150
- (name) => fs.statSync(path9.join(sectionsDir, name)).isDirectory()
3412
+ (name) => fs.statSync(path11.join(sectionsDir, name)).isDirectory()
3151
3413
  );
3152
3414
  for (const sectionName of sections) {
3153
3415
  if (!registryContent.includes(`sections/${sectionName}`) && !registryContent.includes(`"${sectionName}"`)) {
@@ -3170,7 +3432,7 @@ async function validateCommand(options) {
3170
3432
  });
3171
3433
  }
3172
3434
  }
3173
- const pagesDir = path9.join(themePath, "pages");
3435
+ const pagesDir = path11.join(themePath, "pages");
3174
3436
  if (fs.existsSync(pagesDir)) {
3175
3437
  const allSchemaTypeSet = new Set(
3176
3438
  schemaTypes.map((s) => s.schemaType || s.folderName)
@@ -3222,9 +3484,9 @@ async function validateCommand(options) {
3222
3484
  }
3223
3485
  async function loadSchemaTypes(themePath, sectionsDir) {
3224
3486
  const results = [];
3225
- const sections = fs.readdirSync(sectionsDir).filter((name) => fs.statSync(path9.join(sectionsDir, name)).isDirectory());
3487
+ const sections = fs.readdirSync(sectionsDir).filter((name) => fs.statSync(path11.join(sectionsDir, name)).isDirectory());
3226
3488
  for (const sectionName of sections) {
3227
- const schemaFile = path9.join(
3489
+ const schemaFile = path11.join(
3228
3490
  sectionsDir,
3229
3491
  sectionName,
3230
3492
  `${sectionName}.schema.ts`
@@ -3262,7 +3524,7 @@ async function validatePageSectionTypes(pagesDir, validTypes) {
3262
3524
  const issues = [];
3263
3525
  const files = fs.readdirSync(pagesDir).filter((f) => f.match(/\.(ts|js)$/));
3264
3526
  for (const file of files) {
3265
- const content = fs.readFileSync(path9.join(pagesDir, file), "utf-8");
3527
+ const content = fs.readFileSync(path11.join(pagesDir, file), "utf-8");
3266
3528
  const pageName = file.replace(/\.(ts|js)$/, "");
3267
3529
  const sectionsMatch = content.match(/\bsections:\s*\[/);
3268
3530
  if (!sectionsMatch || sectionsMatch.index === void 0) continue;
@@ -3275,9 +3537,13 @@ async function validatePageSectionTypes(pagesDir, validTypes) {
3275
3537
  endIdx = i;
3276
3538
  }
3277
3539
  const sectionsBlock = content.slice(startIdx, endIdx);
3278
- const typeMatches = sectionsBlock.matchAll(/\btype:\s*["']([^"']+)["']/g);
3279
- for (const match of typeMatches) {
3540
+ const sectionTypeMatches = sectionsBlock.matchAll(
3541
+ /\bid:\s*["'][^"']*["'],\s*\n?\s*type:\s*["']([^"']+)["']/g
3542
+ );
3543
+ for (const match of sectionTypeMatches) {
3280
3544
  const sectionType = match[1];
3545
+ if (COMPONENT_TYPES.has(sectionType)) continue;
3546
+ if (BLOCK_TYPES.has(sectionType)) continue;
3281
3547
  if (!validTypes.has(sectionType)) {
3282
3548
  issues.push({
3283
3549
  type: "error",
@@ -3330,6 +3596,64 @@ var FIELD_TYPES = /* @__PURE__ */ new Set([
3330
3596
  "inline_richtext",
3331
3597
  "repeater"
3332
3598
  ]);
3599
+ var COMPONENT_TYPES = /* @__PURE__ */ new Set([
3600
+ "heading",
3601
+ "paragraph",
3602
+ "button",
3603
+ "image",
3604
+ "link",
3605
+ "icon",
3606
+ "badge",
3607
+ "divider",
3608
+ "spacer",
3609
+ "container",
3610
+ "grid",
3611
+ "columns",
3612
+ "card",
3613
+ "quote",
3614
+ "input",
3615
+ "textarea",
3616
+ "checkbox",
3617
+ "select",
3618
+ "video",
3619
+ "gallery",
3620
+ "alert",
3621
+ "progress",
3622
+ "rating",
3623
+ "timer",
3624
+ "list",
3625
+ "table",
3626
+ "accordion",
3627
+ "tabs",
3628
+ "code",
3629
+ "map",
3630
+ "product-card",
3631
+ "blog-card",
3632
+ "social-links",
3633
+ "hotline-contacts",
3634
+ "company-info",
3635
+ "torn-separator"
3636
+ ]);
3637
+ var BLOCK_TYPES = /* @__PURE__ */ new Set([
3638
+ "brand-feature",
3639
+ "collection-item",
3640
+ "crafting-step",
3641
+ "testimonial-item",
3642
+ "stat-item",
3643
+ "footer-link",
3644
+ "navigation-links-block",
3645
+ "policy-section",
3646
+ "core-value-card",
3647
+ "faq-item",
3648
+ "feature-item",
3649
+ "gallery-item",
3650
+ "logo-item",
3651
+ "pricing-tier",
3652
+ "service-item",
3653
+ "stat-card",
3654
+ "team-member",
3655
+ "hero-content"
3656
+ ]);
3333
3657
 
3334
3658
  // src/commands/build.ts
3335
3659
  init_logger();
@@ -3340,14 +3664,14 @@ async function buildCommand(options) {
3340
3664
  if (options.theme) {
3341
3665
  themeName = options.theme;
3342
3666
  try {
3343
- const workspaceThemePath = path9.join(getThemesDir(), themeName);
3667
+ const workspaceThemePath = path11.join(getThemesDir(), themeName);
3344
3668
  if (fs.existsSync(workspaceThemePath)) {
3345
3669
  themePath = workspaceThemePath;
3346
3670
  } else {
3347
- themePath = path9.join(process.cwd(), themeName);
3671
+ themePath = path11.join(process.cwd(), themeName);
3348
3672
  }
3349
3673
  } catch {
3350
- themePath = path9.join(process.cwd(), themeName);
3674
+ themePath = path11.join(process.cwd(), themeName);
3351
3675
  }
3352
3676
  if (!fs.existsSync(themePath)) {
3353
3677
  logger.error(`Theme "${themeName}" not found.`);
@@ -3358,10 +3682,10 @@ async function buildCommand(options) {
3358
3682
  "theme.config.ts",
3359
3683
  "bundle-entry.ts",
3360
3684
  "manifest.ts"
3361
- ].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
3685
+ ].some((f) => fs.existsSync(path11.join(process.cwd(), f)));
3362
3686
  if (isThemeDir2) {
3363
3687
  themePath = process.cwd();
3364
- themeName = path9.basename(themePath);
3688
+ themeName = path11.basename(themePath);
3365
3689
  logger.info(`Building current theme: ${themeName}`);
3366
3690
  } else {
3367
3691
  logger.error(
@@ -3370,7 +3694,7 @@ async function buildCommand(options) {
3370
3694
  process.exit(1);
3371
3695
  }
3372
3696
  }
3373
- const packageJsonPath = path9.join(themePath, "package.json");
3697
+ const packageJsonPath = path11.join(themePath, "package.json");
3374
3698
  const hasPkgJson = fs.existsSync(packageJsonPath);
3375
3699
  if (!hasPkgJson) {
3376
3700
  logger.warning(
@@ -3426,9 +3750,9 @@ async function buildCommand(options) {
3426
3750
  logger.success("\u2713 Theme built successfully!");
3427
3751
  logger.newLine();
3428
3752
  logger.info(`Theme: ${themeName}`);
3429
- const distPath = path9.join(themePath, "dist");
3753
+ const distPath = path11.join(themePath, "dist");
3430
3754
  if (fs.existsSync(distPath)) {
3431
- logger.log(`Output: ${path9.relative(process.cwd(), distPath)}`);
3755
+ logger.log(`Output: ${path11.relative(process.cwd(), distPath)}`);
3432
3756
  const files = fs.readdirSync(distPath);
3433
3757
  logger.log(`Files: ${files.length}`);
3434
3758
  }
@@ -3484,7 +3808,7 @@ async function packageCommand(options) {
3484
3808
  let themeName;
3485
3809
  if (options.theme) {
3486
3810
  themeName = options.theme;
3487
- themePath = path9.join(getThemesDir(), themeName);
3811
+ themePath = path11.join(getThemesDir(), themeName);
3488
3812
  if (!fs.existsSync(themePath)) {
3489
3813
  logger.error(`Theme "${themeName}" not found.`);
3490
3814
  process.exit(1);
@@ -3494,10 +3818,10 @@ async function packageCommand(options) {
3494
3818
  "theme.config.ts",
3495
3819
  "bundle-entry.ts",
3496
3820
  "manifest.ts"
3497
- ].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
3821
+ ].some((f) => fs.existsSync(path11.join(process.cwd(), f)));
3498
3822
  if (isThemeDir2) {
3499
3823
  themePath = process.cwd();
3500
- themeName = path9.basename(themePath);
3824
+ themeName = path11.basename(themePath);
3501
3825
  logger.info(`Packaging current theme: ${themeName}`);
3502
3826
  } else {
3503
3827
  logger.error(
@@ -3506,7 +3830,7 @@ async function packageCommand(options) {
3506
3830
  process.exit(1);
3507
3831
  }
3508
3832
  }
3509
- const packageJsonPath = path9.join(themePath, "package.json");
3833
+ const packageJsonPath = path11.join(themePath, "package.json");
3510
3834
  let version2 = "1.0.0";
3511
3835
  if (fs.existsSync(packageJsonPath)) {
3512
3836
  const packageJson = await fs.readJson(packageJsonPath);
@@ -3516,7 +3840,7 @@ async function packageCommand(options) {
3516
3840
  logger.info(`Theme: ${themeName}`);
3517
3841
  logger.info(`Version: ${version2}`);
3518
3842
  logger.newLine();
3519
- const compiledThemePath = path9.join(
3843
+ const compiledThemePath = path11.join(
3520
3844
  process.cwd(),
3521
3845
  "themes",
3522
3846
  themeName,
@@ -3550,8 +3874,8 @@ async function packageCommand(options) {
3550
3874
  logger.newLine();
3551
3875
  logger.section("Step 2: Create Package");
3552
3876
  const packageName = options.name || `${themeName}-${version2}`;
3553
- const outputDir = options.output || path9.join(process.cwd(), "dist");
3554
- const outputPath = path9.join(outputDir, `${packageName}.zip`);
3877
+ const outputDir = options.output || path11.join(process.cwd(), "dist");
3878
+ const outputPath = path11.join(outputDir, `${packageName}.zip`);
3555
3879
  await fs.ensureDir(outputDir);
3556
3880
  logger.startSpinner("Creating zip archive...");
3557
3881
  try {
@@ -3564,11 +3888,11 @@ async function packageCommand(options) {
3564
3888
  logger.newLine();
3565
3889
  logger.info(`Package: ${packageName}.zip`);
3566
3890
  logger.log(`Size: ${sizeMB} MB`);
3567
- logger.log(`Location: ${path9.relative(process.cwd(), outputPath)}`);
3891
+ logger.log(`Location: ${path11.relative(process.cwd(), outputPath)}`);
3568
3892
  logger.newLine();
3569
3893
  logger.section("Next steps:");
3570
3894
  logger.log(
3571
- ` onexthm deploy --package ${path9.relative(process.cwd(), outputPath)}`
3895
+ ` onexthm deploy --package ${path11.relative(process.cwd(), outputPath)}`
3572
3896
  );
3573
3897
  } catch (error) {
3574
3898
  logger.stopSpinner(false, "Failed to create package");
@@ -3626,9 +3950,9 @@ async function deployCommand(options) {
3626
3950
  ensureOneXProject();
3627
3951
  let packagePath;
3628
3952
  if (options.package) {
3629
- packagePath = path9.resolve(options.package);
3953
+ packagePath = path11.resolve(options.package);
3630
3954
  } else if (options.theme) {
3631
- const distDir = path9.join(process.cwd(), "dist");
3955
+ const distDir = path11.join(process.cwd(), "dist");
3632
3956
  if (!fs.existsSync(distDir)) {
3633
3957
  logger.error("No dist/ directory found. Run 'onexthm package' first.");
3634
3958
  process.exit(1);
@@ -3643,7 +3967,7 @@ async function deployCommand(options) {
3643
3967
  process.exit(1);
3644
3968
  }
3645
3969
  packageFiles.sort().reverse();
3646
- packagePath = path9.join(distDir, packageFiles[0]);
3970
+ packagePath = path11.join(distDir, packageFiles[0]);
3647
3971
  } else {
3648
3972
  logger.error("Either --package or --theme must be specified.");
3649
3973
  logger.info("Examples:");
@@ -3657,11 +3981,11 @@ async function deployCommand(options) {
3657
3981
  }
3658
3982
  const stats = await fs.stat(packagePath);
3659
3983
  const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
3660
- const fileName = path9.basename(packagePath);
3984
+ const fileName = path11.basename(packagePath);
3661
3985
  logger.newLine();
3662
3986
  logger.info(`Package: ${fileName}`);
3663
3987
  logger.log(`Size: ${sizeMB} MB`);
3664
- logger.log(`Path: ${path9.relative(process.cwd(), packagePath)}`);
3988
+ logger.log(`Path: ${path11.relative(process.cwd(), packagePath)}`);
3665
3989
  logger.newLine();
3666
3990
  const apiUrl = options.apiUrl || process.env.ONEX_API_URL || "http://localhost:3001";
3667
3991
  const uploadEndpoint = `${apiUrl}/website-api/themes/upload`;
@@ -3818,8 +4142,8 @@ async function downloadBundleZip(apiUrl, themeId, version2) {
3818
4142
  async function createCompatibilityFiles(outputDir, manifest) {
3819
4143
  const entryFile = manifest.output?.entry || "bundle-entry.js";
3820
4144
  if (entryFile !== "bundle-entry.js" && entryFile.startsWith("bundle-entry-")) {
3821
- const hashedPath = path9.join(outputDir, entryFile);
3822
- const stablePath = path9.join(outputDir, "bundle-entry.js");
4145
+ const hashedPath = path11.join(outputDir, entryFile);
4146
+ const stablePath = path11.join(outputDir, "bundle-entry.js");
3823
4147
  if (await fs.pathExists(hashedPath)) {
3824
4148
  await fs.copy(hashedPath, stablePath);
3825
4149
  const mapPath = hashedPath + ".map";
@@ -3828,13 +4152,13 @@ async function createCompatibilityFiles(outputDir, manifest) {
3828
4152
  }
3829
4153
  }
3830
4154
  }
3831
- const sectionsRegistryPath = path9.join(outputDir, "sections-registry.js");
4155
+ const sectionsRegistryPath = path11.join(outputDir, "sections-registry.js");
3832
4156
  const content = `// Re-export all sections from bundle-entry
3833
4157
  // This file exists to maintain compatibility with the import path
3834
4158
  export * from './bundle-entry.js';
3835
4159
  `;
3836
4160
  await fs.writeFile(sectionsRegistryPath, content, "utf-8");
3837
- const pkgJsonPath = path9.join(outputDir, "package.json");
4161
+ const pkgJsonPath = path11.join(outputDir, "package.json");
3838
4162
  await fs.writeFile(pkgJsonPath, '{\n "type": "module"\n}\n', "utf-8");
3839
4163
  }
3840
4164
  function showDownloadFailureHelp(themeId, apiUrl) {
@@ -3865,9 +4189,7 @@ function showDownloadFailureHelp(themeId, apiUrl) {
3865
4189
  console.log();
3866
4190
  console.log(chalk4.white("2. Check API URL configuration:"));
3867
4191
  console.log(chalk4.gray(` Current API URL: ${apiUrl}`));
3868
- console.log(
3869
- chalk4.gray(" Override with NEXT_PUBLIC_API_URL or ONEXTHM_API_URL")
3870
- );
4192
+ console.log(chalk4.gray(" Override with ONEXTHM_API_URL env var if needed"));
3871
4193
  console.log();
3872
4194
  console.log(chalk4.white("3. Pin a specific version (CI/production):"));
3873
4195
  console.log(
@@ -3877,15 +4199,17 @@ function showDownloadFailureHelp(themeId, apiUrl) {
3877
4199
  }
3878
4200
  async function downloadCommand(options) {
3879
4201
  logger.header("Download Theme");
4202
+ const env = options.env ?? "dev";
4203
+ const apiUrl = getApiUrl(env);
4204
+ logger.info(`Environment: ${env} (${apiUrl})`);
3880
4205
  const spinner = ora("Initializing download...").start();
3881
- if (options.bucket || options.environment) {
4206
+ if (options.bucket) {
3882
4207
  spinner.stop();
3883
4208
  logger.warning(
3884
- "--bucket and --environment are deprecated and ignored. Themes are now served via HTTP from the website-api Lambda."
4209
+ "--bucket is deprecated and ignored. Themes are now served via HTTP from the website-api Lambda."
3885
4210
  );
3886
4211
  spinner.start();
3887
4212
  }
3888
- const apiUrl = getApiUrl();
3889
4213
  try {
3890
4214
  const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || process.env.THEME_ID;
3891
4215
  const requestedVersion = options.version || process.env.THEME_VERSION || "latest";
@@ -3933,7 +4257,7 @@ async function downloadCommand(options) {
3933
4257
  zip.extractAllTo(outputDir, true);
3934
4258
  const entries = zip.getEntries().filter((e) => !e.isDirectory);
3935
4259
  spinner.succeed(`Extracted ${entries.length} files to ${outputDir}`);
3936
- const manifestPath = path9.join(outputDir, "manifest.json");
4260
+ const manifestPath = path11.join(outputDir, "manifest.json");
3937
4261
  const manifest = await fs.readJson(manifestPath);
3938
4262
  await createCompatibilityFiles(outputDir, manifest);
3939
4263
  console.log();
@@ -3942,6 +4266,7 @@ async function downloadCommand(options) {
3942
4266
  console.log(
3943
4267
  chalk4.cyan(" Theme: ") + chalk4.white(`${themeId}@${resolvedVersion}`)
3944
4268
  );
4269
+ console.log(chalk4.cyan(" Env: ") + chalk4.white(env));
3945
4270
  console.log(chalk4.cyan(" Source: ") + chalk4.white(apiUrl));
3946
4271
  console.log(chalk4.cyan(" Output: ") + chalk4.white(outputDir));
3947
4272
  console.log(chalk4.cyan(" Files: ") + chalk4.white(entries.length));
@@ -3984,11 +4309,9 @@ async function resolveLatestVersion2(apiUrl, themeId) {
3984
4309
  }
3985
4310
  return latest;
3986
4311
  }
3987
- async function fetchSourceZip(apiUrl, themeId, version2) {
4312
+ async function fetchSourceZip(apiUrl, themeId, version2, env) {
3988
4313
  const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/source?version=${encodeURIComponent(version2)}`;
3989
- const response = await authenticatedFetch(url, {
3990
- method: "GET"
3991
- });
4314
+ const response = await authenticatedFetch(url, { method: "GET" }, env);
3992
4315
  if (!response.ok) {
3993
4316
  if (response.status === 404) {
3994
4317
  throw new Error(
@@ -3997,7 +4320,7 @@ async function fetchSourceZip(apiUrl, themeId, version2) {
3997
4320
  }
3998
4321
  if (response.status === 401 || response.status === 403) {
3999
4322
  throw new Error(
4000
- `Not authorized to download source for "${themeId}". Run \`onexthm login\` first.`
4323
+ `Not authorized to download source for "${themeId}". Run \`onexthm login --env ${env}\` first.`
4001
4324
  );
4002
4325
  }
4003
4326
  throw new Error(
@@ -4055,7 +4378,7 @@ async function renameTheme(themeDir, oldName, newName) {
4055
4378
  const oldPrefix = `${oldName}-`;
4056
4379
  const newPrefix = `${newName}-`;
4057
4380
  const newDisplayName = newName.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
4058
- const pkgPath = path9.join(themeDir, "package.json");
4381
+ const pkgPath = path11.join(themeDir, "package.json");
4059
4382
  if (await fs.pathExists(pkgPath)) {
4060
4383
  const pkg = await fs.readJson(pkgPath);
4061
4384
  pkg.name = `@onex-themes/${newName}`;
@@ -4071,7 +4394,7 @@ async function renameTheme(themeDir, oldName, newName) {
4071
4394
  }
4072
4395
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
4073
4396
  }
4074
- const configPath = path9.join(themeDir, "theme.config.ts");
4397
+ const configPath = path11.join(themeDir, "theme.config.ts");
4075
4398
  if (await fs.pathExists(configPath)) {
4076
4399
  let content = await fs.readFile(configPath, "utf-8");
4077
4400
  content = content.replace(/id:\s*"[^"]*"/, `id: "${newName}"`);
@@ -4081,7 +4404,7 @@ async function renameTheme(themeDir, oldName, newName) {
4081
4404
  );
4082
4405
  await fs.writeFile(configPath, content);
4083
4406
  }
4084
- const layoutPath = path9.join(themeDir, "theme.layout.ts");
4407
+ const layoutPath = path11.join(themeDir, "theme.layout.ts");
4085
4408
  if (await fs.pathExists(layoutPath)) {
4086
4409
  let content = await fs.readFile(layoutPath, "utf-8");
4087
4410
  content = content.replace(/id:\s*"[^"]*"/, `id: "${newName}"`);
@@ -4094,7 +4417,7 @@ async function renameTheme(themeDir, oldName, newName) {
4094
4417
  const oldDisplayName = oldName.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
4095
4418
  const tsFiles = await glob("**/*.ts", { cwd: themeDir, nodir: true });
4096
4419
  for (const file of tsFiles) {
4097
- const filePath = path9.join(themeDir, file);
4420
+ const filePath = path11.join(themeDir, file);
4098
4421
  let content = await fs.readFile(filePath, "utf-8");
4099
4422
  const original = content;
4100
4423
  content = content.replace(
@@ -4116,14 +4439,19 @@ async function renameTheme(themeDir, oldName, newName) {
4116
4439
  }
4117
4440
  async function cloneCommand(themeName, options) {
4118
4441
  logger.header("Clone Theme Source");
4119
- if (options.bucket || options.environment) {
4442
+ const env = options.env ?? "dev";
4443
+ const apiUrl = getApiUrl(env);
4444
+ logger.info(`Environment: ${env} (${apiUrl})`);
4445
+ if (options.bucket) {
4120
4446
  logger.warning(
4121
- "--bucket and --environment are deprecated and ignored. Source is now fetched via HTTP from the website-api Lambda."
4447
+ "--bucket is deprecated and ignored. Source is now fetched via HTTP from the website-api Lambda."
4122
4448
  );
4123
4449
  }
4124
- const tokens = await getValidTokens();
4450
+ const tokens = await getValidTokens(env);
4125
4451
  if (!tokens) {
4126
- logger.error("Not logged in. Run: onexthm login");
4452
+ logger.error(
4453
+ `Not logged in to ${env} environment. Run: onexthm login --env ${env}`
4454
+ );
4127
4455
  process.exit(1);
4128
4456
  }
4129
4457
  let newName = options.name;
@@ -4132,8 +4460,7 @@ async function cloneCommand(themeName, options) {
4132
4460
  }
4133
4461
  const spinner = ora("Initializing clone...").start();
4134
4462
  try {
4135
- const apiUrl = getApiUrl();
4136
- const outputDir = options.output || path9.resolve(process.cwd(), newName);
4463
+ const outputDir = options.output || path11.resolve(process.cwd(), newName);
4137
4464
  if (await fs.pathExists(outputDir)) {
4138
4465
  spinner.fail(chalk4.red(`Directory already exists: ${outputDir}`));
4139
4466
  logger.info(
@@ -4152,7 +4479,7 @@ async function cloneCommand(themeName, options) {
4152
4479
  spinner.start(`Downloading source.zip for ${themeName}@${version2}...`);
4153
4480
  let zipBuffer;
4154
4481
  try {
4155
- zipBuffer = await fetchSourceZip(apiUrl, themeName, version2);
4482
+ zipBuffer = await fetchSourceZip(apiUrl, themeName, version2, env);
4156
4483
  } catch (error) {
4157
4484
  spinner.fail(chalk4.red(error.message));
4158
4485
  console.log();
@@ -4179,20 +4506,20 @@ async function cloneCommand(themeName, options) {
4179
4506
  spinner.succeed(
4180
4507
  `Renamed theme: ${chalk4.gray(themeName)} \u2192 ${chalk4.cyan(newName)}`
4181
4508
  );
4182
- const envExamplePath = path9.join(outputDir, ".env.example");
4509
+ const envExamplePath = path11.join(outputDir, ".env.example");
4183
4510
  if (!await fs.pathExists(envExamplePath)) {
4184
4511
  await fs.writeFile(
4185
4512
  envExamplePath,
4186
4513
  [
4187
4514
  "# API Configuration (enables real data in preview)",
4188
4515
  "# Get your Company ID from the OneX dashboard",
4189
- "NEXT_PUBLIC_API_URL=https://platform-dev.onexeos.com",
4516
+ `NEXT_PUBLIC_API_URL=${apiUrl}`,
4190
4517
  "NEXT_PUBLIC_COMPANY_ID=",
4191
4518
  ""
4192
4519
  ].join("\n")
4193
4520
  );
4194
4521
  }
4195
- const mcpJsonPath = path9.join(outputDir, ".mcp.json");
4522
+ const mcpJsonPath = path11.join(outputDir, ".mcp.json");
4196
4523
  if (await fs.pathExists(mcpJsonPath)) {
4197
4524
  const { default: inquirerMod } = await import('inquirer');
4198
4525
  const { figmaApiKey } = await inquirerMod.prompt([
@@ -4217,7 +4544,7 @@ async function cloneCommand(themeName, options) {
4217
4544
  }
4218
4545
  if (options.install !== false) {
4219
4546
  const hasPkgJson = await fs.pathExists(
4220
- path9.join(outputDir, "package.json")
4547
+ path11.join(outputDir, "package.json")
4221
4548
  );
4222
4549
  if (hasPkgJson) {
4223
4550
  spinner.start("Installing dependencies...");
@@ -4239,12 +4566,13 @@ async function cloneCommand(themeName, options) {
4239
4566
  console.log(
4240
4567
  chalk4.cyan(" Source: ") + chalk4.gray(`${themeName}@${version2}`)
4241
4568
  );
4569
+ console.log(chalk4.cyan(" Env: ") + chalk4.white(env));
4242
4570
  console.log(chalk4.cyan(" Theme: ") + chalk4.white(newName));
4243
4571
  console.log(chalk4.cyan(" Location: ") + chalk4.white(outputDir));
4244
4572
  console.log(chalk4.cyan(" Files: ") + chalk4.white(entries.length));
4245
4573
  console.log();
4246
4574
  console.log(chalk4.cyan("Next steps:"));
4247
- console.log(chalk4.gray(` cd ${path9.relative(process.cwd(), outputDir)}`));
4575
+ console.log(chalk4.gray(` cd ${path11.relative(process.cwd(), outputDir)}`));
4248
4576
  console.log(
4249
4577
  chalk4.gray(" cp .env.example .env # then add your Company ID")
4250
4578
  );
@@ -4271,14 +4599,14 @@ async function devCommand(options) {
4271
4599
  if (options.theme) {
4272
4600
  themeName = options.theme;
4273
4601
  try {
4274
- const workspaceThemePath = path9.join(getThemesDir(), themeName);
4602
+ const workspaceThemePath = path11.join(getThemesDir(), themeName);
4275
4603
  if (fs.existsSync(workspaceThemePath)) {
4276
4604
  themePath = workspaceThemePath;
4277
4605
  } else {
4278
- themePath = path9.join(process.cwd(), themeName);
4606
+ themePath = path11.join(process.cwd(), themeName);
4279
4607
  }
4280
4608
  } catch {
4281
- themePath = path9.join(process.cwd(), themeName);
4609
+ themePath = path11.join(process.cwd(), themeName);
4282
4610
  }
4283
4611
  if (!fs.existsSync(themePath)) {
4284
4612
  logger.error(`Theme "${themeName}" not found.`);
@@ -4289,10 +4617,10 @@ async function devCommand(options) {
4289
4617
  "theme.config.ts",
4290
4618
  "bundle-entry.ts",
4291
4619
  "manifest.ts"
4292
- ].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
4620
+ ].some((f) => fs.existsSync(path11.join(process.cwd(), f)));
4293
4621
  if (isThemeDir2) {
4294
4622
  themePath = process.cwd();
4295
- themeName = path9.basename(themePath);
4623
+ themeName = path11.basename(themePath);
4296
4624
  } else {
4297
4625
  logger.error(
4298
4626
  "Not in a theme directory and no --theme specified. Run from theme root or use --theme flag."
@@ -4361,9 +4689,9 @@ async function devCommand(options) {
4361
4689
  watcher.close();
4362
4690
  await context2.dispose();
4363
4691
  server.close();
4364
- const shimPath = path9.join(outputDir, ".process-shim.js");
4692
+ const shimPath = path11.join(outputDir, ".process-shim.js");
4365
4693
  try {
4366
- await fs8.unlink(shimPath);
4694
+ await fs9.unlink(shimPath);
4367
4695
  } catch {
4368
4696
  }
4369
4697
  process.exit(0);
@@ -4372,8 +4700,8 @@ async function devCommand(options) {
4372
4700
 
4373
4701
  // src/commands/config.ts
4374
4702
  init_logger();
4375
- var CONFIG_DIR = path9.join(os.homedir(), ".onexthm");
4376
- var CONFIG_FILE = path9.join(CONFIG_DIR, ".env");
4703
+ var CONFIG_DIR = path11.join(os.homedir(), ".onexthm");
4704
+ var CONFIG_FILE = path11.join(CONFIG_DIR, ".env");
4377
4705
  var CONFIG_ENTRIES = [
4378
4706
  {
4379
4707
  key: "AWS_ACCESS_KEY_ID",
@@ -4515,9 +4843,13 @@ async function configCommand() {
4515
4843
 
4516
4844
  // src/commands/login.ts
4517
4845
  init_logger();
4518
- async function loginCommand() {
4846
+ async function loginCommand(options = {}) {
4847
+ const env = options.env ?? "dev";
4848
+ const apiUrl = getApiUrl(env);
4519
4849
  logger.header("OneX Theme Developer Login");
4520
- const existing = loadAuthTokens();
4850
+ logger.info(`Environment: ${env} (${apiUrl})`);
4851
+ logger.newLine();
4852
+ const existing = loadAuthTokens(env);
4521
4853
  if (existing) {
4522
4854
  logger.info(`Already logged in as: ${existing.user.email}`);
4523
4855
  const { relogin } = await inquirer.prompt([
@@ -4546,7 +4878,6 @@ async function loginCommand() {
4546
4878
  ]);
4547
4879
  logger.startSpinner("Logging in...");
4548
4880
  try {
4549
- const apiUrl = getApiUrl();
4550
4881
  const response = await fetch(`${apiUrl}/auth/login`, {
4551
4882
  method: "POST",
4552
4883
  headers: { "Content-Type": "application/json" },
@@ -4581,15 +4912,18 @@ async function loginCommand() {
4581
4912
  userId: claims.sub
4582
4913
  }
4583
4914
  };
4584
- await saveAuthTokens(tokens);
4915
+ await saveAuthTokens(tokens, env);
4585
4916
  logger.stopSpinner(true, "Logged in!");
4586
4917
  logger.newLine();
4587
- logger.info(` Email: ${tokens.user.email}`);
4588
- if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
4918
+ logger.info(` Environment: ${env}`);
4919
+ logger.info(` Email: ${tokens.user.email}`);
4920
+ if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
4589
4921
  if (tokens.user.companyId)
4590
- logger.info(` Company: ${tokens.user.companyId}`);
4922
+ logger.info(` Company: ${tokens.user.companyId}`);
4591
4923
  logger.newLine();
4592
- logger.success("Token stored securely in ~/.onexthm/auth.json (encrypted)");
4924
+ logger.success(
4925
+ `Token stored securely in ~/.onexthm/auth-${env}.json (encrypted)`
4926
+ );
4593
4927
  } catch (error) {
4594
4928
  logger.stopSpinner(false, "Login failed");
4595
4929
  logger.error(error instanceof Error ? error.message : "Connection failed");
@@ -4599,142 +4933,418 @@ async function loginCommand() {
4599
4933
 
4600
4934
  // src/commands/logout.ts
4601
4935
  init_logger();
4602
- async function logoutCommand() {
4603
- const tokens = loadAuthTokens();
4936
+ async function logoutCommand(options = {}) {
4937
+ const env = options.env ?? "dev";
4938
+ const tokens = loadAuthTokens(env);
4604
4939
  if (!tokens) {
4605
- logger.info("Not logged in.");
4940
+ logger.info(`Not logged in to ${env} environment.`);
4606
4941
  return;
4607
4942
  }
4608
- await clearAuthTokens();
4609
- logger.success(`Logged out (was: ${tokens.user.email})`);
4943
+ await clearAuthTokens(env);
4944
+ logger.success(`Logged out of ${env} (was: ${tokens.user.email})`);
4610
4945
  }
4611
4946
 
4612
4947
  // src/commands/whoami.ts
4613
4948
  init_logger();
4614
- async function whoamiCommand() {
4615
- const tokens = loadAuthTokens();
4949
+ async function whoamiCommand(options = {}) {
4950
+ const env = options.env ?? "dev";
4951
+ const tokens = loadAuthTokens(env);
4616
4952
  if (!tokens) {
4617
- logger.error("Not logged in. Run: onexthm login");
4953
+ logger.error(
4954
+ `Not logged in to ${env} environment. Run: onexthm login --env ${env}`
4955
+ );
4618
4956
  process.exit(1);
4619
4957
  }
4620
4958
  const expired = isTokenExpired(tokens);
4621
4959
  logger.header("OneX Theme Developer");
4622
- logger.info(` Email: ${tokens.user.email}`);
4623
- if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
4960
+ logger.info(` Environment: ${env} (${getApiUrl(env)})`);
4961
+ logger.info(` Email: ${tokens.user.email}`);
4962
+ if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
4624
4963
  if (tokens.user.companyId)
4625
- logger.info(` Company: ${tokens.user.companyId}`);
4964
+ logger.info(` Company: ${tokens.user.companyId}`);
4626
4965
  logger.info(
4627
- ` Status: ${expired ? "\u26A0 Token expired (will auto-refresh)" : "\u2713 Active"}`
4966
+ ` Status: ${expired ? "\u26A0 Token expired (will auto-refresh)" : "\u2713 Active"}`
4628
4967
  );
4629
4968
  }
4630
4969
 
4631
4970
  // src/commands/publish.ts
4632
4971
  init_logger();
4633
- var MIME_MAP = {
4634
- ".png": "image/png",
4635
- ".jpg": "image/jpeg",
4636
- ".jpeg": "image/jpeg",
4637
- ".gif": "image/gif",
4638
- ".webp": "image/webp",
4639
- ".avif": "image/avif",
4640
- ".svg": "image/svg+xml",
4641
- ".ico": "image/x-icon",
4642
- ".bmp": "image/bmp",
4643
- ".woff": "font/woff",
4644
- ".woff2": "font/woff2",
4645
- ".ttf": "font/ttf",
4646
- ".otf": "font/otf",
4647
- ".eot": "application/vnd.ms-fontobject",
4648
- ".mp4": "video/mp4",
4649
- ".webm": "video/webm",
4650
- ".mov": "video/quicktime",
4651
- ".ogg": "video/ogg",
4652
- ".json": "application/json"
4972
+ init_scan_theme_assets();
4973
+
4974
+ // src/utils/fetch-prior-schemas.ts
4975
+ async function fetchPriorGateManifests(themeId, env) {
4976
+ const apiUrl = getApiUrl(env);
4977
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/gate-manifests/latest`;
4978
+ let res;
4979
+ try {
4980
+ res = await authenticatedFetch(url, { method: "GET" }, env);
4981
+ } catch (err) {
4982
+ return {
4983
+ result: null,
4984
+ reason: `network error: ${err instanceof Error ? err.message : "unknown"}`
4985
+ };
4986
+ }
4987
+ if (res.status === 404) {
4988
+ return { result: null, reason: "no-prior" };
4989
+ }
4990
+ if (!res.ok) {
4991
+ return {
4992
+ result: null,
4993
+ reason: `server returned ${res.status} ${res.statusText}`
4994
+ };
4995
+ }
4996
+ let data;
4997
+ try {
4998
+ data = await res.json();
4999
+ } catch {
5000
+ return { result: null, reason: "non-JSON response from server" };
5001
+ }
5002
+ const body = data.statusCode ? data.body : data;
5003
+ if (!body || typeof body.version !== "string" || !body.schemas || !body.assets) {
5004
+ return { result: null, reason: "malformed response (missing fields)" };
5005
+ }
5006
+ return {
5007
+ result: {
5008
+ version: body.version,
5009
+ schemas: body.schemas,
5010
+ assets: body.assets
5011
+ },
5012
+ reason: null
5013
+ };
5014
+ }
5015
+
5016
+ // src/utils/schema-diff.ts
5017
+ var SEVERITY = {
5018
+ safe: 0,
5019
+ "safe-rename": 1,
5020
+ "defaults-only": 2,
5021
+ additive: 3,
5022
+ breaking: 4,
5023
+ "breaking-asset": 5,
5024
+ "breaking-severe": 6
4653
5025
  };
4654
- var HASH_LEN = 8;
4655
- var VIDEO_EXTENSIONS = [
4656
- ".mp4",
4657
- ".webm",
4658
- ".ogg",
4659
- ".mov",
4660
- ".avi",
4661
- ".mkv"
4662
- ];
4663
- function isVideoAsset(filePath) {
4664
- const lower = filePath.toLowerCase();
4665
- return VIDEO_EXTENSIONS.some((ext) => lower.endsWith(ext));
5026
+ var BUMP_FOR = {
5027
+ safe: "patch",
5028
+ "safe-rename": "patch",
5029
+ "defaults-only": "patch",
5030
+ additive: "minor",
5031
+ breaking: "major",
5032
+ "breaking-asset": "major",
5033
+ "breaking-severe": "major"
5034
+ };
5035
+ function bumpFor(kind) {
5036
+ return BUMP_FOR[kind];
4666
5037
  }
4667
- function mimeFor(filename) {
4668
- const ext = path9.extname(filename).toLowerCase();
4669
- return MIME_MAP[ext] || "application/octet-stream";
5038
+ function maxSeverity(a, b) {
5039
+ return SEVERITY[a] >= SEVERITY[b] ? a : b;
4670
5040
  }
4671
- async function sha256Prefix(absPath, len) {
4672
- const buf = await fs.readFile(absPath);
4673
- return crypto.createHash("sha256").update(buf).digest("hex").slice(0, len);
5041
+ function classify(changes) {
5042
+ let highest = "safe";
5043
+ for (const c of changes) {
5044
+ highest = maxSeverity(highest, c.kind);
5045
+ }
5046
+ return { bump: bumpFor(highest), highest, changes };
4674
5047
  }
4675
- function insertHashIntoName(relPath, hash) {
4676
- const dir = path9.posix.dirname(relPath);
4677
- const base = path9.posix.basename(relPath);
4678
- const ext = path9.posix.extname(base);
4679
- const stem = ext ? base.slice(0, -ext.length) : base;
4680
- const hashed = `${stem}-${hash}${ext}`;
4681
- return dir === "." ? hashed : `${dir}/${hashed}`;
5048
+ function diffManifests(prior, current) {
5049
+ const changes = [];
5050
+ const priorSections = prior.schemas.sections;
5051
+ const currentSections = current.schemas.sections;
5052
+ const sectionTypes = /* @__PURE__ */ new Set([
5053
+ ...Object.keys(priorSections),
5054
+ ...Object.keys(currentSections)
5055
+ ]);
5056
+ for (const type of [...sectionTypes].sort()) {
5057
+ const p = priorSections[type];
5058
+ const c = currentSections[type];
5059
+ if (p && !c) {
5060
+ changes.push({
5061
+ kind: "breaking-severe",
5062
+ path: `sections.${type}`,
5063
+ detail: `Section type "${type}" removed. Pages using this section will render empty.`
5064
+ });
5065
+ continue;
5066
+ }
5067
+ if (!p && c) {
5068
+ changes.push({
5069
+ kind: "additive",
5070
+ path: `sections.${type}`,
5071
+ detail: `Section type "${type}" added.`
5072
+ });
5073
+ continue;
5074
+ }
5075
+ if (p && c) diffSection(p, c, changes);
5076
+ }
5077
+ diffAssets(prior.assets, current.assets, changes);
5078
+ return changes;
4682
5079
  }
4683
- async function scanThemeAssets(distDir) {
4684
- const assetsDir = path9.join(distDir, "theme-assets");
4685
- if (!await fs.pathExists(assetsDir)) return [];
4686
- const files = await glob("**/*", {
4687
- cwd: assetsDir,
4688
- nodir: true,
4689
- dot: false
4690
- });
4691
- const results = [];
4692
- for (const rel of files) {
4693
- const absPath = path9.join(assetsDir, rel);
4694
- const stat = await fs.stat(absPath);
4695
- if (!stat.isFile()) continue;
4696
- const originalPath = rel.split(path9.sep).join("/");
4697
- const hash = await sha256Prefix(absPath, HASH_LEN);
4698
- const hashedPath = insertHashIntoName(originalPath, hash);
4699
- const contentType = mimeFor(rel);
4700
- results.push({
4701
- originalPath,
4702
- hashedPath,
4703
- hash,
4704
- size: stat.size,
4705
- contentType,
4706
- absPath
5080
+ function diffSection(prior, current, out) {
5081
+ const type = current.type;
5082
+ if (JSON.stringify(prior.dataRequirements) !== JSON.stringify(current.dataRequirements)) {
5083
+ out.push({
5084
+ kind: "breaking",
5085
+ path: `sections.${type}.dataRequirements`,
5086
+ detail: "dataRequirements changed."
4707
5087
  });
4708
5088
  }
4709
- results.sort((a, b) => a.originalPath.localeCompare(b.originalPath));
4710
- return results;
5089
+ diffFieldList(
5090
+ prior.settings,
5091
+ current.settings,
5092
+ `sections.${type}.settings`,
5093
+ out
5094
+ );
5095
+ diffDefaults(
5096
+ prior.defaults,
5097
+ current.defaults,
5098
+ `sections.${type}.defaults`,
5099
+ out
5100
+ );
5101
+ diffBlocks(prior.blocks, current.blocks, `sections.${type}.blocks`, out);
4711
5102
  }
4712
- function buildAssetMap(entries) {
4713
- const map = {};
4714
- for (const e of entries) {
4715
- map[e.originalPath] = e.hashedPath;
5103
+ function diffBlocks(prior, current, pathPrefix, out) {
5104
+ const priorByType = new Map(prior.map((b) => [b.type, b]));
5105
+ const currentByType = new Map(current.map((b) => [b.type, b]));
5106
+ for (const type of /* @__PURE__ */ new Set([
5107
+ ...priorByType.keys(),
5108
+ ...currentByType.keys()
5109
+ ])) {
5110
+ const p = priorByType.get(type);
5111
+ const c = currentByType.get(type);
5112
+ if (p && !c) {
5113
+ out.push({
5114
+ kind: "breaking",
5115
+ path: `${pathPrefix}.${type}`,
5116
+ detail: `Block type "${type}" removed.`
5117
+ });
5118
+ continue;
5119
+ }
5120
+ if (!p && c) {
5121
+ out.push({
5122
+ kind: "additive",
5123
+ path: `${pathPrefix}.${type}`,
5124
+ detail: `Block type "${type}" added.`
5125
+ });
5126
+ continue;
5127
+ }
5128
+ if (p && c) {
5129
+ diffFieldList(
5130
+ p.settings,
5131
+ c.settings,
5132
+ `${pathPrefix}.${type}.settings`,
5133
+ out
5134
+ );
5135
+ diffDefaults(
5136
+ p.defaults,
5137
+ c.defaults,
5138
+ `${pathPrefix}.${type}.defaults`,
5139
+ out
5140
+ );
5141
+ }
5142
+ }
5143
+ }
5144
+ function diffFieldList(prior, current, pathPrefix, out) {
5145
+ const priorById = new Map(prior.map((f) => [f.id, f]));
5146
+ const currentById = new Map(current.map((f) => [f.id, f]));
5147
+ const aliasToCurrent = /* @__PURE__ */ new Map();
5148
+ for (const f of current) {
5149
+ if (f.aliases) {
5150
+ for (const alias of f.aliases) {
5151
+ aliasToCurrent.set(alias, f);
5152
+ }
5153
+ }
5154
+ }
5155
+ for (const [id, p] of priorById) {
5156
+ const c = currentById.get(id);
5157
+ if (c) {
5158
+ diffFieldPair(p, c, `${pathPrefix}.${id}`, out);
5159
+ continue;
5160
+ }
5161
+ const renamed = aliasToCurrent.get(id);
5162
+ if (renamed) {
5163
+ if (renamed.type === p.type) {
5164
+ out.push({
5165
+ kind: "safe-rename",
5166
+ path: `${pathPrefix}.${id}`,
5167
+ detail: `Field "${id}" renamed to "${renamed.id}" (alias preserved).`
5168
+ });
5169
+ } else {
5170
+ out.push({
5171
+ kind: "breaking",
5172
+ path: `${pathPrefix}.${id}`,
5173
+ detail: `Field "${id}" renamed to "${renamed.id}" but type changed (${p.type} \u2192 ${renamed.type}).`
5174
+ });
5175
+ }
5176
+ } else {
5177
+ out.push({
5178
+ kind: "breaking",
5179
+ path: `${pathPrefix}.${id}`,
5180
+ detail: `Field "${id}" removed. Consider adding aliases: ["${id}"] to the replacement field if this was a rename.`
5181
+ });
5182
+ }
5183
+ }
5184
+ for (const [id, c] of currentById) {
5185
+ if (priorById.has(id)) continue;
5186
+ const coveredByAlias = c.aliases?.some((a) => priorById.has(a)) ?? false;
5187
+ if (coveredByAlias) continue;
5188
+ if (c.required && c.default === void 0) {
5189
+ out.push({
5190
+ kind: "breaking",
5191
+ path: `${pathPrefix}.${id}`,
5192
+ detail: `Required field "${id}" added with no default. Existing instances cannot satisfy it.`
5193
+ });
5194
+ } else {
5195
+ out.push({
5196
+ kind: "additive",
5197
+ path: `${pathPrefix}.${id}`,
5198
+ detail: `Field "${id}" added.`
5199
+ });
5200
+ }
5201
+ }
5202
+ }
5203
+ function diffFieldPair(p, c, path23, out) {
5204
+ if (p.type !== c.type) {
5205
+ out.push({
5206
+ kind: "breaking",
5207
+ path: path23,
5208
+ detail: `Type changed (${p.type} \u2192 ${c.type}). Saved values may misrender.`
5209
+ });
5210
+ return;
5211
+ }
5212
+ if (p.required !== true && c.required === true) {
5213
+ out.push({
5214
+ kind: "breaking",
5215
+ path: path23,
5216
+ detail: "Field became required. Existing empty instances now invalid."
5217
+ });
5218
+ }
5219
+ if (typeof p.maxLength === "number" || typeof c.maxLength === "number") {
5220
+ if ((c.maxLength ?? Infinity) < (p.maxLength ?? Infinity)) {
5221
+ out.push({
5222
+ kind: "breaking",
5223
+ path: path23,
5224
+ detail: `maxLength tightened (${p.maxLength ?? "\u221E"} \u2192 ${c.maxLength}).`
5225
+ });
5226
+ }
5227
+ }
5228
+ if (typeof p.min === "number" || typeof c.min === "number") {
5229
+ if ((c.min ?? -Infinity) > (p.min ?? -Infinity)) {
5230
+ out.push({
5231
+ kind: "breaking",
5232
+ path: path23,
5233
+ detail: `min raised (${p.min ?? "-\u221E"} \u2192 ${c.min}).`
5234
+ });
5235
+ }
5236
+ }
5237
+ if (typeof p.max === "number" || typeof c.max === "number") {
5238
+ if ((c.max ?? Infinity) < (p.max ?? Infinity)) {
5239
+ out.push({
5240
+ kind: "breaking",
5241
+ path: path23,
5242
+ detail: `max lowered (${p.max ?? "\u221E"} \u2192 ${c.max}).`
5243
+ });
5244
+ }
5245
+ }
5246
+ if (p.options || c.options) {
5247
+ const priorOpts = new Set(p.options ?? []);
5248
+ const currentOpts = new Set(c.options ?? []);
5249
+ const removed = [...priorOpts].filter((o) => !currentOpts.has(o));
5250
+ const added = [...currentOpts].filter((o) => !priorOpts.has(o));
5251
+ if (removed.length > 0) {
5252
+ out.push({
5253
+ kind: "breaking",
5254
+ path: path23,
5255
+ detail: `Option(s) removed: ${removed.join(", ")}. Existing saved values may be orphaned.`
5256
+ });
5257
+ }
5258
+ if (added.length > 0) {
5259
+ out.push({
5260
+ kind: "additive",
5261
+ path: path23,
5262
+ detail: `Option(s) added: ${added.join(", ")}.`
5263
+ });
5264
+ }
5265
+ }
5266
+ if (!deepEqual(p.default, c.default)) {
5267
+ out.push({
5268
+ kind: "defaults-only",
5269
+ path: path23,
5270
+ detail: `Default changed: ${JSON.stringify(p.default)} \u2192 ${JSON.stringify(c.default)}.`
5271
+ });
5272
+ }
5273
+ }
5274
+ function diffDefaults(prior, current, pathPrefix, out) {
5275
+ const keys = /* @__PURE__ */ new Set([...Object.keys(prior), ...Object.keys(current)]);
5276
+ for (const key of [...keys].sort()) {
5277
+ if (!(key in prior) || !(key in current)) continue;
5278
+ if (!deepEqual(prior[key], current[key])) {
5279
+ out.push({
5280
+ kind: "defaults-only",
5281
+ path: `${pathPrefix}.${key}`,
5282
+ detail: `Default value changed.`
5283
+ });
5284
+ }
5285
+ }
5286
+ }
5287
+ function diffAssets(prior, current, out) {
5288
+ const currentPaths = new Set(current.assets.map((a) => a.path));
5289
+ for (const a of prior.assets) {
5290
+ if (!currentPaths.has(a.path)) {
5291
+ out.push({
5292
+ kind: "breaking-asset",
5293
+ path: `theme-assets/${a.path}`,
5294
+ detail: `Asset "${a.path}" was present in the prior version and is now missing. Code that references it hardcoded will break.`
5295
+ });
5296
+ }
4716
5297
  }
4717
- return map;
5298
+ }
5299
+ function deepEqual(a, b) {
5300
+ if (a === b) return true;
5301
+ if (a === null || b === null) return false;
5302
+ if (typeof a !== typeof b) return false;
5303
+ if (typeof a !== "object") return false;
5304
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
5305
+ if (Array.isArray(a) && Array.isArray(b)) {
5306
+ if (a.length !== b.length) return false;
5307
+ for (let i = 0; i < a.length; i++) {
5308
+ if (!deepEqual(a[i], b[i])) return false;
5309
+ }
5310
+ return true;
5311
+ }
5312
+ const ak = Object.keys(a);
5313
+ const bk = Object.keys(b);
5314
+ if (ak.length !== bk.length) return false;
5315
+ for (const k of ak) {
5316
+ if (!deepEqual(
5317
+ a[k],
5318
+ b[k]
5319
+ ))
5320
+ return false;
5321
+ }
5322
+ return true;
4718
5323
  }
4719
5324
 
4720
5325
  // src/commands/publish.ts
4721
5326
  async function publishCommand(options) {
5327
+ const env = options.env ?? "dev";
4722
5328
  logger.header("OneX Theme Publish");
4723
- const tokens = await getValidTokens();
5329
+ logger.info(`Environment: ${env} (${getApiUrl(env)})`);
5330
+ logger.newLine();
5331
+ const tokens = await getValidTokens(env);
4724
5332
  if (!tokens) {
4725
- logger.error("Not logged in. Run: onexthm login");
5333
+ logger.error(
5334
+ `Not logged in to ${env} environment. Run: onexthm login --env ${env}`
5335
+ );
4726
5336
  process.exit(1);
4727
5337
  }
4728
5338
  logger.info(`Logged in as: ${tokens.user.email}`);
4729
5339
  let themePath;
4730
5340
  if (options.theme) {
4731
- themePath = path9.resolve(options.theme);
5341
+ themePath = path11.resolve(options.theme);
4732
5342
  } else {
4733
5343
  const isThemeDir2 = [
4734
5344
  "theme.config.ts",
4735
5345
  "bundle-entry.ts",
4736
5346
  "manifest.ts"
4737
- ].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
5347
+ ].some((f) => fs.existsSync(path11.join(process.cwd(), f)));
4738
5348
  if (isThemeDir2) {
4739
5349
  themePath = process.cwd();
4740
5350
  } else {
@@ -4744,13 +5354,13 @@ async function publishCommand(options) {
4744
5354
  process.exit(1);
4745
5355
  }
4746
5356
  }
4747
- const pkgPath = path9.join(themePath, "package.json");
5357
+ const pkgPath = path11.join(themePath, "package.json");
4748
5358
  if (!fs.existsSync(pkgPath)) {
4749
5359
  logger.error("No package.json found in theme directory");
4750
5360
  process.exit(1);
4751
5361
  }
4752
5362
  const pkg = fs.readJsonSync(pkgPath);
4753
- const themeId = pkg.name?.replace("@onex-themes/", "") || path9.basename(themePath);
5363
+ const themeId = pkg.name?.replace("@onex-themes/", "") || path11.basename(themePath);
4754
5364
  if (options.bump) {
4755
5365
  const currentVersion = pkg.version || "1.0.0";
4756
5366
  const newVersion = semver.inc(currentVersion, options.bump);
@@ -4773,56 +5383,63 @@ async function publishCommand(options) {
4773
5383
  logger.info(`Theme: ${themeId}`);
4774
5384
  logger.info(`Version: ${version2}`);
4775
5385
  logger.newLine();
4776
- const apiUrl = getApiUrl();
4777
- logger.startSpinner("Registering theme...");
4778
- try {
4779
- const regResponse = await authenticatedFetch(
4780
- `${apiUrl}/website-api/themes/register`,
4781
- {
4782
- method: "POST",
4783
- body: JSON.stringify({
4784
- themeId,
4785
- name: pkg.displayName || themeId,
4786
- description: pkg.description || "",
4787
- email: tokens.user.email,
4788
- author: typeof pkg.author === "string" ? pkg.author : pkg.author?.name || tokens.user.name || "",
4789
- category: pkg.onex?.category || "MINIMAL",
4790
- tags: pkg.keywords || [],
4791
- thumbnail_url: pkg.onex?.thumbnail || ""
4792
- })
4793
- }
4794
- );
4795
- const regData = await regResponse.json();
4796
- const regBody = regData.statusCode ? regData.body : regData;
4797
- if (!regResponse.ok) {
4798
- const errMsg = regBody.error || regBody.message || "Registration failed";
4799
- if (!errMsg.includes("already registered")) {
4800
- logger.stopSpinner(false, "Registration failed");
4801
- logger.error(errMsg);
4802
- process.exit(1);
5386
+ const apiUrl = getApiUrl(env);
5387
+ if (!options.dryRun) {
5388
+ logger.startSpinner("Registering theme...");
5389
+ try {
5390
+ const regResponse = await authenticatedFetch(
5391
+ `${apiUrl}/website-api/themes/register`,
5392
+ {
5393
+ method: "POST",
5394
+ body: JSON.stringify({
5395
+ themeId,
5396
+ name: pkg.displayName || themeId,
5397
+ description: pkg.description || "",
5398
+ email: tokens.user.email,
5399
+ author: typeof pkg.author === "string" ? pkg.author : pkg.author?.name || tokens.user.name || "",
5400
+ category: pkg.onex?.category || "MINIMAL",
5401
+ tags: pkg.keywords || [],
5402
+ thumbnail_url: pkg.onex?.thumbnail || ""
5403
+ })
5404
+ },
5405
+ env
5406
+ );
5407
+ const regData = await regResponse.json();
5408
+ const regBody = regData.statusCode ? regData.body : regData;
5409
+ if (!regResponse.ok) {
5410
+ const errMsg = regBody.error || regBody.message || "Registration failed";
5411
+ if (!errMsg.includes("already registered")) {
5412
+ logger.stopSpinner(false, "Registration failed");
5413
+ logger.error(errMsg);
5414
+ process.exit(1);
5415
+ }
4803
5416
  }
5417
+ logger.stopSpinner(true, regBody.message || "Theme registered");
5418
+ } catch (error) {
5419
+ logger.stopSpinner(false, "Registration failed");
5420
+ logger.error(
5421
+ error instanceof Error ? error.message : "Connection failed"
5422
+ );
5423
+ process.exit(1);
4804
5424
  }
4805
- logger.stopSpinner(true, regBody.message || "Theme registered");
4806
- } catch (error) {
4807
- logger.stopSpinner(false, "Registration failed");
4808
- logger.error(error instanceof Error ? error.message : "Connection failed");
4809
- process.exit(1);
4810
5425
  }
4811
- logger.startSpinner("Checking version availability...");
4812
- try {
4813
- const checkResponse = await authenticatedFetch(
4814
- `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(version2)}/exists`,
4815
- { method: "GET" }
4816
- );
4817
- const checkData = await checkResponse.json();
4818
- const checkBody = checkData.statusCode ? checkData.body : checkData;
4819
- if (checkBody.exists) {
4820
- logger.stopSpinner(false, "Version already published");
4821
- const patchVer = semver.inc(version2, "patch") || "?";
4822
- const minorVer = semver.inc(version2, "minor") || "?";
4823
- const majorVer = semver.inc(version2, "major") || "?";
4824
- logger.error(
4825
- `
5426
+ if (!options.dryRun) {
5427
+ logger.startSpinner("Checking version availability...");
5428
+ try {
5429
+ const checkResponse = await authenticatedFetch(
5430
+ `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(version2)}/exists`,
5431
+ { method: "GET" },
5432
+ env
5433
+ );
5434
+ const checkData = await checkResponse.json();
5435
+ const checkBody = checkData.statusCode ? checkData.body : checkData;
5436
+ if (checkBody.exists) {
5437
+ logger.stopSpinner(false, "Version already published");
5438
+ const patchVer = semver.inc(version2, "patch") || "?";
5439
+ const minorVer = semver.inc(version2, "minor") || "?";
5440
+ const majorVer = semver.inc(version2, "major") || "?";
5441
+ logger.error(
5442
+ `
4826
5443
  Version ${version2} of "${themeId}" is already published and cannot be overwritten.
4827
5444
 
4828
5445
  To publish a new version:
@@ -4833,12 +5450,16 @@ Or use the --bump flag:
4833
5450
  onexthm publish --bump patch (${version2} -> ${patchVer})
4834
5451
  onexthm publish --bump minor (${version2} -> ${minorVer})
4835
5452
  onexthm publish --bump major (${version2} -> ${majorVer})`
5453
+ );
5454
+ process.exit(1);
5455
+ }
5456
+ logger.stopSpinner(true, `Version ${version2} is available`);
5457
+ } catch (error) {
5458
+ logger.stopSpinner(
5459
+ true,
5460
+ "Version check skipped (endpoint not available)"
4836
5461
  );
4837
- process.exit(1);
4838
5462
  }
4839
- logger.stopSpinner(true, `Version ${version2} is available`);
4840
- } catch (error) {
4841
- logger.stopSpinner(true, "Version check skipped (endpoint not available)");
4842
5463
  }
4843
5464
  logger.startSpinner("Building theme...");
4844
5465
  try {
@@ -4855,7 +5476,19 @@ Or use the --bump flag:
4855
5476
  logger.error(error instanceof Error ? error.message : "Build error");
4856
5477
  process.exit(1);
4857
5478
  }
4858
- const distDir = path9.join(themePath, "dist");
5479
+ const distDir = path11.join(themePath, "dist");
5480
+ const classification = await runSchemaDiffGate(
5481
+ themeId,
5482
+ distDir,
5483
+ env,
5484
+ options
5485
+ );
5486
+ if (options.dryRun) {
5487
+ const exitCode = classification?.highest === "breaking" || classification?.highest === "breaking-severe" || classification?.highest === "breaking-asset" ? 2 : 0;
5488
+ logger.newLine();
5489
+ logger.info(`Dry run complete (exit ${exitCode}). No files uploaded.`);
5490
+ process.exit(exitCode);
5491
+ }
4859
5492
  let assetEntries = [];
4860
5493
  try {
4861
5494
  assetEntries = await scanThemeAssets(distDir);
@@ -4877,7 +5510,7 @@ Or use the --bump flag:
4877
5510
  logger.startSpinner(`Uploading ${videoAssets.length} video(s)...`);
4878
5511
  try {
4879
5512
  for (const video of videoAssets) {
4880
- const url = await uploadVideoMultipart(apiUrl, themeId, video);
5513
+ const url = await uploadVideoMultipart(apiUrl, themeId, video, env);
4881
5514
  videoUrls[video.originalPath] = url;
4882
5515
  }
4883
5516
  logger.stopSpinner(true, `Uploaded ${videoAssets.length} video(s)`);
@@ -4892,7 +5525,7 @@ Or use the --bump flag:
4892
5525
  for (const [originalPath, url] of Object.entries(videoUrls)) {
4893
5526
  assetMap[originalPath] = url;
4894
5527
  }
4895
- const assetMapPath = path9.join(distDir, "asset-map.json");
5528
+ const assetMapPath = path11.join(distDir, "asset-map.json");
4896
5529
  await fs.writeFile(assetMapPath, JSON.stringify(assetMap, null, 2));
4897
5530
  } catch (error) {
4898
5531
  logger.error(
@@ -4919,7 +5552,8 @@ Or use the --bump flag:
4919
5552
  content_type: a.contentType
4920
5553
  }))
4921
5554
  })
4922
- }
5555
+ },
5556
+ env
4923
5557
  );
4924
5558
  const pubData = await pubResponse.json();
4925
5559
  const pubBody = pubData.statusCode ? pubData.body : pubData;
@@ -4945,6 +5579,12 @@ Or use the --bump flag:
4945
5579
  }
4946
5580
  if (assetUploads.length > 0) {
4947
5581
  logger.startSpinner(`Uploading ${assetUploads.length} asset(s) to S3...`);
5582
+ if (assetUploads[0]) {
5583
+ logger.log(
5584
+ ` [debug] sample presigned PUT URL: ${assetUploads[0].upload_url}`
5585
+ );
5586
+ logger.log(` [debug] sample s3_key: ${assetUploads[0].s3_key}`);
5587
+ }
4948
5588
  const CONCURRENCY = 8;
4949
5589
  const byHashedPath = new Map(regularAssets.map((a) => [a.hashedPath, a]));
4950
5590
  const queue = [...assetUploads];
@@ -4971,6 +5611,13 @@ Or use the --bump flag:
4971
5611
  body: buf
4972
5612
  });
4973
5613
  if (!res.ok) {
5614
+ if (failed === 0) {
5615
+ const errBody = await res.text().catch(() => "(unreadable)");
5616
+ logger.log(` [debug] PUT ${item.upload_url}`);
5617
+ logger.log(
5618
+ ` [debug] response ${res.status} ${res.statusText}: ${errBody.slice(0, 500)}`
5619
+ );
5620
+ }
4974
5621
  throw new Error(`HTTP ${res.status}`);
4975
5622
  }
4976
5623
  uploaded++;
@@ -5001,7 +5648,7 @@ Or use the --bump flag:
5001
5648
  logger.error("Build the theme first: onexthm build");
5002
5649
  process.exit(1);
5003
5650
  }
5004
- const bundleZipPath = path9.join(themePath, "dist", "bundle.zip");
5651
+ const bundleZipPath = path11.join(themePath, "dist", "bundle.zip");
5005
5652
  await createZip(distDir, bundleZipPath, [
5006
5653
  "bundle.zip",
5007
5654
  "source.zip",
@@ -5028,7 +5675,7 @@ Or use the --bump flag:
5028
5675
  }
5029
5676
  logger.startSpinner("Uploading source...");
5030
5677
  try {
5031
- const sourceZipPath = path9.join(themePath, "dist", "source.zip");
5678
+ const sourceZipPath = path11.join(themePath, "dist", "source.zip");
5032
5679
  await createZip(themePath, sourceZipPath, [
5033
5680
  "node_modules",
5034
5681
  "dist",
@@ -5055,7 +5702,8 @@ Or use the --bump flag:
5055
5702
  try {
5056
5703
  const confirmResponse = await authenticatedFetch(
5057
5704
  `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(version2)}/confirm`,
5058
- { method: "POST" }
5705
+ { method: "POST" },
5706
+ env
5059
5707
  );
5060
5708
  const confirmData = await confirmResponse.json();
5061
5709
  const confirmBody = confirmData.statusCode ? confirmData.body : confirmData;
@@ -5101,9 +5749,9 @@ Or use the --bump flag:
5101
5749
  }
5102
5750
  logger.newLine();
5103
5751
  logger.success(`\u2713 Theme "${themeId}" v${version2} published!`);
5104
- await uploadThumbnail(apiUrl, themeId, themePath, distDir);
5752
+ await uploadThumbnail(apiUrl, themeId, themePath, distDir, env);
5105
5753
  }
5106
- async function uploadThumbnail(apiUrl, themeId, themePath, distDir) {
5754
+ async function uploadThumbnail(apiUrl, themeId, themePath, distDir, env = "dev") {
5107
5755
  const THUMBNAIL_CANDIDATES = [
5108
5756
  { file: "thumbnail.png", mime: "image/png" },
5109
5757
  { file: "thumbnail.jpg", mime: "image/jpeg" },
@@ -5113,7 +5761,7 @@ async function uploadThumbnail(apiUrl, themeId, themePath, distDir) {
5113
5761
  let imageBase64 = null;
5114
5762
  let mimeType = "image/png";
5115
5763
  for (const { file, mime } of THUMBNAIL_CANDIDATES) {
5116
- const candidate = path9.join(themePath, file);
5764
+ const candidate = path11.join(themePath, file);
5117
5765
  if (fs.existsSync(candidate)) {
5118
5766
  const buf = fs.readFileSync(candidate);
5119
5767
  imageBase64 = `data:${mime};base64,${buf.toString("base64")}`;
@@ -5138,9 +5786,19 @@ async function uploadThumbnail(apiUrl, themeId, themePath, distDir) {
5138
5786
  }
5139
5787
  }
5140
5788
  logger.startSpinner("Uploading thumbnail...");
5789
+ const imageUploadUrl = `${apiUrl}/media/images/upload`;
5790
+ const imageRequestBody = {
5791
+ prefix: `themes/${themeId}`,
5792
+ image: imageBase64 ? `${imageBase64.slice(0, 60)}... [${Math.round(imageBase64.length * 3 / 4 / 1024)} KB]` : null,
5793
+ name: "thumbnail.png"
5794
+ };
5795
+ logger.log(` \u2192 POST ${imageUploadUrl}`);
5796
+ logger.log(
5797
+ ` \u2192 body: ${JSON.stringify({ ...imageRequestBody, image: imageBase64 ? `<base64 ${mimeType} truncated>` : null })}`
5798
+ );
5141
5799
  try {
5142
5800
  const uploadRes = await authenticatedFetch(
5143
- `${apiUrl}/media/images/upload`,
5801
+ imageUploadUrl,
5144
5802
  {
5145
5803
  method: "POST",
5146
5804
  body: JSON.stringify({
@@ -5148,30 +5806,56 @@ async function uploadThumbnail(apiUrl, themeId, themePath, distDir) {
5148
5806
  image: imageBase64,
5149
5807
  name: "thumbnail.png"
5150
5808
  })
5151
- }
5809
+ },
5810
+ env
5152
5811
  );
5153
- const uploadData = await uploadRes.json();
5812
+ logger.log(` \u2190 HTTP ${uploadRes.status} ${uploadRes.statusText}`);
5813
+ const uploadRawText = await uploadRes.text();
5814
+ logger.log(` \u2190 raw response: ${uploadRawText.slice(0, 500)}`);
5815
+ let uploadData;
5816
+ try {
5817
+ uploadData = JSON.parse(uploadRawText);
5818
+ } catch {
5819
+ throw new Error(
5820
+ `Image upload returned non-JSON (HTTP ${uploadRes.status}): ${uploadRawText.slice(0, 200)}`
5821
+ );
5822
+ }
5154
5823
  const uploadBody = uploadData.statusCode ? uploadData.body : uploadData;
5155
5824
  if (!uploadRes.ok || !uploadBody.url) {
5156
- throw new Error(uploadBody.error || "Upload failed");
5825
+ throw new Error(
5826
+ `Image upload failed \u2014 HTTP ${uploadRes.status}: ${uploadBody.error || uploadBody.message || JSON.stringify(uploadBody).slice(0, 300)}`
5827
+ );
5157
5828
  }
5829
+ const patchUrl = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}`;
5830
+ logger.log(` \u2192 PATCH ${patchUrl}`);
5158
5831
  const patchRes = await authenticatedFetch(
5159
- `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}`,
5832
+ patchUrl,
5160
5833
  {
5161
5834
  method: "PATCH",
5162
5835
  body: JSON.stringify({ thumbnail_url: uploadBody.url })
5163
- }
5836
+ },
5837
+ env
5164
5838
  );
5839
+ logger.log(` \u2190 HTTP ${patchRes.status} ${patchRes.statusText}`);
5165
5840
  if (!patchRes.ok) {
5166
- const patchData = await patchRes.json();
5167
- const patchBody = patchData.statusCode ? patchData.body : patchData;
5168
- throw new Error(patchBody.error || "Failed to set thumbnail");
5841
+ const patchRawText = await patchRes.text();
5842
+ logger.log(` \u2190 raw response: ${patchRawText.slice(0, 500)}`);
5843
+ let patchBody = {};
5844
+ try {
5845
+ patchBody = JSON.parse(patchRawText);
5846
+ } catch {
5847
+ }
5848
+ patchBody = patchBody.statusCode ? patchBody.body : patchBody;
5849
+ throw new Error(
5850
+ `Thumbnail patch failed \u2014 HTTP ${patchRes.status}: ${patchBody.error || patchBody.message || patchRawText.slice(0, 200)}`
5851
+ );
5169
5852
  }
5170
5853
  logger.stopSpinner(true, "Thumbnail set");
5171
5854
  } catch (err) {
5172
- logger.stopSpinner(false, "Thumbnail upload skipped");
5855
+ logger.stopSpinner(false, "Thumbnail upload failed");
5856
+ logger.error(err instanceof Error ? err.message : String(err));
5173
5857
  logger.info(
5174
- `Theme published successfully. Thumbnail can be updated later.`
5858
+ "Theme published successfully. Thumbnail can be updated later."
5175
5859
  );
5176
5860
  }
5177
5861
  }
@@ -5179,7 +5863,7 @@ async function screenshotHomePage(themePath, distDir) {
5179
5863
  const { compilePreviewRuntime: compilePreviewRuntime2 } = await Promise.resolve().then(() => (init_compile_theme(), compile_theme_exports));
5180
5864
  const { createDevServer: createDevServer2 } = await Promise.resolve().then(() => (init_dev_server(), dev_server_exports));
5181
5865
  const previewRuntimePath = await compilePreviewRuntime2(themePath);
5182
- const themeName = path9.basename(themePath);
5866
+ const themeName = path11.basename(themePath);
5183
5867
  const port = await findFreePort(4500);
5184
5868
  const server = createDevServer2({
5185
5869
  port,
@@ -5235,25 +5919,40 @@ async function findFreePort(start) {
5235
5919
  srv.on("error", () => resolve(findFreePort(start + 1)));
5236
5920
  });
5237
5921
  }
5238
- async function uploadVideoMultipart(apiUrl, themeId, video) {
5239
- const fileName = path9.basename(video.originalPath);
5922
+ async function uploadVideoMultipart(apiUrl, themeId, video, env = "dev") {
5923
+ const fileName = path11.basename(video.originalPath);
5924
+ const videoInitUrl = `${apiUrl}/media/videos/multipart/init`;
5925
+ const videoInitBody = {
5926
+ file_name: fileName,
5927
+ content_type: video.contentType,
5928
+ file_size: video.size,
5929
+ prefix: `themes/${themeId}/assets`
5930
+ };
5931
+ logger.log(` \u2192 POST ${videoInitUrl}`);
5932
+ logger.log(` \u2192 body: ${JSON.stringify(videoInitBody)}`);
5240
5933
  const initRes = await authenticatedFetch(
5241
- `${apiUrl}/media/videos/multipart/init`,
5934
+ videoInitUrl,
5242
5935
  {
5243
5936
  method: "POST",
5244
- body: JSON.stringify({
5245
- file_name: fileName,
5246
- content_type: video.contentType,
5247
- file_size: video.size,
5248
- prefix: `themes/${themeId}/assets`
5249
- })
5250
- }
5937
+ body: JSON.stringify(videoInitBody)
5938
+ },
5939
+ env
5251
5940
  );
5252
- const initData = await initRes.json();
5941
+ logger.log(` \u2190 HTTP ${initRes.status} ${initRes.statusText}`);
5942
+ const initRawText = await initRes.text();
5943
+ logger.log(` \u2190 raw response: ${initRawText.slice(0, 500)}`);
5944
+ let initData;
5945
+ try {
5946
+ initData = JSON.parse(initRawText);
5947
+ } catch {
5948
+ throw new Error(
5949
+ `Video init returned non-JSON (HTTP ${initRes.status}): ${initRawText.slice(0, 200)}`
5950
+ );
5951
+ }
5253
5952
  const initBody = initData.statusCode ? initData.body : initData;
5254
5953
  if (!initRes.ok || !initBody.upload_id) {
5255
5954
  throw new Error(
5256
- `Init multipart failed for ${fileName}: ${initBody.error || initRes.status}`
5955
+ `Init multipart failed for ${fileName} \u2014 HTTP ${initRes.status}: ${initBody.error || initBody.message || JSON.stringify(initBody).slice(0, 300)}`
5257
5956
  );
5258
5957
  }
5259
5958
  const { upload_id, file_key, chunk_size, chunk_urls } = initBody;
@@ -5288,25 +5987,47 @@ async function uploadVideoMultipart(apiUrl, themeId, video) {
5288
5987
  )
5289
5988
  );
5290
5989
  parts.sort((a, b) => a.part_number - b.part_number);
5990
+ const videoCompleteUrl = `${apiUrl}/media/videos/multipart/complete`;
5991
+ logger.log(` \u2192 POST ${videoCompleteUrl}`);
5992
+ logger.log(
5993
+ ` \u2192 body: ${JSON.stringify({ upload_id, file_key, parts: `[${parts.length} parts]` })}`
5994
+ );
5291
5995
  const completeRes = await authenticatedFetch(
5292
- `${apiUrl}/media/videos/multipart/complete`,
5996
+ videoCompleteUrl,
5293
5997
  {
5294
5998
  method: "POST",
5295
5999
  body: JSON.stringify({ upload_id, file_key, parts })
5296
- }
6000
+ },
6001
+ env
5297
6002
  );
5298
- const completeData = await completeRes.json();
6003
+ logger.log(` \u2190 HTTP ${completeRes.status} ${completeRes.statusText}`);
6004
+ const completeRawText = await completeRes.text();
6005
+ logger.log(` \u2190 raw response: ${completeRawText.slice(0, 500)}`);
6006
+ let completeData;
6007
+ try {
6008
+ completeData = JSON.parse(completeRawText);
6009
+ } catch {
6010
+ throw new Error(
6011
+ `Video complete returned non-JSON (HTTP ${completeRes.status}): ${completeRawText.slice(0, 200)}`
6012
+ );
6013
+ }
5299
6014
  const completeBody = completeData.statusCode ? completeData.body : completeData;
5300
6015
  if (!completeRes.ok || !completeBody.url) {
5301
6016
  try {
5302
- await authenticatedFetch(`${apiUrl}/media/videos/multipart/abort`, {
5303
- method: "POST",
5304
- body: JSON.stringify({ upload_id, file_key })
5305
- });
6017
+ const abortUrl = `${apiUrl}/media/videos/multipart/abort`;
6018
+ logger.log(` \u2192 POST ${abortUrl} (cleanup)`);
6019
+ await authenticatedFetch(
6020
+ abortUrl,
6021
+ {
6022
+ method: "POST",
6023
+ body: JSON.stringify({ upload_id, file_key })
6024
+ },
6025
+ env
6026
+ );
5306
6027
  } catch {
5307
6028
  }
5308
6029
  throw new Error(
5309
- `Complete multipart failed for ${fileName}: ${completeBody.error || completeRes.status}`
6030
+ `Complete multipart failed for ${fileName} \u2014 HTTP ${completeRes.status}: ${completeBody.error || completeBody.message || JSON.stringify(completeBody).slice(0, 300)}`
5310
6031
  );
5311
6032
  }
5312
6033
  return completeBody.url;
@@ -5328,6 +6049,91 @@ async function createZip(sourceDir, outputPath, exclude) {
5328
6049
  archive.finalize();
5329
6050
  });
5330
6051
  }
6052
+ async function runSchemaDiffGate(themeId, distDir, env, options) {
6053
+ logger.startSpinner("Fetching prior version for diff...");
6054
+ const { result: prior, reason } = await fetchPriorGateManifests(themeId, env);
6055
+ if (!prior) {
6056
+ if (reason === "no-prior") {
6057
+ logger.stopSpinner(true, "First publish \u2014 no prior version to diff");
6058
+ } else {
6059
+ logger.stopSpinner(true, `Gate skipped (${reason})`);
6060
+ }
6061
+ return null;
6062
+ }
6063
+ logger.stopSpinner(true, `Fetched prior version ${prior.version}`);
6064
+ let currentSchemas;
6065
+ let currentAssets;
6066
+ try {
6067
+ currentSchemas = JSON.parse(
6068
+ await fs.readFile(path11.join(distDir, "schemas.json"), "utf-8")
6069
+ );
6070
+ } catch (err) {
6071
+ logger.warning(
6072
+ `Gate skipped: dist/schemas.json missing or unreadable (${err instanceof Error ? err.message : "unknown"})`
6073
+ );
6074
+ return null;
6075
+ }
6076
+ try {
6077
+ currentAssets = JSON.parse(
6078
+ await fs.readFile(path11.join(distDir, "asset-manifest.json"), "utf-8")
6079
+ );
6080
+ } catch {
6081
+ currentAssets = { manifestVersion: 1, assets: [] };
6082
+ }
6083
+ const changes = diffManifests(
6084
+ { schemas: prior.schemas, assets: prior.assets },
6085
+ { schemas: currentSchemas, assets: currentAssets }
6086
+ );
6087
+ const classification = classify(changes);
6088
+ printGateReport(prior.version, classification);
6089
+ if (options.dryRun) return classification;
6090
+ const isBreaking = classification.highest === "breaking" || classification.highest === "breaking-severe" || classification.highest === "breaking-asset";
6091
+ if (isBreaking && !options.force) {
6092
+ logger.error(
6093
+ "\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)."
6094
+ );
6095
+ process.exit(1);
6096
+ }
6097
+ if (classification.highest === "defaults-only" && !options.confirmDefaults) {
6098
+ logger.error(
6099
+ "\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."
6100
+ );
6101
+ process.exit(1);
6102
+ }
6103
+ return classification;
6104
+ }
6105
+ function printGateReport(priorVersion, classification) {
6106
+ logger.newLine();
6107
+ logger.info(`Schema diff vs. v${priorVersion}:`);
6108
+ if (classification.changes.length === 0) {
6109
+ logger.log(" \u2713 Safe \u2014 no schema changes detected");
6110
+ return;
6111
+ }
6112
+ for (const change of classification.changes) {
6113
+ const icon = iconFor(change.kind);
6114
+ logger.log(` ${icon} [${change.kind}] ${change.path} \u2014 ${change.detail}`);
6115
+ }
6116
+ logger.log(
6117
+ `
6118
+ \u2192 Classification: ${classification.highest}. Suggested bump: ${classification.bump}.`
6119
+ );
6120
+ }
6121
+ function iconFor(kind) {
6122
+ switch (kind) {
6123
+ case "safe":
6124
+ case "safe-rename":
6125
+ return "\u2713";
6126
+ case "additive":
6127
+ return "+";
6128
+ case "defaults-only":
6129
+ return "\u26A0";
6130
+ case "breaking":
6131
+ case "breaking-asset":
6132
+ return "\u2717";
6133
+ case "breaking-severe":
6134
+ return "\u2717\u2717";
6135
+ }
6136
+ }
5331
6137
 
5332
6138
  // src/commands/mcp.ts
5333
6139
  init_logger();
@@ -5339,18 +6145,18 @@ var AI_CONTEXT_FILES = [
5339
6145
  ".mcp.json"
5340
6146
  ];
5341
6147
  function resolveTargetDir(opts) {
5342
- return path9.resolve(opts.cwd ?? process.cwd());
6148
+ return path11.resolve(opts.cwd ?? process.cwd());
5343
6149
  }
5344
6150
  function resolveDefaultTemplateDir() {
5345
- return path9.join(getTemplatesDir(), "default");
6151
+ return path11.join(getTemplatesDir(), "default");
5346
6152
  }
5347
6153
  function isThemeDir(dir) {
5348
- return fs.existsSync(path9.join(dir, "theme.config.ts")) || fs.existsSync(path9.join(dir, "theme.config.js"));
6154
+ return fs.existsSync(path11.join(dir, "theme.config.ts")) || fs.existsSync(path11.join(dir, "theme.config.js"));
5349
6155
  }
5350
6156
  function inspectFiles(templateDir, targetDir) {
5351
6157
  return AI_CONTEXT_FILES.map((name) => {
5352
- const templatePath = path9.join(templateDir, name);
5353
- const targetPath = path9.join(targetDir, name);
6158
+ const templatePath = path11.join(templateDir, name);
6159
+ const targetPath = path11.join(targetDir, name);
5354
6160
  const exists = fs.existsSync(targetPath);
5355
6161
  let identical = false;
5356
6162
  if (exists && fs.existsSync(templatePath)) {
@@ -5493,7 +6299,7 @@ async function mcpDoctorCommand(options = {}) {
5493
6299
  return;
5494
6300
  }
5495
6301
  logger.success("theme.config.ts present");
5496
- const mcpJsonPath = path9.join(targetDir, ".mcp.json");
6302
+ const mcpJsonPath = path11.join(targetDir, ".mcp.json");
5497
6303
  if (!fs.existsSync(mcpJsonPath)) {
5498
6304
  logger.error(".mcp.json missing \u2014 run `onexthm mcp setup`");
5499
6305
  } else {
@@ -5531,7 +6337,7 @@ async function mcpDoctorCommand(options = {}) {
5531
6337
  logger.success(`${s.name} up to date`);
5532
6338
  }
5533
6339
  }
5534
- const registryPath = path9.join(targetDir, "sections-registry.ts");
6340
+ const registryPath = path11.join(targetDir, "sections-registry.ts");
5535
6341
  if (fs.existsSync(registryPath)) {
5536
6342
  logger.success("sections-registry.ts present");
5537
6343
  } else {
@@ -5543,22 +6349,22 @@ async function mcpDoctorCommand(options = {}) {
5543
6349
 
5544
6350
  // src/cli.ts
5545
6351
  dotenv.config({
5546
- path: path9.join(process.cwd(), ".env.local"),
6352
+ path: path11.join(process.cwd(), ".env.local"),
5547
6353
  override: true
5548
6354
  });
5549
- dotenv.config({ path: path9.join(process.cwd(), ".env") });
6355
+ dotenv.config({ path: path11.join(process.cwd(), ".env") });
5550
6356
  try {
5551
6357
  const projectRoot = getProjectRoot();
5552
- if (path9.resolve(projectRoot) !== path9.resolve(process.cwd())) {
6358
+ if (path11.resolve(projectRoot) !== path11.resolve(process.cwd())) {
5553
6359
  dotenv.config({
5554
- path: path9.join(projectRoot, ".env.local")
6360
+ path: path11.join(projectRoot, ".env.local")
5555
6361
  });
5556
- dotenv.config({ path: path9.join(projectRoot, ".env") });
6362
+ dotenv.config({ path: path11.join(projectRoot, ".env") });
5557
6363
  }
5558
6364
  } catch {
5559
6365
  }
5560
6366
  dotenv.config({
5561
- path: path9.join(os.homedir(), ".onexthm", ".env"),
6367
+ path: path11.join(os.homedir(), ".onexthm", ".env"),
5562
6368
  quiet: true
5563
6369
  });
5564
6370
  var require2 = createRequire(import.meta.url);
@@ -5569,7 +6375,11 @@ program.command("init").description("Create a new OneX theme project").argument(
5569
6375
  "-t, --template <template>",
5570
6376
  "Template to use (default, minimal)",
5571
6377
  "default"
5572
- ).option("--no-install", "Skip installing dependencies").option("--git", "Initialize git repository").option("-y, --yes", "Skip prompts and use defaults").action(initCommand);
6378
+ ).option("--no-install", "Skip installing dependencies").option("--git", "Initialize git repository").option("-y, --yes", "Skip prompts and use defaults").option(
6379
+ "--env <env>",
6380
+ "Target environment: dev, staging, or prod (default: dev)",
6381
+ "dev"
6382
+ ).action(initCommand);
5573
6383
  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(
5574
6384
  "-c, --category <category>",
5575
6385
  "Section category (headers, content, footers)"
@@ -5596,16 +6406,36 @@ program.command("download").description("Download a published theme via the webs
5596
6406
  "-v, --version <version>",
5597
6407
  "Theme version (default: latest)",
5598
6408
  "latest"
5599
- ).option("-b, --bucket <name>", "[deprecated] ignored").option("-e, --environment <env>", "[deprecated] ignored").option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
6409
+ ).option(
6410
+ "--env <env>",
6411
+ "Target environment: dev, staging, or prod (default: dev)",
6412
+ "dev"
6413
+ ).option("-b, --bucket <name>", "[deprecated] ignored").option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
5600
6414
  program.command("clone").description("Clone theme source code via the website-api").argument("<theme-name>", "Theme to clone").option(
5601
6415
  "-v, --version <version>",
5602
6416
  "Theme version (default: latest)",
5603
6417
  "latest"
5604
- ).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);
6418
+ ).option("-n, --name <name>", "New theme name (skips interactive prompt)").option("-o, --output <dir>", "Output directory").option(
6419
+ "--env <env>",
6420
+ "Target environment: dev, staging, or prod (default: dev)",
6421
+ "dev"
6422
+ ).option("-b, --bucket <name>", "[deprecated] ignored").option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
5605
6423
  program.command("config").description("Configure OneX CLI credentials (AWS, API keys)").action(configCommand);
5606
- program.command("login").description("Login to OneX platform").action(loginCommand);
5607
- program.command("logout").description("Logout from OneX platform").action(logoutCommand);
5608
- program.command("whoami").description("Show current logged-in developer").action(whoamiCommand);
6424
+ program.command("login").description("Login to OneX platform").option(
6425
+ "--env <env>",
6426
+ "Target environment: dev, staging, or prod (default: dev)",
6427
+ "dev"
6428
+ ).action(loginCommand);
6429
+ program.command("logout").description("Logout from OneX platform").option(
6430
+ "--env <env>",
6431
+ "Target environment: dev, staging, or prod (default: dev)",
6432
+ "dev"
6433
+ ).action(logoutCommand);
6434
+ program.command("whoami").description("Show current logged-in developer").option(
6435
+ "--env <env>",
6436
+ "Target environment: dev, staging, or prod (default: dev)",
6437
+ "dev"
6438
+ ).action(whoamiCommand);
5609
6439
  var mcpCmd = program.command("mcp").description("Manage MCP server registration and AI-context files");
5610
6440
  mcpCmd.command("setup").description(
5611
6441
  "Install .mcp.json + CLAUDE.md + AGENTS.md + .cursorrules into the current theme"
@@ -5617,6 +6447,19 @@ mcpCmd.command("doctor").description("Diagnose MCP setup in the current theme di
5617
6447
  program.command("publish").description("Build, scan, and publish theme to marketplace (requires login)").option("-t, --theme <path>", "Theme directory path").option(
5618
6448
  "--bump <type>",
5619
6449
  "Auto-bump version before publish (patch|minor|major)"
6450
+ ).option(
6451
+ "--env <env>",
6452
+ "Target environment: dev, staging, or prod (default: dev)",
6453
+ "dev"
6454
+ ).option(
6455
+ "--dry-run",
6456
+ "Build locally and print the schema-diff classification without publishing"
6457
+ ).option(
6458
+ "--confirm-defaults",
6459
+ "Confirm that changed section/block defaults should propagate to live sites"
6460
+ ).option(
6461
+ "--force",
6462
+ "Publish even when the diff gate detects a breaking change"
5620
6463
  ).action(publishCommand);
5621
6464
  program.configureOutput({
5622
6465
  writeErr: (str) => process.stderr.write(chalk4.red(str))