@onexapis/cli 1.1.65 → 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,15 +2000,16 @@ function getValidCategories() {
1763
2000
  "contact"
1764
2001
  ];
1765
2002
  }
1766
- var AUTH_DIR = path9.join(os.homedir(), ".onexthm");
2003
+ var AUTH_DIR = path11.join(os.homedir(), ".onexthm");
1767
2004
  var ENV_URLS = {
1768
2005
  dev: "https://platform-dev.onexeos.com",
1769
- prod: "https://platform-staging.onexeos.com"
2006
+ staging: "https://platform-staging.onexeos.com",
2007
+ prod: "https://platform-apis.onexeos.com"
1770
2008
  };
1771
2009
  function getAuthFile(env = "dev") {
1772
- const newFile = path9.join(AUTH_DIR, `auth-${env}.json`);
2010
+ const newFile = path11.join(AUTH_DIR, `auth-${env}.json`);
1773
2011
  if (env === "dev") {
1774
- const legacyFile = path9.join(AUTH_DIR, "auth.json");
2012
+ const legacyFile = path11.join(AUTH_DIR, "auth.json");
1775
2013
  if (fs.existsSync(legacyFile) && !fs.existsSync(newFile)) {
1776
2014
  try {
1777
2015
  fs.moveSync(legacyFile, newFile);
@@ -1922,7 +2160,7 @@ async function initCommand(projectName, options = {}) {
1922
2160
  if (!validateThemeName(kebabName)) {
1923
2161
  return "Invalid project name. Use lowercase letters, numbers, and hyphens only.";
1924
2162
  }
1925
- if (fs3.existsSync(path9.join(process.cwd(), kebabName))) {
2163
+ if (fs3.existsSync(path11.join(process.cwd(), kebabName))) {
1926
2164
  return `Directory "${kebabName}" already exists`;
1927
2165
  }
1928
2166
  return true;
@@ -1933,7 +2171,7 @@ async function initCommand(projectName, options = {}) {
1933
2171
  } else {
1934
2172
  name = toKebabCase(projectName);
1935
2173
  }
1936
- const projectPath = path9.join(process.cwd(), name);
2174
+ const projectPath = path11.join(process.cwd(), name);
1937
2175
  if (fs3.existsSync(projectPath)) {
1938
2176
  logger.error(`Directory "${name}" already exists.`);
1939
2177
  process.exit(1);
@@ -2049,7 +2287,7 @@ async function initCommand(projectName, options = {}) {
2049
2287
  description,
2050
2288
  author
2051
2289
  );
2052
- const mcpJsonPath = path9.join(projectPath, ".mcp.json");
2290
+ const mcpJsonPath = path11.join(projectPath, ".mcp.json");
2053
2291
  if (fs3.existsSync(mcpJsonPath)) {
2054
2292
  let mcpContent = fs3.readFileSync(mcpJsonPath, "utf-8");
2055
2293
  if (figmaApiKey) {
@@ -2129,7 +2367,7 @@ async function initCommand(projectName, options = {}) {
2129
2367
  }
2130
2368
  }
2131
2369
  async function renameThemeInFiles(projectPath, themeName, displayName, description, author) {
2132
- const configPath = path9.join(projectPath, "theme.config.ts");
2370
+ const configPath = path11.join(projectPath, "theme.config.ts");
2133
2371
  if (fs3.existsSync(configPath)) {
2134
2372
  let content = fs3.readFileSync(configPath, "utf-8");
2135
2373
  content = content.replace(
@@ -2142,7 +2380,7 @@ async function renameThemeInFiles(projectPath, themeName, displayName, descripti
2142
2380
  );
2143
2381
  fs3.writeFileSync(configPath, content, "utf-8");
2144
2382
  }
2145
- const pkgPath = path9.join(projectPath, "package.json");
2383
+ const pkgPath = path11.join(projectPath, "package.json");
2146
2384
  if (fs3.existsSync(pkgPath)) {
2147
2385
  let content = fs3.readFileSync(pkgPath, "utf-8");
2148
2386
  content = content.replace(
@@ -2164,10 +2402,10 @@ async function createSectionCommand(name, options) {
2164
2402
  ensureOneXProject();
2165
2403
  if (!options.theme) {
2166
2404
  const isStandaloneTheme = ["theme.config.ts", "bundle-entry.ts"].some(
2167
- (f) => fs.existsSync(path9.join(process.cwd(), f))
2405
+ (f) => fs.existsSync(path11.join(process.cwd(), f))
2168
2406
  );
2169
2407
  if (isStandaloneTheme) {
2170
- options.theme = path9.basename(process.cwd());
2408
+ options.theme = path11.basename(process.cwd());
2171
2409
  }
2172
2410
  }
2173
2411
  const sectionName = toKebabCase(name);
@@ -2230,35 +2468,35 @@ async function createSectionCommand(name, options) {
2230
2468
  };
2231
2469
  logger.startSpinner("Creating section files...");
2232
2470
  try {
2233
- const themePath = path9.join(getThemesDir(), themeName);
2234
- const sectionPath = path9.join(themePath, "sections", sectionName);
2471
+ const themePath = path11.join(getThemesDir(), themeName);
2472
+ const sectionPath = path11.join(themePath, "sections", sectionName);
2235
2473
  const schemaContent = generateSectionSchema(data);
2236
2474
  await writeFile(
2237
- path9.join(sectionPath, `${sectionName}.schema.ts`),
2475
+ path11.join(sectionPath, `${sectionName}.schema.ts`),
2238
2476
  schemaContent
2239
2477
  );
2240
2478
  if (createTemplate) {
2241
2479
  const templateContent = generateSectionTemplate(data);
2242
2480
  await writeFile(
2243
- path9.join(sectionPath, `${sectionName}-default.tsx`),
2481
+ path11.join(sectionPath, `${sectionName}-default.tsx`),
2244
2482
  templateContent
2245
2483
  );
2246
2484
  }
2247
2485
  const indexContent = generateSectionIndex(data, createTemplate);
2248
- await writeFile(path9.join(sectionPath, "index.ts"), indexContent);
2486
+ await writeFile(path11.join(sectionPath, "index.ts"), indexContent);
2249
2487
  logger.stopSpinner(true, "Section files created successfully!");
2250
2488
  logger.newLine();
2251
2489
  logger.section("Next steps:");
2252
2490
  logger.log(
2253
- ` 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`))}`
2254
2492
  );
2255
2493
  if (createTemplate) {
2256
2494
  logger.log(
2257
- ` 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`))}`
2258
2496
  );
2259
2497
  }
2260
2498
  logger.log(
2261
- ` 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"))}`
2262
2500
  );
2263
2501
  logger.newLine();
2264
2502
  logger.success("Section created successfully!");
@@ -2406,10 +2644,10 @@ async function createBlockCommand(name, options) {
2406
2644
  ensureOneXProject();
2407
2645
  if (!options.theme) {
2408
2646
  const isStandaloneTheme = ["theme.config.ts", "bundle-entry.ts"].some(
2409
- (f) => fs.existsSync(path9.join(process.cwd(), f))
2647
+ (f) => fs.existsSync(path11.join(process.cwd(), f))
2410
2648
  );
2411
2649
  if (isStandaloneTheme) {
2412
- options.theme = path9.basename(process.cwd());
2650
+ options.theme = path11.basename(process.cwd());
2413
2651
  }
2414
2652
  }
2415
2653
  const blockName = toKebabCase(name);
@@ -2484,24 +2722,24 @@ async function createBlockCommand(name, options) {
2484
2722
  };
2485
2723
  logger.startSpinner("Creating block files...");
2486
2724
  try {
2487
- 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);
2488
2726
  const schemaContent = generateBlockSchema(data);
2489
2727
  await writeFile(
2490
- path9.join(blockPath, `${blockName}.schema.ts`),
2728
+ path11.join(blockPath, `${blockName}.schema.ts`),
2491
2729
  schemaContent
2492
2730
  );
2493
2731
  const componentContent = generateBlockComponent(data);
2494
- await writeFile(path9.join(blockPath, `${blockName}.tsx`), componentContent);
2732
+ await writeFile(path11.join(blockPath, `${blockName}.tsx`), componentContent);
2495
2733
  const indexContent = generateBlockIndex(data);
2496
- await writeFile(path9.join(blockPath, "index.ts"), indexContent);
2734
+ await writeFile(path11.join(blockPath, "index.ts"), indexContent);
2497
2735
  logger.stopSpinner(true, "Block files created successfully!");
2498
2736
  logger.newLine();
2499
2737
  logger.section("Next steps:");
2500
2738
  logger.log(
2501
- ` 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`))}`
2502
2740
  );
2503
2741
  logger.log(
2504
- ` 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`))}`
2505
2743
  );
2506
2744
  logger.log(
2507
2745
  ` 3. Register in block registry: src/lib/registry/block-registry.ts`
@@ -2679,31 +2917,31 @@ async function createComponentCommand(name, options) {
2679
2917
  };
2680
2918
  logger.startSpinner("Creating component files...");
2681
2919
  try {
2682
- const componentPath = path9.join(
2920
+ const componentPath = path11.join(
2683
2921
  getFeaturesDir(),
2684
2922
  "components",
2685
2923
  componentName
2686
2924
  );
2687
2925
  const schemaContent = generateComponentSchema(data);
2688
2926
  await writeFile(
2689
- path9.join(componentPath, `${componentName}.schema.ts`),
2927
+ path11.join(componentPath, `${componentName}.schema.ts`),
2690
2928
  schemaContent
2691
2929
  );
2692
2930
  const componentContent = generateComponent(data);
2693
2931
  await writeFile(
2694
- path9.join(componentPath, `${componentName}.tsx`),
2932
+ path11.join(componentPath, `${componentName}.tsx`),
2695
2933
  componentContent
2696
2934
  );
2697
2935
  const indexContent = generateComponentIndex(data);
2698
- await writeFile(path9.join(componentPath, "index.ts"), indexContent);
2936
+ await writeFile(path11.join(componentPath, "index.ts"), indexContent);
2699
2937
  logger.stopSpinner(true, "Component files created successfully!");
2700
2938
  logger.newLine();
2701
2939
  logger.section("Next steps:");
2702
2940
  logger.log(
2703
- ` 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`))}`
2704
2942
  );
2705
2943
  logger.log(
2706
- ` 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`))}`
2707
2945
  );
2708
2946
  logger.log(
2709
2947
  ` 3. Register in component registry: src/lib/registry/component-registry.ts`
@@ -2860,13 +3098,13 @@ async function listSections(themeFilter) {
2860
3098
  return;
2861
3099
  }
2862
3100
  for (const theme of themes) {
2863
- const sectionsDir = path9.join(getThemesDir(), theme, "sections");
3101
+ const sectionsDir = path11.join(getThemesDir(), theme, "sections");
2864
3102
  if (!fs.existsSync(sectionsDir)) {
2865
3103
  continue;
2866
3104
  }
2867
3105
  const sections = fs.readdirSync(sectionsDir).filter((name) => {
2868
- const sectionPath = path9.join(sectionsDir, name);
2869
- 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"));
2870
3108
  });
2871
3109
  if (sections.length > 0) {
2872
3110
  logger.log(chalk4.cyan(`
@@ -2880,11 +3118,11 @@ async function listSections(themeFilter) {
2880
3118
  }
2881
3119
  async function listBlocks(themeFilter) {
2882
3120
  logger.section("\u{1F9F1} Blocks");
2883
- const sharedBlocksDir = path9.join(getFeaturesDir(), "blocks");
3121
+ const sharedBlocksDir = path11.join(getFeaturesDir(), "blocks");
2884
3122
  if (fs.existsSync(sharedBlocksDir)) {
2885
3123
  const sharedBlocks = fs.readdirSync(sharedBlocksDir).filter((name) => {
2886
- const blockPath = path9.join(sharedBlocksDir, name);
2887
- 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"));
2888
3126
  });
2889
3127
  if (sharedBlocks.length > 0) {
2890
3128
  logger.log(chalk4.cyan("\n Shared:"));
@@ -2895,13 +3133,13 @@ async function listBlocks(themeFilter) {
2895
3133
  }
2896
3134
  const themes = themeFilter ? [themeFilter] : listThemes();
2897
3135
  for (const theme of themes) {
2898
- const blocksDir = path9.join(getThemesDir(), theme, "blocks");
3136
+ const blocksDir = path11.join(getThemesDir(), theme, "blocks");
2899
3137
  if (!fs.existsSync(blocksDir)) {
2900
3138
  continue;
2901
3139
  }
2902
3140
  const blocks = fs.readdirSync(blocksDir).filter((name) => {
2903
- const blockPath = path9.join(blocksDir, name);
2904
- 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"));
2905
3143
  });
2906
3144
  if (blocks.length > 0) {
2907
3145
  logger.log(chalk4.cyan(`
@@ -2915,14 +3153,14 @@ async function listBlocks(themeFilter) {
2915
3153
  }
2916
3154
  async function listComponents() {
2917
3155
  logger.section("\u2699\uFE0F Components");
2918
- const componentsDir = path9.join(getFeaturesDir(), "components");
3156
+ const componentsDir = path11.join(getFeaturesDir(), "components");
2919
3157
  if (!fs.existsSync(componentsDir)) {
2920
3158
  logger.warning("No components directory found");
2921
3159
  return;
2922
3160
  }
2923
3161
  const components = fs.readdirSync(componentsDir).filter((name) => {
2924
- const componentPath = path9.join(componentsDir, name);
2925
- 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"));
2926
3164
  });
2927
3165
  if (components.length === 0) {
2928
3166
  logger.warning("No components found");
@@ -2943,11 +3181,11 @@ async function listThemesInfo() {
2943
3181
  }
2944
3182
  logger.log("");
2945
3183
  for (const theme of themes) {
2946
- const themeDir = path9.join(getThemesDir(), theme);
3184
+ const themeDir = path11.join(getThemesDir(), theme);
2947
3185
  const candidates = ["theme.config.ts", "bundle-entry.ts", "manifest.ts"];
2948
3186
  let manifestContent = "";
2949
3187
  for (const candidate of candidates) {
2950
- const candidatePath = path9.join(themeDir, candidate);
3188
+ const candidatePath = path11.join(themeDir, candidate);
2951
3189
  if (fs.existsSync(candidatePath)) {
2952
3190
  manifestContent = fs.readFileSync(candidatePath, "utf-8");
2953
3191
  break;
@@ -2985,9 +3223,9 @@ async function validateCommand(options) {
2985
3223
  "theme.config.ts",
2986
3224
  "bundle-entry.ts",
2987
3225
  "manifest.ts"
2988
- ].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
3226
+ ].some((f) => fs.existsSync(path11.join(process.cwd(), f)));
2989
3227
  if (isThemeDir2) {
2990
- themeToValidate = path9.basename(process.cwd());
3228
+ themeToValidate = path11.basename(process.cwd());
2991
3229
  logger.info(`Validating current theme: ${themeToValidate}`);
2992
3230
  } else {
2993
3231
  logger.error(
@@ -2996,11 +3234,11 @@ async function validateCommand(options) {
2996
3234
  process.exit(1);
2997
3235
  }
2998
3236
  }
2999
- const themePath = path9.join(getThemesDir(), themeToValidate);
3237
+ const themePath = path11.join(getThemesDir(), themeToValidate);
3000
3238
  logger.startSpinner("Running validation checks...");
3001
3239
  const entryFiles = ["manifest.ts", "theme.config.ts", "bundle-entry.ts"];
3002
3240
  const foundEntry = entryFiles.find(
3003
- (f) => fs.existsSync(path9.join(themePath, f))
3241
+ (f) => fs.existsSync(path11.join(themePath, f))
3004
3242
  );
3005
3243
  if (!foundEntry) {
3006
3244
  issues.push({
@@ -3010,7 +3248,7 @@ async function validateCommand(options) {
3010
3248
  });
3011
3249
  } else if (foundEntry === "manifest.ts") {
3012
3250
  const manifestContent = fs.readFileSync(
3013
- path9.join(themePath, foundEntry),
3251
+ path11.join(themePath, foundEntry),
3014
3252
  "utf-8"
3015
3253
  );
3016
3254
  if (!manifestContent.includes("export const") && !manifestContent.includes("export default") && !manifestContent.includes("export interface")) {
@@ -3021,7 +3259,7 @@ async function validateCommand(options) {
3021
3259
  });
3022
3260
  }
3023
3261
  }
3024
- const configPath = path9.join(themePath, "theme.config.ts");
3262
+ const configPath = path11.join(themePath, "theme.config.ts");
3025
3263
  if (!fs.existsSync(configPath)) {
3026
3264
  issues.push({
3027
3265
  type: "warning",
@@ -3029,7 +3267,7 @@ async function validateCommand(options) {
3029
3267
  message: "Theme config file not found (recommended)"
3030
3268
  });
3031
3269
  }
3032
- const indexPath = path9.join(themePath, "index.ts");
3270
+ const indexPath = path11.join(themePath, "index.ts");
3033
3271
  if (!fs.existsSync(indexPath)) {
3034
3272
  issues.push({
3035
3273
  type: "warning",
@@ -3037,7 +3275,7 @@ async function validateCommand(options) {
3037
3275
  message: "Index file not found (recommended)"
3038
3276
  });
3039
3277
  }
3040
- const sectionsDir = path9.join(themePath, "sections");
3278
+ const sectionsDir = path11.join(themePath, "sections");
3041
3279
  if (!fs.existsSync(sectionsDir)) {
3042
3280
  issues.push({
3043
3281
  type: "warning",
@@ -3046,16 +3284,16 @@ async function validateCommand(options) {
3046
3284
  });
3047
3285
  } else {
3048
3286
  const sections = fs.readdirSync(sectionsDir).filter(
3049
- (name) => fs.statSync(path9.join(sectionsDir, name)).isDirectory()
3287
+ (name) => fs.statSync(path11.join(sectionsDir, name)).isDirectory()
3050
3288
  );
3051
3289
  for (const sectionName of sections) {
3052
- const sectionPath = path9.join(sectionsDir, sectionName);
3053
- const schemaFile = path9.join(sectionPath, `${sectionName}.schema.ts`);
3054
- 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(
3055
3293
  sectionPath,
3056
3294
  `${sectionName}-default.tsx`
3057
3295
  );
3058
- const indexFile = path9.join(sectionPath, "index.ts");
3296
+ const indexFile = path11.join(sectionPath, "index.ts");
3059
3297
  if (!fs.existsSync(schemaFile)) {
3060
3298
  issues.push({
3061
3299
  type: "error",
@@ -3079,14 +3317,14 @@ async function validateCommand(options) {
3079
3317
  }
3080
3318
  }
3081
3319
  }
3082
- const blocksDir = path9.join(themePath, "blocks");
3320
+ const blocksDir = path11.join(themePath, "blocks");
3083
3321
  if (fs.existsSync(blocksDir)) {
3084
- 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());
3085
3323
  for (const blockName of blocks) {
3086
- const blockPath = path9.join(blocksDir, blockName);
3087
- const schemaFile = path9.join(blockPath, `${blockName}.schema.ts`);
3088
- const componentFile = path9.join(blockPath, `${blockName}.tsx`);
3089
- 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");
3090
3328
  if (!fs.existsSync(schemaFile)) {
3091
3329
  issues.push({
3092
3330
  type: "error",
@@ -3112,13 +3350,13 @@ async function validateCommand(options) {
3112
3350
  }
3113
3351
  if (fs.existsSync(sectionsDir)) {
3114
3352
  const sections = fs.readdirSync(sectionsDir).filter(
3115
- (name) => fs.statSync(path9.join(sectionsDir, name)).isDirectory()
3353
+ (name) => fs.statSync(path11.join(sectionsDir, name)).isDirectory()
3116
3354
  );
3117
3355
  for (const sectionName of sections) {
3118
- const sectionPath = path9.join(sectionsDir, sectionName);
3356
+ const sectionPath = path11.join(sectionsDir, sectionName);
3119
3357
  const tsxFiles = fs.readdirSync(sectionPath).filter((f) => f.endsWith(".tsx") && !f.endsWith(".schema.ts"));
3120
3358
  for (const tsxFile of tsxFiles) {
3121
- const filePath = path9.join(sectionPath, tsxFile);
3359
+ const filePath = path11.join(sectionPath, tsxFile);
3122
3360
  const content = fs.readFileSync(filePath, "utf-8");
3123
3361
  const relPath = `sections/${sectionName}/${tsxFile}`;
3124
3362
  if (!content.includes('"use client"') && !content.includes("'use client'")) {
@@ -3166,12 +3404,12 @@ async function validateCommand(options) {
3166
3404
  }
3167
3405
  }
3168
3406
  }
3169
- const registryPath = path9.join(themePath, "sections-registry.ts");
3170
- 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");
3171
3409
  const registryContent = fs.existsSync(registryPath) ? fs.readFileSync(registryPath, "utf-8") : fs.existsSync(bundleEntryPath) ? fs.readFileSync(bundleEntryPath, "utf-8") : "";
3172
3410
  if (fs.existsSync(sectionsDir) && registryContent) {
3173
3411
  const sections = fs.readdirSync(sectionsDir).filter(
3174
- (name) => fs.statSync(path9.join(sectionsDir, name)).isDirectory()
3412
+ (name) => fs.statSync(path11.join(sectionsDir, name)).isDirectory()
3175
3413
  );
3176
3414
  for (const sectionName of sections) {
3177
3415
  if (!registryContent.includes(`sections/${sectionName}`) && !registryContent.includes(`"${sectionName}"`)) {
@@ -3194,7 +3432,7 @@ async function validateCommand(options) {
3194
3432
  });
3195
3433
  }
3196
3434
  }
3197
- const pagesDir = path9.join(themePath, "pages");
3435
+ const pagesDir = path11.join(themePath, "pages");
3198
3436
  if (fs.existsSync(pagesDir)) {
3199
3437
  const allSchemaTypeSet = new Set(
3200
3438
  schemaTypes.map((s) => s.schemaType || s.folderName)
@@ -3246,9 +3484,9 @@ async function validateCommand(options) {
3246
3484
  }
3247
3485
  async function loadSchemaTypes(themePath, sectionsDir) {
3248
3486
  const results = [];
3249
- 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());
3250
3488
  for (const sectionName of sections) {
3251
- const schemaFile = path9.join(
3489
+ const schemaFile = path11.join(
3252
3490
  sectionsDir,
3253
3491
  sectionName,
3254
3492
  `${sectionName}.schema.ts`
@@ -3286,7 +3524,7 @@ async function validatePageSectionTypes(pagesDir, validTypes) {
3286
3524
  const issues = [];
3287
3525
  const files = fs.readdirSync(pagesDir).filter((f) => f.match(/\.(ts|js)$/));
3288
3526
  for (const file of files) {
3289
- const content = fs.readFileSync(path9.join(pagesDir, file), "utf-8");
3527
+ const content = fs.readFileSync(path11.join(pagesDir, file), "utf-8");
3290
3528
  const pageName = file.replace(/\.(ts|js)$/, "");
3291
3529
  const sectionsMatch = content.match(/\bsections:\s*\[/);
3292
3530
  if (!sectionsMatch || sectionsMatch.index === void 0) continue;
@@ -3299,9 +3537,13 @@ async function validatePageSectionTypes(pagesDir, validTypes) {
3299
3537
  endIdx = i;
3300
3538
  }
3301
3539
  const sectionsBlock = content.slice(startIdx, endIdx);
3302
- const typeMatches = sectionsBlock.matchAll(/\btype:\s*["']([^"']+)["']/g);
3303
- 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) {
3304
3544
  const sectionType = match[1];
3545
+ if (COMPONENT_TYPES.has(sectionType)) continue;
3546
+ if (BLOCK_TYPES.has(sectionType)) continue;
3305
3547
  if (!validTypes.has(sectionType)) {
3306
3548
  issues.push({
3307
3549
  type: "error",
@@ -3354,6 +3596,64 @@ var FIELD_TYPES = /* @__PURE__ */ new Set([
3354
3596
  "inline_richtext",
3355
3597
  "repeater"
3356
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
+ ]);
3357
3657
 
3358
3658
  // src/commands/build.ts
3359
3659
  init_logger();
@@ -3364,14 +3664,14 @@ async function buildCommand(options) {
3364
3664
  if (options.theme) {
3365
3665
  themeName = options.theme;
3366
3666
  try {
3367
- const workspaceThemePath = path9.join(getThemesDir(), themeName);
3667
+ const workspaceThemePath = path11.join(getThemesDir(), themeName);
3368
3668
  if (fs.existsSync(workspaceThemePath)) {
3369
3669
  themePath = workspaceThemePath;
3370
3670
  } else {
3371
- themePath = path9.join(process.cwd(), themeName);
3671
+ themePath = path11.join(process.cwd(), themeName);
3372
3672
  }
3373
3673
  } catch {
3374
- themePath = path9.join(process.cwd(), themeName);
3674
+ themePath = path11.join(process.cwd(), themeName);
3375
3675
  }
3376
3676
  if (!fs.existsSync(themePath)) {
3377
3677
  logger.error(`Theme "${themeName}" not found.`);
@@ -3382,10 +3682,10 @@ async function buildCommand(options) {
3382
3682
  "theme.config.ts",
3383
3683
  "bundle-entry.ts",
3384
3684
  "manifest.ts"
3385
- ].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
3685
+ ].some((f) => fs.existsSync(path11.join(process.cwd(), f)));
3386
3686
  if (isThemeDir2) {
3387
3687
  themePath = process.cwd();
3388
- themeName = path9.basename(themePath);
3688
+ themeName = path11.basename(themePath);
3389
3689
  logger.info(`Building current theme: ${themeName}`);
3390
3690
  } else {
3391
3691
  logger.error(
@@ -3394,7 +3694,7 @@ async function buildCommand(options) {
3394
3694
  process.exit(1);
3395
3695
  }
3396
3696
  }
3397
- const packageJsonPath = path9.join(themePath, "package.json");
3697
+ const packageJsonPath = path11.join(themePath, "package.json");
3398
3698
  const hasPkgJson = fs.existsSync(packageJsonPath);
3399
3699
  if (!hasPkgJson) {
3400
3700
  logger.warning(
@@ -3450,9 +3750,9 @@ async function buildCommand(options) {
3450
3750
  logger.success("\u2713 Theme built successfully!");
3451
3751
  logger.newLine();
3452
3752
  logger.info(`Theme: ${themeName}`);
3453
- const distPath = path9.join(themePath, "dist");
3753
+ const distPath = path11.join(themePath, "dist");
3454
3754
  if (fs.existsSync(distPath)) {
3455
- logger.log(`Output: ${path9.relative(process.cwd(), distPath)}`);
3755
+ logger.log(`Output: ${path11.relative(process.cwd(), distPath)}`);
3456
3756
  const files = fs.readdirSync(distPath);
3457
3757
  logger.log(`Files: ${files.length}`);
3458
3758
  }
@@ -3508,7 +3808,7 @@ async function packageCommand(options) {
3508
3808
  let themeName;
3509
3809
  if (options.theme) {
3510
3810
  themeName = options.theme;
3511
- themePath = path9.join(getThemesDir(), themeName);
3811
+ themePath = path11.join(getThemesDir(), themeName);
3512
3812
  if (!fs.existsSync(themePath)) {
3513
3813
  logger.error(`Theme "${themeName}" not found.`);
3514
3814
  process.exit(1);
@@ -3518,10 +3818,10 @@ async function packageCommand(options) {
3518
3818
  "theme.config.ts",
3519
3819
  "bundle-entry.ts",
3520
3820
  "manifest.ts"
3521
- ].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
3821
+ ].some((f) => fs.existsSync(path11.join(process.cwd(), f)));
3522
3822
  if (isThemeDir2) {
3523
3823
  themePath = process.cwd();
3524
- themeName = path9.basename(themePath);
3824
+ themeName = path11.basename(themePath);
3525
3825
  logger.info(`Packaging current theme: ${themeName}`);
3526
3826
  } else {
3527
3827
  logger.error(
@@ -3530,7 +3830,7 @@ async function packageCommand(options) {
3530
3830
  process.exit(1);
3531
3831
  }
3532
3832
  }
3533
- const packageJsonPath = path9.join(themePath, "package.json");
3833
+ const packageJsonPath = path11.join(themePath, "package.json");
3534
3834
  let version2 = "1.0.0";
3535
3835
  if (fs.existsSync(packageJsonPath)) {
3536
3836
  const packageJson = await fs.readJson(packageJsonPath);
@@ -3540,7 +3840,7 @@ async function packageCommand(options) {
3540
3840
  logger.info(`Theme: ${themeName}`);
3541
3841
  logger.info(`Version: ${version2}`);
3542
3842
  logger.newLine();
3543
- const compiledThemePath = path9.join(
3843
+ const compiledThemePath = path11.join(
3544
3844
  process.cwd(),
3545
3845
  "themes",
3546
3846
  themeName,
@@ -3574,8 +3874,8 @@ async function packageCommand(options) {
3574
3874
  logger.newLine();
3575
3875
  logger.section("Step 2: Create Package");
3576
3876
  const packageName = options.name || `${themeName}-${version2}`;
3577
- const outputDir = options.output || path9.join(process.cwd(), "dist");
3578
- 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`);
3579
3879
  await fs.ensureDir(outputDir);
3580
3880
  logger.startSpinner("Creating zip archive...");
3581
3881
  try {
@@ -3588,11 +3888,11 @@ async function packageCommand(options) {
3588
3888
  logger.newLine();
3589
3889
  logger.info(`Package: ${packageName}.zip`);
3590
3890
  logger.log(`Size: ${sizeMB} MB`);
3591
- logger.log(`Location: ${path9.relative(process.cwd(), outputPath)}`);
3891
+ logger.log(`Location: ${path11.relative(process.cwd(), outputPath)}`);
3592
3892
  logger.newLine();
3593
3893
  logger.section("Next steps:");
3594
3894
  logger.log(
3595
- ` onexthm deploy --package ${path9.relative(process.cwd(), outputPath)}`
3895
+ ` onexthm deploy --package ${path11.relative(process.cwd(), outputPath)}`
3596
3896
  );
3597
3897
  } catch (error) {
3598
3898
  logger.stopSpinner(false, "Failed to create package");
@@ -3650,9 +3950,9 @@ async function deployCommand(options) {
3650
3950
  ensureOneXProject();
3651
3951
  let packagePath;
3652
3952
  if (options.package) {
3653
- packagePath = path9.resolve(options.package);
3953
+ packagePath = path11.resolve(options.package);
3654
3954
  } else if (options.theme) {
3655
- const distDir = path9.join(process.cwd(), "dist");
3955
+ const distDir = path11.join(process.cwd(), "dist");
3656
3956
  if (!fs.existsSync(distDir)) {
3657
3957
  logger.error("No dist/ directory found. Run 'onexthm package' first.");
3658
3958
  process.exit(1);
@@ -3667,7 +3967,7 @@ async function deployCommand(options) {
3667
3967
  process.exit(1);
3668
3968
  }
3669
3969
  packageFiles.sort().reverse();
3670
- packagePath = path9.join(distDir, packageFiles[0]);
3970
+ packagePath = path11.join(distDir, packageFiles[0]);
3671
3971
  } else {
3672
3972
  logger.error("Either --package or --theme must be specified.");
3673
3973
  logger.info("Examples:");
@@ -3681,11 +3981,11 @@ async function deployCommand(options) {
3681
3981
  }
3682
3982
  const stats = await fs.stat(packagePath);
3683
3983
  const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
3684
- const fileName = path9.basename(packagePath);
3984
+ const fileName = path11.basename(packagePath);
3685
3985
  logger.newLine();
3686
3986
  logger.info(`Package: ${fileName}`);
3687
3987
  logger.log(`Size: ${sizeMB} MB`);
3688
- logger.log(`Path: ${path9.relative(process.cwd(), packagePath)}`);
3988
+ logger.log(`Path: ${path11.relative(process.cwd(), packagePath)}`);
3689
3989
  logger.newLine();
3690
3990
  const apiUrl = options.apiUrl || process.env.ONEX_API_URL || "http://localhost:3001";
3691
3991
  const uploadEndpoint = `${apiUrl}/website-api/themes/upload`;
@@ -3842,8 +4142,8 @@ async function downloadBundleZip(apiUrl, themeId, version2) {
3842
4142
  async function createCompatibilityFiles(outputDir, manifest) {
3843
4143
  const entryFile = manifest.output?.entry || "bundle-entry.js";
3844
4144
  if (entryFile !== "bundle-entry.js" && entryFile.startsWith("bundle-entry-")) {
3845
- const hashedPath = path9.join(outputDir, entryFile);
3846
- const stablePath = path9.join(outputDir, "bundle-entry.js");
4145
+ const hashedPath = path11.join(outputDir, entryFile);
4146
+ const stablePath = path11.join(outputDir, "bundle-entry.js");
3847
4147
  if (await fs.pathExists(hashedPath)) {
3848
4148
  await fs.copy(hashedPath, stablePath);
3849
4149
  const mapPath = hashedPath + ".map";
@@ -3852,13 +4152,13 @@ async function createCompatibilityFiles(outputDir, manifest) {
3852
4152
  }
3853
4153
  }
3854
4154
  }
3855
- const sectionsRegistryPath = path9.join(outputDir, "sections-registry.js");
4155
+ const sectionsRegistryPath = path11.join(outputDir, "sections-registry.js");
3856
4156
  const content = `// Re-export all sections from bundle-entry
3857
4157
  // This file exists to maintain compatibility with the import path
3858
4158
  export * from './bundle-entry.js';
3859
4159
  `;
3860
4160
  await fs.writeFile(sectionsRegistryPath, content, "utf-8");
3861
- const pkgJsonPath = path9.join(outputDir, "package.json");
4161
+ const pkgJsonPath = path11.join(outputDir, "package.json");
3862
4162
  await fs.writeFile(pkgJsonPath, '{\n "type": "module"\n}\n', "utf-8");
3863
4163
  }
3864
4164
  function showDownloadFailureHelp(themeId, apiUrl) {
@@ -3957,7 +4257,7 @@ async function downloadCommand(options) {
3957
4257
  zip.extractAllTo(outputDir, true);
3958
4258
  const entries = zip.getEntries().filter((e) => !e.isDirectory);
3959
4259
  spinner.succeed(`Extracted ${entries.length} files to ${outputDir}`);
3960
- const manifestPath = path9.join(outputDir, "manifest.json");
4260
+ const manifestPath = path11.join(outputDir, "manifest.json");
3961
4261
  const manifest = await fs.readJson(manifestPath);
3962
4262
  await createCompatibilityFiles(outputDir, manifest);
3963
4263
  console.log();
@@ -4078,7 +4378,7 @@ async function renameTheme(themeDir, oldName, newName) {
4078
4378
  const oldPrefix = `${oldName}-`;
4079
4379
  const newPrefix = `${newName}-`;
4080
4380
  const newDisplayName = newName.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
4081
- const pkgPath = path9.join(themeDir, "package.json");
4381
+ const pkgPath = path11.join(themeDir, "package.json");
4082
4382
  if (await fs.pathExists(pkgPath)) {
4083
4383
  const pkg = await fs.readJson(pkgPath);
4084
4384
  pkg.name = `@onex-themes/${newName}`;
@@ -4094,7 +4394,7 @@ async function renameTheme(themeDir, oldName, newName) {
4094
4394
  }
4095
4395
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
4096
4396
  }
4097
- const configPath = path9.join(themeDir, "theme.config.ts");
4397
+ const configPath = path11.join(themeDir, "theme.config.ts");
4098
4398
  if (await fs.pathExists(configPath)) {
4099
4399
  let content = await fs.readFile(configPath, "utf-8");
4100
4400
  content = content.replace(/id:\s*"[^"]*"/, `id: "${newName}"`);
@@ -4104,7 +4404,7 @@ async function renameTheme(themeDir, oldName, newName) {
4104
4404
  );
4105
4405
  await fs.writeFile(configPath, content);
4106
4406
  }
4107
- const layoutPath = path9.join(themeDir, "theme.layout.ts");
4407
+ const layoutPath = path11.join(themeDir, "theme.layout.ts");
4108
4408
  if (await fs.pathExists(layoutPath)) {
4109
4409
  let content = await fs.readFile(layoutPath, "utf-8");
4110
4410
  content = content.replace(/id:\s*"[^"]*"/, `id: "${newName}"`);
@@ -4117,7 +4417,7 @@ async function renameTheme(themeDir, oldName, newName) {
4117
4417
  const oldDisplayName = oldName.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
4118
4418
  const tsFiles = await glob("**/*.ts", { cwd: themeDir, nodir: true });
4119
4419
  for (const file of tsFiles) {
4120
- const filePath = path9.join(themeDir, file);
4420
+ const filePath = path11.join(themeDir, file);
4121
4421
  let content = await fs.readFile(filePath, "utf-8");
4122
4422
  const original = content;
4123
4423
  content = content.replace(
@@ -4160,7 +4460,7 @@ async function cloneCommand(themeName, options) {
4160
4460
  }
4161
4461
  const spinner = ora("Initializing clone...").start();
4162
4462
  try {
4163
- const outputDir = options.output || path9.resolve(process.cwd(), newName);
4463
+ const outputDir = options.output || path11.resolve(process.cwd(), newName);
4164
4464
  if (await fs.pathExists(outputDir)) {
4165
4465
  spinner.fail(chalk4.red(`Directory already exists: ${outputDir}`));
4166
4466
  logger.info(
@@ -4206,7 +4506,7 @@ async function cloneCommand(themeName, options) {
4206
4506
  spinner.succeed(
4207
4507
  `Renamed theme: ${chalk4.gray(themeName)} \u2192 ${chalk4.cyan(newName)}`
4208
4508
  );
4209
- const envExamplePath = path9.join(outputDir, ".env.example");
4509
+ const envExamplePath = path11.join(outputDir, ".env.example");
4210
4510
  if (!await fs.pathExists(envExamplePath)) {
4211
4511
  await fs.writeFile(
4212
4512
  envExamplePath,
@@ -4219,7 +4519,7 @@ async function cloneCommand(themeName, options) {
4219
4519
  ].join("\n")
4220
4520
  );
4221
4521
  }
4222
- const mcpJsonPath = path9.join(outputDir, ".mcp.json");
4522
+ const mcpJsonPath = path11.join(outputDir, ".mcp.json");
4223
4523
  if (await fs.pathExists(mcpJsonPath)) {
4224
4524
  const { default: inquirerMod } = await import('inquirer');
4225
4525
  const { figmaApiKey } = await inquirerMod.prompt([
@@ -4244,7 +4544,7 @@ async function cloneCommand(themeName, options) {
4244
4544
  }
4245
4545
  if (options.install !== false) {
4246
4546
  const hasPkgJson = await fs.pathExists(
4247
- path9.join(outputDir, "package.json")
4547
+ path11.join(outputDir, "package.json")
4248
4548
  );
4249
4549
  if (hasPkgJson) {
4250
4550
  spinner.start("Installing dependencies...");
@@ -4272,7 +4572,7 @@ async function cloneCommand(themeName, options) {
4272
4572
  console.log(chalk4.cyan(" Files: ") + chalk4.white(entries.length));
4273
4573
  console.log();
4274
4574
  console.log(chalk4.cyan("Next steps:"));
4275
- console.log(chalk4.gray(` cd ${path9.relative(process.cwd(), outputDir)}`));
4575
+ console.log(chalk4.gray(` cd ${path11.relative(process.cwd(), outputDir)}`));
4276
4576
  console.log(
4277
4577
  chalk4.gray(" cp .env.example .env # then add your Company ID")
4278
4578
  );
@@ -4299,14 +4599,14 @@ async function devCommand(options) {
4299
4599
  if (options.theme) {
4300
4600
  themeName = options.theme;
4301
4601
  try {
4302
- const workspaceThemePath = path9.join(getThemesDir(), themeName);
4602
+ const workspaceThemePath = path11.join(getThemesDir(), themeName);
4303
4603
  if (fs.existsSync(workspaceThemePath)) {
4304
4604
  themePath = workspaceThemePath;
4305
4605
  } else {
4306
- themePath = path9.join(process.cwd(), themeName);
4606
+ themePath = path11.join(process.cwd(), themeName);
4307
4607
  }
4308
4608
  } catch {
4309
- themePath = path9.join(process.cwd(), themeName);
4609
+ themePath = path11.join(process.cwd(), themeName);
4310
4610
  }
4311
4611
  if (!fs.existsSync(themePath)) {
4312
4612
  logger.error(`Theme "${themeName}" not found.`);
@@ -4317,10 +4617,10 @@ async function devCommand(options) {
4317
4617
  "theme.config.ts",
4318
4618
  "bundle-entry.ts",
4319
4619
  "manifest.ts"
4320
- ].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
4620
+ ].some((f) => fs.existsSync(path11.join(process.cwd(), f)));
4321
4621
  if (isThemeDir2) {
4322
4622
  themePath = process.cwd();
4323
- themeName = path9.basename(themePath);
4623
+ themeName = path11.basename(themePath);
4324
4624
  } else {
4325
4625
  logger.error(
4326
4626
  "Not in a theme directory and no --theme specified. Run from theme root or use --theme flag."
@@ -4389,9 +4689,9 @@ async function devCommand(options) {
4389
4689
  watcher.close();
4390
4690
  await context2.dispose();
4391
4691
  server.close();
4392
- const shimPath = path9.join(outputDir, ".process-shim.js");
4692
+ const shimPath = path11.join(outputDir, ".process-shim.js");
4393
4693
  try {
4394
- await fs8.unlink(shimPath);
4694
+ await fs9.unlink(shimPath);
4395
4695
  } catch {
4396
4696
  }
4397
4697
  process.exit(0);
@@ -4400,8 +4700,8 @@ async function devCommand(options) {
4400
4700
 
4401
4701
  // src/commands/config.ts
4402
4702
  init_logger();
4403
- var CONFIG_DIR = path9.join(os.homedir(), ".onexthm");
4404
- 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");
4405
4705
  var CONFIG_ENTRIES = [
4406
4706
  {
4407
4707
  key: "AWS_ACCESS_KEY_ID",
@@ -4669,91 +4969,357 @@ async function whoamiCommand(options = {}) {
4669
4969
 
4670
4970
  // src/commands/publish.ts
4671
4971
  init_logger();
4672
- var MIME_MAP = {
4673
- ".png": "image/png",
4674
- ".jpg": "image/jpeg",
4675
- ".jpeg": "image/jpeg",
4676
- ".gif": "image/gif",
4677
- ".webp": "image/webp",
4678
- ".avif": "image/avif",
4679
- ".svg": "image/svg+xml",
4680
- ".ico": "image/x-icon",
4681
- ".bmp": "image/bmp",
4682
- ".woff": "font/woff",
4683
- ".woff2": "font/woff2",
4684
- ".ttf": "font/ttf",
4685
- ".otf": "font/otf",
4686
- ".eot": "application/vnd.ms-fontobject",
4687
- ".mp4": "video/mp4",
4688
- ".webm": "video/webm",
4689
- ".mov": "video/quicktime",
4690
- ".ogg": "video/ogg",
4691
- ".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
4692
5025
  };
4693
- var HASH_LEN = 8;
4694
- var VIDEO_EXTENSIONS = [
4695
- ".mp4",
4696
- ".webm",
4697
- ".ogg",
4698
- ".mov",
4699
- ".avi",
4700
- ".mkv"
4701
- ];
4702
- function isVideoAsset(filePath) {
4703
- const lower = filePath.toLowerCase();
4704
- 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];
4705
5037
  }
4706
- function mimeFor(filename) {
4707
- const ext = path9.extname(filename).toLowerCase();
4708
- return MIME_MAP[ext] || "application/octet-stream";
5038
+ function maxSeverity(a, b) {
5039
+ return SEVERITY[a] >= SEVERITY[b] ? a : b;
4709
5040
  }
4710
- async function sha256Prefix(absPath, len) {
4711
- const buf = await fs.readFile(absPath);
4712
- 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 };
4713
5047
  }
4714
- function insertHashIntoName(relPath, hash) {
4715
- const dir = path9.posix.dirname(relPath);
4716
- const base = path9.posix.basename(relPath);
4717
- const ext = path9.posix.extname(base);
4718
- const stem = ext ? base.slice(0, -ext.length) : base;
4719
- const hashed = `${stem}-${hash}${ext}`;
4720
- 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;
4721
5079
  }
4722
- async function scanThemeAssets(distDir) {
4723
- const assetsDir = path9.join(distDir, "theme-assets");
4724
- if (!await fs.pathExists(assetsDir)) return [];
4725
- const files = await glob("**/*", {
4726
- cwd: assetsDir,
4727
- nodir: true,
4728
- dot: false
4729
- });
4730
- const results = [];
4731
- for (const rel of files) {
4732
- const absPath = path9.join(assetsDir, rel);
4733
- const stat = await fs.stat(absPath);
4734
- if (!stat.isFile()) continue;
4735
- const originalPath = rel.split(path9.sep).join("/");
4736
- const hash = await sha256Prefix(absPath, HASH_LEN);
4737
- const hashedPath = insertHashIntoName(originalPath, hash);
4738
- const contentType = mimeFor(rel);
4739
- results.push({
4740
- originalPath,
4741
- hashedPath,
4742
- hash,
4743
- size: stat.size,
4744
- contentType,
4745
- 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."
4746
5087
  });
4747
5088
  }
4748
- results.sort((a, b) => a.originalPath.localeCompare(b.originalPath));
4749
- 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);
4750
5102
  }
4751
- function buildAssetMap(entries) {
4752
- const map = {};
4753
- for (const e of entries) {
4754
- 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
+ }
4755
5297
  }
4756
- 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;
4757
5323
  }
4758
5324
 
4759
5325
  // src/commands/publish.ts
@@ -4772,13 +5338,13 @@ async function publishCommand(options) {
4772
5338
  logger.info(`Logged in as: ${tokens.user.email}`);
4773
5339
  let themePath;
4774
5340
  if (options.theme) {
4775
- themePath = path9.resolve(options.theme);
5341
+ themePath = path11.resolve(options.theme);
4776
5342
  } else {
4777
5343
  const isThemeDir2 = [
4778
5344
  "theme.config.ts",
4779
5345
  "bundle-entry.ts",
4780
5346
  "manifest.ts"
4781
- ].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
5347
+ ].some((f) => fs.existsSync(path11.join(process.cwd(), f)));
4782
5348
  if (isThemeDir2) {
4783
5349
  themePath = process.cwd();
4784
5350
  } else {
@@ -4788,13 +5354,13 @@ async function publishCommand(options) {
4788
5354
  process.exit(1);
4789
5355
  }
4790
5356
  }
4791
- const pkgPath = path9.join(themePath, "package.json");
5357
+ const pkgPath = path11.join(themePath, "package.json");
4792
5358
  if (!fs.existsSync(pkgPath)) {
4793
5359
  logger.error("No package.json found in theme directory");
4794
5360
  process.exit(1);
4795
5361
  }
4796
5362
  const pkg = fs.readJsonSync(pkgPath);
4797
- const themeId = pkg.name?.replace("@onex-themes/", "") || path9.basename(themePath);
5363
+ const themeId = pkg.name?.replace("@onex-themes/", "") || path11.basename(themePath);
4798
5364
  if (options.bump) {
4799
5365
  const currentVersion = pkg.version || "1.0.0";
4800
5366
  const newVersion = semver.inc(currentVersion, options.bump);
@@ -4818,57 +5384,62 @@ async function publishCommand(options) {
4818
5384
  logger.info(`Version: ${version2}`);
4819
5385
  logger.newLine();
4820
5386
  const apiUrl = getApiUrl(env);
4821
- logger.startSpinner("Registering theme...");
4822
- try {
4823
- const regResponse = await authenticatedFetch(
4824
- `${apiUrl}/website-api/themes/register`,
4825
- {
4826
- method: "POST",
4827
- body: JSON.stringify({
4828
- themeId,
4829
- name: pkg.displayName || themeId,
4830
- description: pkg.description || "",
4831
- email: tokens.user.email,
4832
- author: typeof pkg.author === "string" ? pkg.author : pkg.author?.name || tokens.user.name || "",
4833
- category: pkg.onex?.category || "MINIMAL",
4834
- tags: pkg.keywords || [],
4835
- thumbnail_url: pkg.onex?.thumbnail || ""
4836
- })
4837
- },
4838
- env
4839
- );
4840
- const regData = await regResponse.json();
4841
- const regBody = regData.statusCode ? regData.body : regData;
4842
- if (!regResponse.ok) {
4843
- const errMsg = regBody.error || regBody.message || "Registration failed";
4844
- if (!errMsg.includes("already registered")) {
4845
- logger.stopSpinner(false, "Registration failed");
4846
- logger.error(errMsg);
4847
- process.exit(1);
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
+ }
4848
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);
4849
5424
  }
4850
- logger.stopSpinner(true, regBody.message || "Theme registered");
4851
- } catch (error) {
4852
- logger.stopSpinner(false, "Registration failed");
4853
- logger.error(error instanceof Error ? error.message : "Connection failed");
4854
- process.exit(1);
4855
5425
  }
4856
- logger.startSpinner("Checking version availability...");
4857
- try {
4858
- const checkResponse = await authenticatedFetch(
4859
- `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(version2)}/exists`,
4860
- { method: "GET" },
4861
- env
4862
- );
4863
- const checkData = await checkResponse.json();
4864
- const checkBody = checkData.statusCode ? checkData.body : checkData;
4865
- if (checkBody.exists) {
4866
- logger.stopSpinner(false, "Version already published");
4867
- const patchVer = semver.inc(version2, "patch") || "?";
4868
- const minorVer = semver.inc(version2, "minor") || "?";
4869
- const majorVer = semver.inc(version2, "major") || "?";
4870
- logger.error(
4871
- `
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
+ `
4872
5443
  Version ${version2} of "${themeId}" is already published and cannot be overwritten.
4873
5444
 
4874
5445
  To publish a new version:
@@ -4879,12 +5450,16 @@ Or use the --bump flag:
4879
5450
  onexthm publish --bump patch (${version2} -> ${patchVer})
4880
5451
  onexthm publish --bump minor (${version2} -> ${minorVer})
4881
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)"
4882
5461
  );
4883
- process.exit(1);
4884
5462
  }
4885
- logger.stopSpinner(true, `Version ${version2} is available`);
4886
- } catch (error) {
4887
- logger.stopSpinner(true, "Version check skipped (endpoint not available)");
4888
5463
  }
4889
5464
  logger.startSpinner("Building theme...");
4890
5465
  try {
@@ -4901,7 +5476,19 @@ Or use the --bump flag:
4901
5476
  logger.error(error instanceof Error ? error.message : "Build error");
4902
5477
  process.exit(1);
4903
5478
  }
4904
- 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
+ }
4905
5492
  let assetEntries = [];
4906
5493
  try {
4907
5494
  assetEntries = await scanThemeAssets(distDir);
@@ -4938,7 +5525,7 @@ Or use the --bump flag:
4938
5525
  for (const [originalPath, url] of Object.entries(videoUrls)) {
4939
5526
  assetMap[originalPath] = url;
4940
5527
  }
4941
- const assetMapPath = path9.join(distDir, "asset-map.json");
5528
+ const assetMapPath = path11.join(distDir, "asset-map.json");
4942
5529
  await fs.writeFile(assetMapPath, JSON.stringify(assetMap, null, 2));
4943
5530
  } catch (error) {
4944
5531
  logger.error(
@@ -5061,7 +5648,7 @@ Or use the --bump flag:
5061
5648
  logger.error("Build the theme first: onexthm build");
5062
5649
  process.exit(1);
5063
5650
  }
5064
- const bundleZipPath = path9.join(themePath, "dist", "bundle.zip");
5651
+ const bundleZipPath = path11.join(themePath, "dist", "bundle.zip");
5065
5652
  await createZip(distDir, bundleZipPath, [
5066
5653
  "bundle.zip",
5067
5654
  "source.zip",
@@ -5088,7 +5675,7 @@ Or use the --bump flag:
5088
5675
  }
5089
5676
  logger.startSpinner("Uploading source...");
5090
5677
  try {
5091
- const sourceZipPath = path9.join(themePath, "dist", "source.zip");
5678
+ const sourceZipPath = path11.join(themePath, "dist", "source.zip");
5092
5679
  await createZip(themePath, sourceZipPath, [
5093
5680
  "node_modules",
5094
5681
  "dist",
@@ -5174,7 +5761,7 @@ async function uploadThumbnail(apiUrl, themeId, themePath, distDir, env = "dev")
5174
5761
  let imageBase64 = null;
5175
5762
  let mimeType = "image/png";
5176
5763
  for (const { file, mime } of THUMBNAIL_CANDIDATES) {
5177
- const candidate = path9.join(themePath, file);
5764
+ const candidate = path11.join(themePath, file);
5178
5765
  if (fs.existsSync(candidate)) {
5179
5766
  const buf = fs.readFileSync(candidate);
5180
5767
  imageBase64 = `data:${mime};base64,${buf.toString("base64")}`;
@@ -5276,7 +5863,7 @@ async function screenshotHomePage(themePath, distDir) {
5276
5863
  const { compilePreviewRuntime: compilePreviewRuntime2 } = await Promise.resolve().then(() => (init_compile_theme(), compile_theme_exports));
5277
5864
  const { createDevServer: createDevServer2 } = await Promise.resolve().then(() => (init_dev_server(), dev_server_exports));
5278
5865
  const previewRuntimePath = await compilePreviewRuntime2(themePath);
5279
- const themeName = path9.basename(themePath);
5866
+ const themeName = path11.basename(themePath);
5280
5867
  const port = await findFreePort(4500);
5281
5868
  const server = createDevServer2({
5282
5869
  port,
@@ -5333,7 +5920,7 @@ async function findFreePort(start) {
5333
5920
  });
5334
5921
  }
5335
5922
  async function uploadVideoMultipart(apiUrl, themeId, video, env = "dev") {
5336
- const fileName = path9.basename(video.originalPath);
5923
+ const fileName = path11.basename(video.originalPath);
5337
5924
  const videoInitUrl = `${apiUrl}/media/videos/multipart/init`;
5338
5925
  const videoInitBody = {
5339
5926
  file_name: fileName,
@@ -5462,6 +6049,91 @@ async function createZip(sourceDir, outputPath, exclude) {
5462
6049
  archive.finalize();
5463
6050
  });
5464
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
+ }
5465
6137
 
5466
6138
  // src/commands/mcp.ts
5467
6139
  init_logger();
@@ -5473,18 +6145,18 @@ var AI_CONTEXT_FILES = [
5473
6145
  ".mcp.json"
5474
6146
  ];
5475
6147
  function resolveTargetDir(opts) {
5476
- return path9.resolve(opts.cwd ?? process.cwd());
6148
+ return path11.resolve(opts.cwd ?? process.cwd());
5477
6149
  }
5478
6150
  function resolveDefaultTemplateDir() {
5479
- return path9.join(getTemplatesDir(), "default");
6151
+ return path11.join(getTemplatesDir(), "default");
5480
6152
  }
5481
6153
  function isThemeDir(dir) {
5482
- 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"));
5483
6155
  }
5484
6156
  function inspectFiles(templateDir, targetDir) {
5485
6157
  return AI_CONTEXT_FILES.map((name) => {
5486
- const templatePath = path9.join(templateDir, name);
5487
- const targetPath = path9.join(targetDir, name);
6158
+ const templatePath = path11.join(templateDir, name);
6159
+ const targetPath = path11.join(targetDir, name);
5488
6160
  const exists = fs.existsSync(targetPath);
5489
6161
  let identical = false;
5490
6162
  if (exists && fs.existsSync(templatePath)) {
@@ -5627,7 +6299,7 @@ async function mcpDoctorCommand(options = {}) {
5627
6299
  return;
5628
6300
  }
5629
6301
  logger.success("theme.config.ts present");
5630
- const mcpJsonPath = path9.join(targetDir, ".mcp.json");
6302
+ const mcpJsonPath = path11.join(targetDir, ".mcp.json");
5631
6303
  if (!fs.existsSync(mcpJsonPath)) {
5632
6304
  logger.error(".mcp.json missing \u2014 run `onexthm mcp setup`");
5633
6305
  } else {
@@ -5665,7 +6337,7 @@ async function mcpDoctorCommand(options = {}) {
5665
6337
  logger.success(`${s.name} up to date`);
5666
6338
  }
5667
6339
  }
5668
- const registryPath = path9.join(targetDir, "sections-registry.ts");
6340
+ const registryPath = path11.join(targetDir, "sections-registry.ts");
5669
6341
  if (fs.existsSync(registryPath)) {
5670
6342
  logger.success("sections-registry.ts present");
5671
6343
  } else {
@@ -5677,22 +6349,22 @@ async function mcpDoctorCommand(options = {}) {
5677
6349
 
5678
6350
  // src/cli.ts
5679
6351
  dotenv.config({
5680
- path: path9.join(process.cwd(), ".env.local"),
6352
+ path: path11.join(process.cwd(), ".env.local"),
5681
6353
  override: true
5682
6354
  });
5683
- dotenv.config({ path: path9.join(process.cwd(), ".env") });
6355
+ dotenv.config({ path: path11.join(process.cwd(), ".env") });
5684
6356
  try {
5685
6357
  const projectRoot = getProjectRoot();
5686
- if (path9.resolve(projectRoot) !== path9.resolve(process.cwd())) {
6358
+ if (path11.resolve(projectRoot) !== path11.resolve(process.cwd())) {
5687
6359
  dotenv.config({
5688
- path: path9.join(projectRoot, ".env.local")
6360
+ path: path11.join(projectRoot, ".env.local")
5689
6361
  });
5690
- dotenv.config({ path: path9.join(projectRoot, ".env") });
6362
+ dotenv.config({ path: path11.join(projectRoot, ".env") });
5691
6363
  }
5692
6364
  } catch {
5693
6365
  }
5694
6366
  dotenv.config({
5695
- path: path9.join(os.homedir(), ".onexthm", ".env"),
6367
+ path: path11.join(os.homedir(), ".onexthm", ".env"),
5696
6368
  quiet: true
5697
6369
  });
5698
6370
  var require2 = createRequire(import.meta.url);
@@ -5705,7 +6377,7 @@ program.command("init").description("Create a new OneX theme project").argument(
5705
6377
  "default"
5706
6378
  ).option("--no-install", "Skip installing dependencies").option("--git", "Initialize git repository").option("-y, --yes", "Skip prompts and use defaults").option(
5707
6379
  "--env <env>",
5708
- "Target environment: dev or prod (default: dev)",
6380
+ "Target environment: dev, staging, or prod (default: dev)",
5709
6381
  "dev"
5710
6382
  ).action(initCommand);
5711
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(
@@ -5736,7 +6408,7 @@ program.command("download").description("Download a published theme via the webs
5736
6408
  "latest"
5737
6409
  ).option(
5738
6410
  "--env <env>",
5739
- "Target environment: dev or prod (default: dev)",
6411
+ "Target environment: dev, staging, or prod (default: dev)",
5740
6412
  "dev"
5741
6413
  ).option("-b, --bucket <name>", "[deprecated] ignored").option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
5742
6414
  program.command("clone").description("Clone theme source code via the website-api").argument("<theme-name>", "Theme to clone").option(
@@ -5745,23 +6417,23 @@ program.command("clone").description("Clone theme source code via the website-ap
5745
6417
  "latest"
5746
6418
  ).option("-n, --name <name>", "New theme name (skips interactive prompt)").option("-o, --output <dir>", "Output directory").option(
5747
6419
  "--env <env>",
5748
- "Target environment: dev or prod (default: dev)",
6420
+ "Target environment: dev, staging, or prod (default: dev)",
5749
6421
  "dev"
5750
6422
  ).option("-b, --bucket <name>", "[deprecated] ignored").option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
5751
6423
  program.command("config").description("Configure OneX CLI credentials (AWS, API keys)").action(configCommand);
5752
6424
  program.command("login").description("Login to OneX platform").option(
5753
6425
  "--env <env>",
5754
- "Target environment: dev or prod (default: dev)",
6426
+ "Target environment: dev, staging, or prod (default: dev)",
5755
6427
  "dev"
5756
6428
  ).action(loginCommand);
5757
6429
  program.command("logout").description("Logout from OneX platform").option(
5758
6430
  "--env <env>",
5759
- "Target environment: dev or prod (default: dev)",
6431
+ "Target environment: dev, staging, or prod (default: dev)",
5760
6432
  "dev"
5761
6433
  ).action(logoutCommand);
5762
6434
  program.command("whoami").description("Show current logged-in developer").option(
5763
6435
  "--env <env>",
5764
- "Target environment: dev or prod (default: dev)",
6436
+ "Target environment: dev, staging, or prod (default: dev)",
5765
6437
  "dev"
5766
6438
  ).action(whoamiCommand);
5767
6439
  var mcpCmd = program.command("mcp").description("Manage MCP server registration and AI-context files");
@@ -5777,8 +6449,17 @@ program.command("publish").description("Build, scan, and publish theme to market
5777
6449
  "Auto-bump version before publish (patch|minor|major)"
5778
6450
  ).option(
5779
6451
  "--env <env>",
5780
- "Target environment: dev or prod (default: dev)",
6452
+ "Target environment: dev, staging, or prod (default: dev)",
5781
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"
5782
6463
  ).action(publishCommand);
5783
6464
  program.configureOutput({
5784
6465
  writeErr: (str) => process.stderr.write(chalk4.red(str))