@onexapis/cli 1.1.65 → 1.1.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -3,18 +3,18 @@
3
3
 
4
4
  var chalk4 = require('chalk');
5
5
  var ora = require('ora');
6
- var esbuild = require('esbuild');
7
- var path9 = require('path');
8
- var fs8 = require('fs/promises');
9
- var crypto = require('crypto');
6
+ var fs3 = require('fs');
7
+ var path13 = require('path');
10
8
  var glob = require('glob');
9
+ var fs8 = require('fs-extra');
10
+ var crypto = require('crypto');
11
+ var fs11 = require('fs/promises');
12
+ var esbuild = require('esbuild');
11
13
  var module$1 = require('module');
12
14
  var http = require('http');
13
- var fs3 = require('fs');
14
15
  var ws = require('ws');
15
16
  var os = require('os');
16
17
  var dotenv = require('dotenv');
17
- var fs = require('fs-extra');
18
18
  var ejs = require('ejs');
19
19
  var child_process = require('child_process');
20
20
  var commander = require('commander');
@@ -49,15 +49,15 @@ function _interopNamespace(e) {
49
49
 
50
50
  var chalk4__default = /*#__PURE__*/_interopDefault(chalk4);
51
51
  var ora__default = /*#__PURE__*/_interopDefault(ora);
52
- var esbuild__namespace = /*#__PURE__*/_interopNamespace(esbuild);
53
- var path9__default = /*#__PURE__*/_interopDefault(path9);
52
+ var fs3__default = /*#__PURE__*/_interopDefault(fs3);
53
+ var path13__default = /*#__PURE__*/_interopDefault(path13);
54
54
  var fs8__default = /*#__PURE__*/_interopDefault(fs8);
55
55
  var crypto__default = /*#__PURE__*/_interopDefault(crypto);
56
+ var fs11__default = /*#__PURE__*/_interopDefault(fs11);
57
+ var esbuild__namespace = /*#__PURE__*/_interopNamespace(esbuild);
56
58
  var http__default = /*#__PURE__*/_interopDefault(http);
57
- var fs3__default = /*#__PURE__*/_interopDefault(fs3);
58
59
  var os__default = /*#__PURE__*/_interopDefault(os);
59
60
  var dotenv__default = /*#__PURE__*/_interopDefault(dotenv);
60
- var fs__default = /*#__PURE__*/_interopDefault(fs);
61
61
  var ejs__default = /*#__PURE__*/_interopDefault(ejs);
62
62
  var inquirer__default = /*#__PURE__*/_interopDefault(inquirer);
63
63
  var archiver__default = /*#__PURE__*/_interopDefault(archiver);
@@ -132,6 +132,308 @@ var init_logger = __esm({
132
132
  logger = new Logger();
133
133
  }
134
134
  });
135
+ function isNextjsProject(dir) {
136
+ return fs3__default.default.existsSync(path13__default.default.join(dir, "next.config.ts")) || fs3__default.default.existsSync(path13__default.default.join(dir, "next.config.js")) || fs3__default.default.existsSync(path13__default.default.join(dir, "next.config.mjs"));
137
+ }
138
+ var init_detect_nextjs = __esm({
139
+ "src/utils/detect-nextjs.ts"() {
140
+ }
141
+ });
142
+ function sortedCopy(value) {
143
+ if (Array.isArray(value)) {
144
+ return value.map((v) => sortedCopy(v));
145
+ }
146
+ if (value && typeof value === "object") {
147
+ const sorted = {};
148
+ for (const key of Object.keys(value).sort()) {
149
+ sorted[key] = sortedCopy(value[key]);
150
+ }
151
+ return sorted;
152
+ }
153
+ return value;
154
+ }
155
+ function normalizeField(raw) {
156
+ const out = {
157
+ id: String(raw.id),
158
+ type: String(raw.type)
159
+ };
160
+ if (raw.required === true) out.required = true;
161
+ if (raw.default !== void 0) out.default = raw.default;
162
+ if (Array.isArray(raw.aliases) && raw.aliases.length > 0) {
163
+ out.aliases = [...raw.aliases].map(String).sort();
164
+ }
165
+ if (typeof raw.maxLength === "number") out.maxLength = raw.maxLength;
166
+ if (typeof raw.min === "number") out.min = raw.min;
167
+ if (typeof raw.max === "number") out.max = raw.max;
168
+ if (typeof raw.step === "number") out.step = raw.step;
169
+ if (Array.isArray(raw.accept)) {
170
+ out.accept = [...raw.accept].map(String).sort();
171
+ }
172
+ if (Array.isArray(raw.options)) {
173
+ out.options = raw.options.map((o) => String(o?.value ?? o)).sort();
174
+ }
175
+ return out;
176
+ }
177
+ function normalizeBlock(raw) {
178
+ return {
179
+ type: String(raw.type),
180
+ settings: Array.isArray(raw.settings) ? raw.settings.map(normalizeField).sort(sortFieldsById) : [],
181
+ defaults: raw.defaults && typeof raw.defaults === "object" ? sortedCopy(raw.defaults) : {},
182
+ ...typeof raw.limit === "number" ? { limit: raw.limit } : {},
183
+ ...typeof raw.min === "number" ? { min: raw.min } : {},
184
+ ...raw.sortable === true ? { sortable: true } : {},
185
+ ...raw.baseType ? { baseType: String(raw.baseType) } : {}
186
+ };
187
+ }
188
+ function normalizeTemplate(raw) {
189
+ const out = { id: String(raw.id) };
190
+ if (raw.isDefault === true) out.isDefault = true;
191
+ if (Array.isArray(raw.settings)) {
192
+ out.settings = raw.settings.map(normalizeField).sort(sortFieldsById);
193
+ }
194
+ if (raw.defaults && typeof raw.defaults === "object") {
195
+ out.defaults = sortedCopy(raw.defaults);
196
+ }
197
+ return out;
198
+ }
199
+ function sortFieldsById(a, b) {
200
+ return a.id.localeCompare(b.id);
201
+ }
202
+ function sortByType(a, b) {
203
+ return a.type.localeCompare(b.type);
204
+ }
205
+ function normalizeSection(raw) {
206
+ return {
207
+ type: String(raw.type),
208
+ settings: Array.isArray(raw.settings) ? raw.settings.map(normalizeField).sort(sortFieldsById) : [],
209
+ defaults: raw.defaults && typeof raw.defaults === "object" ? sortedCopy(raw.defaults) : {},
210
+ blocks: Array.isArray(raw.blocks) ? raw.blocks.map(normalizeBlock).sort(sortByType) : [],
211
+ templates: Array.isArray(raw.templates) ? raw.templates.map(normalizeTemplate).sort(sortFieldsById) : [],
212
+ dataRequirements: raw.dataRequirements && typeof raw.dataRequirements === "object" ? sortedCopy(raw.dataRequirements) : null,
213
+ ...raw.global === true ? { global: true } : {},
214
+ ...typeof raw.maxBlocks === "number" ? { maxBlocks: raw.maxBlocks } : {}
215
+ };
216
+ }
217
+ async function extractSchemas(themePath) {
218
+ const { createJiti } = await import('jiti');
219
+ const jiti = createJiti((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.js', document.baseURI).href)));
220
+ const schemaFiles = await glob.glob("sections/**/*.schema.ts", { cwd: themePath });
221
+ const sections = {};
222
+ for (const file of schemaFiles) {
223
+ try {
224
+ const mod = await jiti.import(path13__default.default.join(themePath, file));
225
+ const exports$1 = mod;
226
+ for (const value of Object.values(exports$1)) {
227
+ if (value && typeof value === "object" && typeof value.type === "string" && Array.isArray(value.settings)) {
228
+ const section = normalizeSection(value);
229
+ sections[section.type] = section;
230
+ }
231
+ }
232
+ } catch {
233
+ }
234
+ }
235
+ const manifest = {
236
+ manifestVersion: 1,
237
+ sections: {}
238
+ };
239
+ for (const type of Object.keys(sections).sort()) {
240
+ manifest.sections[type] = sections[type];
241
+ }
242
+ return manifest;
243
+ }
244
+ function serializeManifest(manifest) {
245
+ return JSON.stringify(sortedCopy(manifest), null, 2);
246
+ }
247
+ var init_extract_schemas = __esm({
248
+ "src/utils/extract-schemas.ts"() {
249
+ }
250
+ });
251
+ function isVideoAsset(filePath) {
252
+ const lower = filePath.toLowerCase();
253
+ return VIDEO_EXTENSIONS.some((ext) => lower.endsWith(ext));
254
+ }
255
+ function mimeFor(filename) {
256
+ const ext = path13__default.default.extname(filename).toLowerCase();
257
+ return MIME_MAP[ext] || "application/octet-stream";
258
+ }
259
+ async function sha256Prefix(absPath, len) {
260
+ const buf = await fs8__default.default.readFile(absPath);
261
+ return crypto__default.default.createHash("sha256").update(buf).digest("hex").slice(0, len);
262
+ }
263
+ function insertHashIntoName(relPath, hash) {
264
+ const dir = path13__default.default.posix.dirname(relPath);
265
+ const base = path13__default.default.posix.basename(relPath);
266
+ const ext = path13__default.default.posix.extname(base);
267
+ const stem = ext ? base.slice(0, -ext.length) : base;
268
+ const hashed = `${stem}-${hash}${ext}`;
269
+ return dir === "." ? hashed : `${dir}/${hashed}`;
270
+ }
271
+ async function scanThemeAssets(distDir) {
272
+ const assetsDir = path13__default.default.join(distDir, "theme-assets");
273
+ if (!await fs8__default.default.pathExists(assetsDir)) return [];
274
+ const files = await glob.glob("**/*", {
275
+ cwd: assetsDir,
276
+ nodir: true,
277
+ dot: false
278
+ });
279
+ const results = [];
280
+ for (const rel of files) {
281
+ const absPath = path13__default.default.join(assetsDir, rel);
282
+ const stat = await fs8__default.default.stat(absPath);
283
+ if (!stat.isFile()) continue;
284
+ const originalPath = rel.split(path13__default.default.sep).join("/");
285
+ const hash = await sha256Prefix(absPath, HASH_LEN);
286
+ const hashedPath = insertHashIntoName(originalPath, hash);
287
+ const contentType = mimeFor(rel);
288
+ results.push({
289
+ originalPath,
290
+ hashedPath,
291
+ hash,
292
+ size: stat.size,
293
+ contentType,
294
+ absPath
295
+ });
296
+ }
297
+ results.sort((a, b) => a.originalPath.localeCompare(b.originalPath));
298
+ return results;
299
+ }
300
+ function buildAssetMap(entries) {
301
+ const map = {};
302
+ for (const e of entries) {
303
+ map[e.originalPath] = e.hashedPath;
304
+ }
305
+ return map;
306
+ }
307
+ var MIME_MAP, HASH_LEN, VIDEO_EXTENSIONS;
308
+ var init_scan_theme_assets = __esm({
309
+ "src/utils/scan-theme-assets.ts"() {
310
+ MIME_MAP = {
311
+ ".png": "image/png",
312
+ ".jpg": "image/jpeg",
313
+ ".jpeg": "image/jpeg",
314
+ ".gif": "image/gif",
315
+ ".webp": "image/webp",
316
+ ".avif": "image/avif",
317
+ ".svg": "image/svg+xml",
318
+ ".ico": "image/x-icon",
319
+ ".bmp": "image/bmp",
320
+ ".woff": "font/woff",
321
+ ".woff2": "font/woff2",
322
+ ".ttf": "font/ttf",
323
+ ".otf": "font/otf",
324
+ ".eot": "application/vnd.ms-fontobject",
325
+ ".mp4": "video/mp4",
326
+ ".webm": "video/webm",
327
+ ".mov": "video/quicktime",
328
+ ".ogg": "video/ogg",
329
+ ".json": "application/json"
330
+ };
331
+ HASH_LEN = 8;
332
+ VIDEO_EXTENSIONS = [
333
+ ".mp4",
334
+ ".webm",
335
+ ".ogg",
336
+ ".mov",
337
+ ".avi",
338
+ ".mkv"
339
+ ];
340
+ }
341
+ });
342
+ async function scanAppDirectory(themePath) {
343
+ const appDir = path13__default.default.join(themePath, "app");
344
+ let pageFiles;
345
+ try {
346
+ pageFiles = await glob.glob("**/page.{tsx,ts,jsx,js}", { cwd: appDir });
347
+ } catch {
348
+ return [];
349
+ }
350
+ if (pageFiles.length === 0) return [];
351
+ const pages = [];
352
+ for (const pageFile of pageFiles) {
353
+ const routePath = deriveRoutePath(pageFile);
354
+ const absPageFile = path13__default.default.join(appDir, pageFile);
355
+ let source;
356
+ try {
357
+ source = await fs11__default.default.readFile(absPageFile, "utf-8");
358
+ } catch {
359
+ continue;
360
+ }
361
+ const sections = await extractSectionsFromPage(source, themePath);
362
+ pages.push({
363
+ routePath,
364
+ sourceFile: path13__default.default.join("app", pageFile),
365
+ sections
366
+ });
367
+ }
368
+ return pages;
369
+ }
370
+ function deriveRoutePath(pageFile) {
371
+ const dir = path13__default.default.dirname(pageFile);
372
+ if (dir === ".") return "/";
373
+ return "/" + dir;
374
+ }
375
+ async function extractSectionsFromPage(source, themePath) {
376
+ const importRegex = /import\s+\w+\s+from\s+["'](@\/|\.\.?\/)(components\/[^"']+)["']/g;
377
+ const sections = [];
378
+ const seen = /* @__PURE__ */ new Set();
379
+ for (const match of source.matchAll(importRegex)) {
380
+ const rawImportPath = match[2];
381
+ const componentDir = path13__default.default.dirname(rawImportPath);
382
+ const absComponentDir = path13__default.default.join(themePath, componentDir);
383
+ if (seen.has(componentDir)) continue;
384
+ seen.add(componentDir);
385
+ const sectionJsonPath = path13__default.default.join(absComponentDir, "section.json");
386
+ let sectionJson;
387
+ try {
388
+ const raw = await fs11__default.default.readFile(sectionJsonPath, "utf-8");
389
+ sectionJson = JSON.parse(raw);
390
+ } catch {
391
+ continue;
392
+ }
393
+ if (sectionJson.type !== "opaque-react") continue;
394
+ if (!sectionJson.entry) continue;
395
+ sections.push({
396
+ type: "opaque-react",
397
+ name: sectionJson.name ?? path13__default.default.basename(componentDir),
398
+ entry: path13__default.default.join(componentDir, sectionJson.entry),
399
+ componentDir
400
+ });
401
+ }
402
+ return sections;
403
+ }
404
+ function buildNextjsPagesMap(pages, themeId) {
405
+ const result = {};
406
+ for (const page of pages) {
407
+ const id = page.routePath === "/" ? "home" : page.routePath.replace(/\//g, "-").replace(/^-/, "");
408
+ const makeSectionType = (name) => `${themeId}-${name.toLowerCase().replace(/\s+/g, "-")}`;
409
+ result[id] = {
410
+ id,
411
+ name: id.charAt(0).toUpperCase() + id.slice(1),
412
+ path: page.routePath,
413
+ config: {
414
+ id,
415
+ path: page.routePath,
416
+ sections: page.sections.map((s, i) => ({
417
+ id: `${id}-section-${i}`,
418
+ type: makeSectionType(s.name),
419
+ sectionType: "opaque-react",
420
+ settings: {}
421
+ }))
422
+ },
423
+ sections: page.sections.map((s, i) => ({
424
+ id: `${id}-section-${i}`,
425
+ type: makeSectionType(s.name),
426
+ sectionType: "opaque-react",
427
+ settings: {}
428
+ }))
429
+ };
430
+ }
431
+ return result;
432
+ }
433
+ var init_nextjs_page_scanner = __esm({
434
+ "src/utils/nextjs-page-scanner.ts"() {
435
+ }
436
+ });
135
437
 
136
438
  // src/utils/compile-theme.ts
137
439
  var compile_theme_exports = {};
@@ -147,8 +449,8 @@ async function generateThemeCSS(themePath, outDir) {
147
449
  const tailwindcss = (await import('tailwindcss')).default;
148
450
  const tailwindConfig = {
149
451
  content: [
150
- path9__default.default.join(themePath, "sections/**/*.{ts,tsx}"),
151
- path9__default.default.join(themePath, "components/**/*.{ts,tsx}")
452
+ path13__default.default.join(themePath, "sections/**/*.{ts,tsx}"),
453
+ path13__default.default.join(themePath, "components/**/*.{ts,tsx}")
152
454
  ],
153
455
  theme: { extend: {} },
154
456
  plugins: []
@@ -158,7 +460,7 @@ async function generateThemeCSS(themePath, outDir) {
158
460
  inputCSS,
159
461
  { from: void 0 }
160
462
  );
161
- await fs8__default.default.writeFile(path9__default.default.join(outDir, "bundle.css"), result.css);
463
+ await fs11__default.default.writeFile(path13__default.default.join(outDir, "bundle.css"), result.css);
162
464
  logger.info("Generated bundle.css");
163
465
  } catch (err) {
164
466
  logger.warning(
@@ -169,12 +471,12 @@ async function generateThemeCSS(themePath, outDir) {
169
471
  async function resolveNodeModulesFile(startDir, relativePath) {
170
472
  let dir = startDir;
171
473
  while (true) {
172
- const candidate = path9__default.default.join(dir, "node_modules", relativePath);
474
+ const candidate = path13__default.default.join(dir, "node_modules", relativePath);
173
475
  try {
174
- await fs8__default.default.access(candidate);
476
+ await fs11__default.default.access(candidate);
175
477
  return candidate;
176
478
  } catch {
177
- const parent = path9__default.default.dirname(dir);
479
+ const parent = path13__default.default.dirname(dir);
178
480
  if (parent === dir) break;
179
481
  dir = parent;
180
482
  }
@@ -198,7 +500,7 @@ async function scanImportsFromPackage(sourceDir, packageName) {
198
500
  });
199
501
  for (const file of sourceFiles) {
200
502
  try {
201
- const content = await fs8__default.default.readFile(path9__default.default.join(sourceDir, file), "utf-8");
503
+ const content = await fs11__default.default.readFile(path13__default.default.join(sourceDir, file), "utf-8");
202
504
  for (const match of content.matchAll(namespaceImportRegex)) {
203
505
  const subpath = match[1] ? match[1].slice(1) : "";
204
506
  if (!result[subpath]) result[subpath] = /* @__PURE__ */ new Set();
@@ -252,17 +554,17 @@ function createCoreGlobalPlugin(themePath) {
252
554
  const distFileName = subpath ? `${subpath}.mjs` : "index.mjs";
253
555
  let distPath = await resolveNodeModulesFile(
254
556
  themePath,
255
- path9__default.default.join("@onexapis", "core", "dist", distFileName)
557
+ path13__default.default.join("@onexapis", "core", "dist", distFileName)
256
558
  );
257
559
  if (!distPath) {
258
560
  distPath = await resolveNodeModulesFile(
259
561
  __dirname,
260
- path9__default.default.join("@onexapis", "core", "dist", distFileName)
562
+ path13__default.default.join("@onexapis", "core", "dist", distFileName)
261
563
  );
262
564
  }
263
565
  try {
264
566
  if (!distPath) throw new Error("not found");
265
- const distContent = await fs8__default.default.readFile(distPath, "utf-8");
567
+ const distContent = await fs11__default.default.readFile(distPath, "utf-8");
266
568
  const exportMatches = distContent.matchAll(/export\s*\{([^}]+)\}/g);
267
569
  for (const m of exportMatches) {
268
570
  const names = m[1].split(",").map((n) => {
@@ -309,180 +611,6 @@ ${namedExportLines}
309
611
  }
310
612
  };
311
613
  }
312
- function createThemeDepsStubPlugin(themePath) {
313
- return {
314
- name: "theme-deps-stub",
315
- setup(build2) {
316
- const tryResolveOrStub = (filter, namespace) => {
317
- build2.onResolve({ filter }, async (args) => {
318
- if (args.pluginData?.skipStub) return void 0;
319
- try {
320
- const result = await build2.resolve(args.path, {
321
- kind: args.kind,
322
- resolveDir: args.resolveDir || themePath,
323
- importer: args.importer,
324
- namespace: "file",
325
- pluginData: { skipStub: true }
326
- });
327
- if (!result.errors.length) return result;
328
- } catch {
329
- }
330
- try {
331
- const req = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.js', document.baseURI).href)) || __filename);
332
- const resolved = req.resolve(args.path);
333
- if (resolved) return { path: resolved, namespace: "file" };
334
- } catch {
335
- }
336
- return { path: args.path, namespace };
337
- });
338
- };
339
- tryResolveOrStub(/^next\//, "next-stub");
340
- build2.onLoad({ filter: /.*/, namespace: "next-stub" }, (args) => {
341
- const stubs = {
342
- "next/image": `
343
- import React from 'react';
344
- const Image = React.forwardRef((props, ref) => {
345
- const { src, alt, width, height, fill, priority, sizes, quality, placeholder, blurDataURL, onLoad, onError, style, className, ...rest } = props;
346
- const imgSrc = typeof src === 'object' ? src.src : src;
347
- const fillStyle = fill ? { position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: style?.objectFit || 'cover', display: 'block' } : {};
348
- const mergedStyle = { ...fillStyle, ...style };
349
- return React.createElement('img', {
350
- ref, src: imgSrc, alt,
351
- width: fill ? undefined : width, height: fill ? undefined : height,
352
- loading: priority ? 'eager' : 'lazy',
353
- style: Object.keys(mergedStyle).length > 0 ? mergedStyle : undefined,
354
- className, onLoad, onError, ...rest,
355
- });
356
- });
357
- export default Image;
358
- `,
359
- "next/link": `
360
- import React from 'react';
361
- const Link = ({ href, children, ...rest }) => React.createElement('a', { href, ...rest }, children);
362
- export default Link;
363
- `,
364
- "next/navigation": `
365
- export function useRouter() { return { push(u){window.location.href=u}, replace(u){window.location.href=u}, back(){window.history.back()}, forward(){window.history.forward()}, refresh(){window.location.reload()}, prefetch(){} }; }
366
- export function usePathname() { return window.location.pathname; }
367
- export function useSearchParams() { return new URLSearchParams(window.location.search); }
368
- export function useParams() { return {}; }
369
- export function redirect(url) { window.location.href = url; }
370
- export function notFound() { throw new Error('Not Found'); }
371
- `,
372
- "next/headers": `
373
- export function cookies() { return { get(){}, getAll(){ return []; }, set(){}, delete(){}, has(){ return false; } }; }
374
- export function headers() { return new Headers(); }
375
- `
376
- };
377
- return {
378
- contents: stubs[args.path] || "export default {};",
379
- loader: "jsx",
380
- resolveDir: themePath
381
- };
382
- });
383
- const lucideImports = /* @__PURE__ */ new Set();
384
- let lucideThemeScanned = false;
385
- tryResolveOrStub(/^lucide-react/, "lucide-stub");
386
- build2.onLoad({ filter: /.*/, namespace: "lucide-stub" }, async () => {
387
- if (!lucideThemeScanned) {
388
- lucideThemeScanned = true;
389
- try {
390
- const scanned = await scanImportsFromPackage(
391
- themePath,
392
- "lucide-react"
393
- );
394
- for (const names of Object.values(scanned)) {
395
- for (const name of names) lucideImports.add(name);
396
- }
397
- } catch {
398
- }
399
- }
400
- const iconNames = [...lucideImports];
401
- const exports$1 = iconNames.map((n) => `icon as ${n}`).join(", ");
402
- return {
403
- contents: `
404
- const icon = (props) => null;
405
- export { ${exports$1} };
406
- export default new Proxy({}, { get: (_, name) => name === '__esModule' ? true : icon });
407
- `.trim(),
408
- loader: "jsx"
409
- };
410
- });
411
- tryResolveOrStub(/^framer-motion/, "motion-stub");
412
- build2.onLoad({ filter: /.*/, namespace: "motion-stub" }, () => ({
413
- contents: `
414
- import React from 'react';
415
- const handler = { get: (_, name) => {
416
- if (name === '__esModule') return true;
417
- return React.forwardRef((props, ref) => React.createElement(name, { ...props, ref }));
418
- }};
419
- export const motion = new Proxy({}, handler);
420
- export const AnimatePresence = ({ children }) => children || null;
421
- export function useInView() { return true; }
422
- export default { motion, AnimatePresence, useInView };
423
- `.trim(),
424
- loader: "jsx",
425
- resolveDir: themePath
426
- }));
427
- tryResolveOrStub(/^sonner$/, "sonner-stub");
428
- build2.onLoad({ filter: /.*/, namespace: "sonner-stub" }, () => ({
429
- contents: `
430
- export const toast = new Proxy(() => {}, { get: () => () => {} });
431
- export const Toaster = () => null;
432
- export default { toast, Toaster };
433
- `.trim(),
434
- loader: "jsx"
435
- }));
436
- tryResolveOrStub(/^react-hook-form$/, "rhf-stub");
437
- build2.onLoad({ filter: /.*/, namespace: "rhf-stub" }, () => ({
438
- contents: `
439
- export function useForm() {
440
- return {
441
- register: () => ({}),
442
- handleSubmit: (fn) => (e) => { e?.preventDefault?.(); fn({}); },
443
- formState: { errors: {}, isSubmitting: false, isValid: true },
444
- watch: () => undefined,
445
- setValue: () => {},
446
- reset: () => {},
447
- control: {},
448
- };
449
- }
450
- export function useController() { return { field: {}, fieldState: {} }; }
451
- export function useFormContext() { return useForm(); }
452
- `.trim(),
453
- loader: "js"
454
- }));
455
- tryResolveOrStub(/^@hookform\/resolvers/, "hookform-resolvers-stub");
456
- build2.onLoad(
457
- { filter: /.*/, namespace: "hookform-resolvers-stub" },
458
- () => ({
459
- contents: `export function zodResolver() { return () => ({ values: {}, errors: {} }); }`,
460
- loader: "js"
461
- })
462
- );
463
- tryResolveOrStub(/^next-intl$/, "next-intl-stub");
464
- build2.onLoad({ filter: /.*/, namespace: "next-intl-stub" }, () => ({
465
- contents: `
466
- export function useTranslations(ns) {
467
- return (key) => ns ? ns + '.' + key : key;
468
- }
469
- export function useLocale() { return 'en'; }
470
- export function useMessages() { return {}; }
471
- `.trim(),
472
- loader: "js"
473
- }));
474
- tryResolveOrStub(/^zod$/, "zod-stub");
475
- build2.onLoad({ filter: /.*/, namespace: "zod-stub" }, () => ({
476
- contents: `
477
- const schema = () => ({ parse: (v) => v, safeParse: (v) => ({ success: true, data: v }), optional: schema, min: schema, max: schema, email: schema, url: schema, regex: schema, refine: schema, transform: schema });
478
- export const z = { string: schema, number: schema, boolean: schema, object: (s) => ({ ...schema(), shape: s }), array: schema, enum: schema, union: schema, literal: schema, infer: undefined };
479
- export default z;
480
- `.trim(),
481
- loader: "js"
482
- }));
483
- }
484
- };
485
- }
486
614
  async function generateThemeData(themePath, outputDir, themeId) {
487
615
  const { createJiti } = await import('jiti');
488
616
  const jiti = createJiti((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.js', document.baseURI).href)));
@@ -491,7 +619,7 @@ async function generateThemeData(themePath, outputDir, themeId) {
491
619
  const pages = {};
492
620
  for (const ext of [".ts", ".js"]) {
493
621
  try {
494
- const mod = await jiti.import(path9__default.default.join(themePath, `theme.config${ext}`));
622
+ const mod = await jiti.import(path13__default.default.join(themePath, `theme.config${ext}`));
495
623
  themeConfig = mod.default || mod;
496
624
  break;
497
625
  } catch {
@@ -499,20 +627,20 @@ async function generateThemeData(themePath, outputDir, themeId) {
499
627
  }
500
628
  for (const ext of [".ts", ".js"]) {
501
629
  try {
502
- const mod = await jiti.import(path9__default.default.join(themePath, `theme.layout${ext}`));
630
+ const mod = await jiti.import(path13__default.default.join(themePath, `theme.layout${ext}`));
503
631
  layoutConfig = mod.default || mod;
504
632
  break;
505
633
  } catch {
506
634
  }
507
635
  }
508
636
  const schemas = {};
509
- const sectionsDir = path9__default.default.join(themePath, "sections");
637
+ const sectionsDir = path13__default.default.join(themePath, "sections");
510
638
  try {
511
- const sectionDirs = await fs8__default.default.readdir(sectionsDir);
639
+ const sectionDirs = await fs11__default.default.readdir(sectionsDir);
512
640
  for (const dir of sectionDirs) {
513
- const schemaFile = path9__default.default.join(sectionsDir, dir, `${dir}.schema.ts`);
641
+ const schemaFile = path13__default.default.join(sectionsDir, dir, `${dir}.schema.ts`);
514
642
  try {
515
- await fs8__default.default.access(schemaFile);
643
+ await fs11__default.default.access(schemaFile);
516
644
  const mod = await jiti.import(schemaFile);
517
645
  for (const [key, value] of Object.entries(mod)) {
518
646
  if (key.endsWith("Schema") && value && typeof value === "object" && value.type) {
@@ -524,14 +652,14 @@ async function generateThemeData(themePath, outputDir, themeId) {
524
652
  }
525
653
  } catch {
526
654
  }
527
- const pagesDir = path9__default.default.join(themePath, "pages");
655
+ const pagesDir = path13__default.default.join(themePath, "pages");
528
656
  try {
529
- const files = await fs8__default.default.readdir(pagesDir);
657
+ const files = await fs11__default.default.readdir(pagesDir);
530
658
  for (const file of files) {
531
659
  if (!file.match(/\.(ts|js)$/)) continue;
532
660
  const name = file.replace(/\.(ts|js)$/, "");
533
661
  try {
534
- const mod = await jiti.import(path9__default.default.join(pagesDir, file));
662
+ const mod = await jiti.import(path13__default.default.join(pagesDir, file));
535
663
  const config = mod.default || mod;
536
664
  const sections = (config.sections || []).map((section) => {
537
665
  const schema = schemas[section.type];
@@ -559,8 +687,16 @@ async function generateThemeData(themePath, outputDir, themeId) {
559
687
  }
560
688
  } catch {
561
689
  }
562
- await fs8__default.default.writeFile(
563
- path9__default.default.join(outputDir, "theme-data.json"),
690
+ if (isNextjsProject(themePath)) {
691
+ const nextjsPages = await scanAppDirectory(themePath);
692
+ if (nextjsPages.length > 0) {
693
+ const nextjsPagesMap = buildNextjsPagesMap(nextjsPages, themeId);
694
+ Object.assign(pages, nextjsPagesMap);
695
+ logger.info(`Scanned ${nextjsPages.length} Next.js app/ pages`);
696
+ }
697
+ }
698
+ await fs11__default.default.writeFile(
699
+ path13__default.default.join(outputDir, "theme-data.json"),
564
700
  JSON.stringify(
565
701
  {
566
702
  themeId,
@@ -583,22 +719,22 @@ async function generateThemeData(themePath, outputDir, themeId) {
583
719
  logger.info(`Generated theme-data.json (${Object.keys(pages).length} pages)`);
584
720
  }
585
721
  async function contentHashEntry(outputDir) {
586
- const entryPath = path9__default.default.join(outputDir, "bundle-entry.js");
587
- const mapPath = path9__default.default.join(outputDir, "bundle-entry.js.map");
722
+ const entryPath = path13__default.default.join(outputDir, "bundle-entry.js");
723
+ const mapPath = path13__default.default.join(outputDir, "bundle-entry.js.map");
588
724
  let entryContent;
589
725
  try {
590
- entryContent = await fs8__default.default.readFile(entryPath, "utf-8");
726
+ entryContent = await fs11__default.default.readFile(entryPath, "utf-8");
591
727
  } catch {
592
- const indexPath = path9__default.default.join(outputDir, "index.js");
728
+ const indexPath = path13__default.default.join(outputDir, "index.js");
593
729
  try {
594
- entryContent = await fs8__default.default.readFile(indexPath, "utf-8");
730
+ entryContent = await fs11__default.default.readFile(indexPath, "utf-8");
595
731
  } catch {
596
732
  logger.warning("No entry file found in output, skipping content hash");
597
733
  return;
598
734
  }
599
735
  const hash2 = crypto__default.default.createHash("sha256").update(entryContent).digest("hex").slice(0, 8);
600
736
  const hashedName2 = `bundle-entry-${hash2}.js`;
601
- const indexMapPath = path9__default.default.join(outputDir, "index.js.map");
737
+ const indexMapPath = path13__default.default.join(outputDir, "index.js.map");
602
738
  const hashedMapName2 = `bundle-entry-${hash2}.js.map`;
603
739
  entryContent = entryContent.replace(
604
740
  /\/\/# sourceMappingURL=index\.js\.map/,
@@ -606,18 +742,18 @@ async function contentHashEntry(outputDir) {
606
742
  );
607
743
  const oldFiles2 = await glob.glob("bundle-entry-*.js*", { cwd: outputDir });
608
744
  for (const f of oldFiles2) {
609
- await fs8__default.default.unlink(path9__default.default.join(outputDir, f));
745
+ await fs11__default.default.unlink(path13__default.default.join(outputDir, f));
610
746
  }
611
- await fs8__default.default.writeFile(path9__default.default.join(outputDir, hashedName2), entryContent);
612
- await fs8__default.default.unlink(indexPath);
747
+ await fs11__default.default.writeFile(path13__default.default.join(outputDir, hashedName2), entryContent);
748
+ await fs11__default.default.unlink(indexPath);
613
749
  try {
614
- await fs8__default.default.unlink(entryPath);
750
+ await fs11__default.default.unlink(entryPath);
615
751
  } catch {
616
752
  }
617
- await fs8__default.default.writeFile(entryPath, entryContent);
753
+ await fs11__default.default.writeFile(entryPath, entryContent);
618
754
  try {
619
- await fs8__default.default.access(indexMapPath);
620
- await fs8__default.default.rename(indexMapPath, path9__default.default.join(outputDir, hashedMapName2));
755
+ await fs11__default.default.access(indexMapPath);
756
+ await fs11__default.default.rename(indexMapPath, path13__default.default.join(outputDir, hashedMapName2));
621
757
  } catch {
622
758
  }
623
759
  logger.info(`Entry hashed: ${hashedName2}`);
@@ -632,17 +768,17 @@ async function contentHashEntry(outputDir) {
632
768
  );
633
769
  const oldFiles = await glob.glob("bundle-entry-*.js*", { cwd: outputDir });
634
770
  for (const f of oldFiles) {
635
- await fs8__default.default.unlink(path9__default.default.join(outputDir, f));
771
+ await fs11__default.default.unlink(path13__default.default.join(outputDir, f));
636
772
  }
637
- await fs8__default.default.writeFile(path9__default.default.join(outputDir, hashedName), entryContent);
773
+ await fs11__default.default.writeFile(path13__default.default.join(outputDir, hashedName), entryContent);
638
774
  try {
639
- await fs8__default.default.unlink(entryPath);
775
+ await fs11__default.default.unlink(entryPath);
640
776
  } catch {
641
777
  }
642
- await fs8__default.default.writeFile(entryPath, entryContent);
778
+ await fs11__default.default.writeFile(entryPath, entryContent);
643
779
  try {
644
- await fs8__default.default.access(mapPath);
645
- await fs8__default.default.rename(mapPath, path9__default.default.join(outputDir, hashedMapName));
780
+ await fs11__default.default.access(mapPath);
781
+ await fs11__default.default.rename(mapPath, path13__default.default.join(outputDir, hashedMapName));
646
782
  } catch {
647
783
  }
648
784
  logger.info(`Entry hashed: ${hashedName}`);
@@ -654,7 +790,7 @@ async function extractDataRequirements(themePath) {
654
790
  const requirements = {};
655
791
  for (const file of schemaFiles) {
656
792
  try {
657
- const mod = await jiti.import(path9__default.default.join(themePath, file));
793
+ const mod = await jiti.import(path13__default.default.join(themePath, file));
658
794
  const exports$1 = mod;
659
795
  for (const value of Object.values(exports$1)) {
660
796
  if (value && typeof value === "object" && typeof value.type === "string" && value.dataRequirements && typeof value.dataRequirements === "object") {
@@ -669,12 +805,105 @@ async function extractDataRequirements(themePath) {
669
805
  }
670
806
  return requirements;
671
807
  }
808
+ async function writeGateManifests(themePath, outputDir) {
809
+ try {
810
+ const schemas = await extractSchemas(themePath);
811
+ await fs11__default.default.writeFile(
812
+ path13__default.default.join(outputDir, "schemas.json"),
813
+ serializeManifest(schemas)
814
+ );
815
+ logger.info(
816
+ `Generated schemas.json (${Object.keys(schemas.sections).length} sections)`
817
+ );
818
+ } catch (err) {
819
+ logger.warning(
820
+ `schemas.json not written: ${err instanceof Error ? err.message : String(err)}`
821
+ );
822
+ }
823
+ try {
824
+ const entries = await scanThemeAssets(outputDir);
825
+ const assets = entries.map((e) => ({
826
+ path: e.originalPath,
827
+ hash: e.hash,
828
+ size: e.size,
829
+ contentType: e.contentType
830
+ }));
831
+ await fs11__default.default.writeFile(
832
+ path13__default.default.join(outputDir, "asset-manifest.json"),
833
+ JSON.stringify({ manifestVersion: 1, assets }, null, 2)
834
+ );
835
+ logger.info(`Generated asset-manifest.json (${assets.length} assets)`);
836
+ } catch (err) {
837
+ logger.warning(
838
+ `asset-manifest.json not written: ${err instanceof Error ? err.message : String(err)}`
839
+ );
840
+ }
841
+ }
842
+ async function compileSections(themePath, outputDir) {
843
+ const sectionsDir = path13__default.default.join(themePath, "sections");
844
+ let sectionDirs;
845
+ try {
846
+ sectionDirs = await fs11__default.default.readdir(sectionsDir);
847
+ } catch {
848
+ return;
849
+ }
850
+ for (const dirName of sectionDirs) {
851
+ const sectionSrc = path13__default.default.join(sectionsDir, dirName);
852
+ const sectionOut = path13__default.default.join(outputDir, "sections", dirName);
853
+ let section = null;
854
+ try {
855
+ const raw = await fs11__default.default.readFile(
856
+ path13__default.default.join(sectionSrc, "section.manifest.json"),
857
+ "utf-8"
858
+ );
859
+ section = JSON.parse(raw);
860
+ } catch {
861
+ continue;
862
+ }
863
+ switch (section.type) {
864
+ case "editable":
865
+ case "opaque-react":
866
+ break;
867
+ case "html": {
868
+ await fs11__default.default.mkdir(sectionOut, { recursive: true });
869
+ const htmlSrc = path13__default.default.join(sectionSrc, section.html);
870
+ let htmlContent = await fs11__default.default.readFile(htmlSrc, "utf-8");
871
+ htmlContent = htmlContent.replace(
872
+ /<script[^>]+src=["']https?:\/\/[^"']*["'][^>]*><\/script>/gi,
873
+ ""
874
+ );
875
+ await fs11__default.default.writeFile(path13__default.default.join(sectionOut, path13__default.default.basename(section.html)), htmlContent);
876
+ if (section.css) {
877
+ await fs11__default.default.copyFile(
878
+ path13__default.default.join(sectionSrc, section.css),
879
+ path13__default.default.join(sectionOut, path13__default.default.basename(section.css))
880
+ );
881
+ }
882
+ break;
883
+ }
884
+ case "iframe":
885
+ break;
886
+ case "webcomponent": {
887
+ await fs11__default.default.mkdir(sectionOut, { recursive: true });
888
+ await fs11__default.default.copyFile(
889
+ path13__default.default.join(sectionSrc, section.bundle),
890
+ path13__default.default.join(sectionOut, path13__default.default.basename(section.bundle))
891
+ );
892
+ break;
893
+ }
894
+ default:
895
+ throw new Error(
896
+ `Unknown section type. Valid types: editable, opaque-react, html, iframe, webcomponent`
897
+ );
898
+ }
899
+ }
900
+ }
672
901
  async function generateManifest(themeName, themePath, outputDir) {
673
902
  let version2 = "1.0.0";
674
903
  let themeId = themeName;
675
904
  try {
676
- const pkgContent = await fs8__default.default.readFile(
677
- path9__default.default.join(themePath, "package.json"),
905
+ const pkgContent = await fs11__default.default.readFile(
906
+ path13__default.default.join(themePath, "package.json"),
678
907
  "utf-8"
679
908
  );
680
909
  const pkg = JSON.parse(pkgContent);
@@ -692,7 +921,7 @@ async function generateManifest(themeName, themePath, outputDir) {
692
921
  const dataRequirements = await extractDataRequirements(themePath);
693
922
  let hasThemeConfig = false;
694
923
  try {
695
- await fs8__default.default.access(path9__default.default.join(themePath, "theme.config.ts"));
924
+ await fs11__default.default.access(path13__default.default.join(themePath, "theme.config.ts"));
696
925
  hasThemeConfig = true;
697
926
  } catch {
698
927
  }
@@ -733,24 +962,34 @@ async function generateManifest(themeName, themePath, outputDir) {
733
962
  // Section data requirements for server-side prefetching (keyed by section type)
734
963
  dataRequirements
735
964
  };
736
- await fs8__default.default.writeFile(
737
- path9__default.default.join(outputDir, "manifest.json"),
965
+ await fs11__default.default.writeFile(
966
+ path13__default.default.join(outputDir, "manifest.json"),
738
967
  JSON.stringify(manifest, null, 2)
739
968
  );
740
969
  }
741
970
  async function compileStandaloneTheme(themePath, themeName) {
742
- const outputDir = path9__default.default.join(themePath, "dist");
743
- const bundleEntry = path9__default.default.join(themePath, "bundle-entry.ts");
744
- const indexEntry = path9__default.default.join(themePath, "index.ts");
971
+ const outputDir = path13__default.default.join(themePath, "dist");
972
+ const isNextjs = isNextjsProject(themePath);
973
+ if (isNextjs) {
974
+ logger.info("Detected Next.js project \u2014 using next/* shims");
975
+ }
976
+ const bundleEntry = path13__default.default.join(themePath, "bundle-entry.ts");
977
+ const indexEntry = path13__default.default.join(themePath, "index.ts");
745
978
  let entryPoint = indexEntry;
746
979
  try {
747
- await fs8__default.default.access(bundleEntry);
980
+ await fs11__default.default.access(bundleEntry);
748
981
  entryPoint = bundleEntry;
749
982
  } catch {
750
983
  }
751
- const shimPath = path9__default.default.join(outputDir, ".process-shim.js");
752
- await fs8__default.default.mkdir(outputDir, { recursive: true });
753
- await fs8__default.default.writeFile(shimPath, PROCESS_SHIM);
984
+ const shimPath = path13__default.default.join(outputDir, ".process-shim.js");
985
+ await fs11__default.default.mkdir(outputDir, { recursive: true });
986
+ await fs11__default.default.writeFile(shimPath, PROCESS_SHIM);
987
+ const plugins = [
988
+ reactGlobalPlugin,
989
+ reactQueryGlobalPlugin,
990
+ createCoreGlobalPlugin(themePath)
991
+ ];
992
+ if (isNextjs) plugins.unshift(nextShimPlugin);
754
993
  const buildOptions = {
755
994
  entryPoints: [entryPoint],
756
995
  bundle: true,
@@ -762,12 +1001,7 @@ async function compileStandaloneTheme(themePath, themeName) {
762
1001
  banner: {
763
1002
  js: '"use client";'
764
1003
  },
765
- plugins: [
766
- reactGlobalPlugin,
767
- reactQueryGlobalPlugin,
768
- createCoreGlobalPlugin(themePath),
769
- createThemeDepsStubPlugin(themePath)
770
- ],
1004
+ plugins,
771
1005
  external: [],
772
1006
  alias: {
773
1007
  events: "events/",
@@ -801,19 +1035,21 @@ async function compileStandaloneTheme(themePath, themeName) {
801
1035
  try {
802
1036
  const result = await esbuild__namespace.build(buildOptions);
803
1037
  try {
804
- await fs8__default.default.unlink(shimPath);
1038
+ await fs11__default.default.unlink(shimPath);
805
1039
  } catch {
806
1040
  }
1041
+ await compileSections(themePath, outputDir);
807
1042
  await contentHashEntry(outputDir);
808
- const themeAssetsDir = path9__default.default.join(themePath, "assets");
809
- const distThemeAssets = path9__default.default.join(outputDir, "theme-assets");
1043
+ const themeAssetsDir = path13__default.default.join(themePath, "assets");
1044
+ const distThemeAssets = path13__default.default.join(outputDir, "theme-assets");
810
1045
  try {
811
- await fs8__default.default.access(themeAssetsDir);
812
- await fs8__default.default.cp(themeAssetsDir, distThemeAssets, { recursive: true });
1046
+ await fs11__default.default.access(themeAssetsDir);
1047
+ await fs11__default.default.cp(themeAssetsDir, distThemeAssets, { recursive: true });
813
1048
  logger.info("Copied static assets to dist/theme-assets/");
814
1049
  } catch {
815
1050
  }
816
1051
  await generateManifest(themeName, themePath, outputDir);
1052
+ await writeGateManifests(themePath, outputDir);
817
1053
  await generateThemeData(themePath, outputDir, themeName);
818
1054
  await generateThemeCSS(themePath, outputDir);
819
1055
  if (result.metafile) {
@@ -828,7 +1064,7 @@ async function compileStandaloneTheme(themePath, themeName) {
828
1064
  return true;
829
1065
  } catch (error) {
830
1066
  try {
831
- await fs8__default.default.unlink(shimPath);
1067
+ await fs11__default.default.unlink(shimPath);
832
1068
  } catch {
833
1069
  }
834
1070
  logger.error(`esbuild compilation failed: ${error}`);
@@ -836,18 +1072,25 @@ async function compileStandaloneTheme(themePath, themeName) {
836
1072
  }
837
1073
  }
838
1074
  async function compileStandaloneThemeDev(themePath, themeName) {
839
- const outputDir = path9__default.default.join(themePath, "dist");
840
- const bundleEntry = path9__default.default.join(themePath, "bundle-entry.ts");
841
- const indexEntry = path9__default.default.join(themePath, "index.ts");
1075
+ const outputDir = path13__default.default.join(themePath, "dist");
1076
+ const isNextjs = isNextjsProject(themePath);
1077
+ const bundleEntry = path13__default.default.join(themePath, "bundle-entry.ts");
1078
+ const indexEntry = path13__default.default.join(themePath, "index.ts");
842
1079
  let entryPoint = indexEntry;
843
1080
  try {
844
- await fs8__default.default.access(bundleEntry);
1081
+ await fs11__default.default.access(bundleEntry);
845
1082
  entryPoint = bundleEntry;
846
1083
  } catch {
847
1084
  }
848
- const shimPath = path9__default.default.join(outputDir, ".process-shim.js");
849
- await fs8__default.default.mkdir(outputDir, { recursive: true });
850
- await fs8__default.default.writeFile(shimPath, PROCESS_SHIM);
1085
+ const shimPath = path13__default.default.join(outputDir, ".process-shim.js");
1086
+ await fs11__default.default.mkdir(outputDir, { recursive: true });
1087
+ await fs11__default.default.writeFile(shimPath, PROCESS_SHIM);
1088
+ const devPlugins = [
1089
+ reactGlobalPlugin,
1090
+ reactQueryGlobalPlugin,
1091
+ createCoreGlobalPlugin(themePath)
1092
+ ];
1093
+ if (isNextjs) devPlugins.unshift(nextShimPlugin);
851
1094
  const buildOptions = {
852
1095
  entryPoints: [entryPoint],
853
1096
  bundle: true,
@@ -858,12 +1101,7 @@ async function compileStandaloneThemeDev(themePath, themeName) {
858
1101
  banner: {
859
1102
  js: '"use client";'
860
1103
  },
861
- plugins: [
862
- reactGlobalPlugin,
863
- reactQueryGlobalPlugin,
864
- createCoreGlobalPlugin(themePath),
865
- createThemeDepsStubPlugin(themePath)
866
- ],
1104
+ plugins: devPlugins,
867
1105
  external: [],
868
1106
  alias: {
869
1107
  events: "events/",
@@ -901,18 +1139,18 @@ async function compileStandaloneThemeDev(themePath, themeName) {
901
1139
  return { context: context2, outputDir };
902
1140
  }
903
1141
  async function compilePreviewRuntime(themePath) {
904
- const outputDir = path9__default.default.join(themePath, "dist");
905
- await fs8__default.default.mkdir(outputDir, { recursive: true });
906
- const outputPath = path9__default.default.join(outputDir, "preview-runtime.js");
1142
+ const outputDir = path13__default.default.join(themePath, "dist");
1143
+ await fs11__default.default.mkdir(outputDir, { recursive: true });
1144
+ const outputPath = path13__default.default.join(outputDir, "preview-runtime.js");
907
1145
  const locations = [
908
- path9__default.default.join(__dirname, "..", "preview", "preview-app.tsx"),
909
- path9__default.default.join(__dirname, "preview", "preview-app.tsx"),
910
- path9__default.default.join(__dirname, "..", "..", "src", "preview", "preview-app.tsx")
1146
+ path13__default.default.join(__dirname, "..", "preview", "preview-app.tsx"),
1147
+ path13__default.default.join(__dirname, "preview", "preview-app.tsx"),
1148
+ path13__default.default.join(__dirname, "..", "..", "src", "preview", "preview-app.tsx")
911
1149
  ];
912
1150
  let previewEntryPath = null;
913
1151
  for (const loc of locations) {
914
1152
  try {
915
- await fs8__default.default.access(loc);
1153
+ await fs11__default.default.access(loc);
916
1154
  previewEntryPath = loc;
917
1155
  break;
918
1156
  } catch {
@@ -995,10 +1233,10 @@ ${locations.join("\n")}`
995
1233
  if (!lucideScanned) {
996
1234
  lucideScanned = true;
997
1235
  const coreSrcCandidates = [
998
- path9__default.default.join(themePath, "node_modules", "@onexapis", "core", "src"),
999
- path9__default.default.join(themePath, "..", "..", "packages", "core", "src"),
1236
+ path13__default.default.join(themePath, "node_modules", "@onexapis", "core", "src"),
1237
+ path13__default.default.join(themePath, "..", "..", "packages", "core", "src"),
1000
1238
  // monorepo sibling
1001
- path9__default.default.join(
1239
+ path13__default.default.join(
1002
1240
  __dirname,
1003
1241
  "..",
1004
1242
  "..",
@@ -1013,7 +1251,7 @@ ${locations.join("\n")}`
1013
1251
  let coreSourceDir = null;
1014
1252
  for (const candidate of coreSrcCandidates) {
1015
1253
  try {
1016
- await fs8__default.default.access(candidate);
1254
+ await fs11__default.default.access(candidate);
1017
1255
  coreSourceDir = candidate;
1018
1256
  break;
1019
1257
  } catch {
@@ -1032,21 +1270,21 @@ ${locations.join("\n")}`
1032
1270
  }
1033
1271
  } else {
1034
1272
  const coreDistCandidates = [
1035
- path9__default.default.join(themePath, "node_modules", "@onexapis", "core", "dist")
1273
+ path13__default.default.join(themePath, "node_modules", "@onexapis", "core", "dist")
1036
1274
  ];
1037
1275
  const resolvedDist = await resolveNodeModulesFile(
1038
1276
  __dirname,
1039
- path9__default.default.join("@onexapis", "core", "dist")
1277
+ path13__default.default.join("@onexapis", "core", "dist")
1040
1278
  );
1041
1279
  if (resolvedDist) coreDistCandidates.push(resolvedDist);
1042
1280
  for (const candidate of coreDistCandidates) {
1043
1281
  try {
1044
- await fs8__default.default.access(candidate);
1282
+ await fs11__default.default.access(candidate);
1045
1283
  const mjsFiles = await glob.glob("*.mjs", { cwd: candidate });
1046
1284
  const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']lucide-react["']/g;
1047
1285
  for (const file of mjsFiles) {
1048
- const content = await fs8__default.default.readFile(
1049
- path9__default.default.join(candidate, file),
1286
+ const content = await fs11__default.default.readFile(
1287
+ path13__default.default.join(candidate, file),
1050
1288
  "utf-8"
1051
1289
  );
1052
1290
  for (const match of content.matchAll(importRegex)) {
@@ -1101,7 +1339,7 @@ export default new Proxy({}, { get: (_, name) => name === '__esModule' ? true :
1101
1339
  const req = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.js', document.baseURI).href)) || __filename);
1102
1340
  const cjsPath = req.resolve("framer-motion");
1103
1341
  const pkgDir = cjsPath.replace(/[/\\]dist[/\\].*$/, "");
1104
- const esmEntry = path9__default.default.join(pkgDir, "dist", "es", "index.mjs");
1342
+ const esmEntry = path13__default.default.join(pkgDir, "dist", "es", "index.mjs");
1105
1343
  const { existsSync } = await import('fs');
1106
1344
  if (existsSync(esmEntry)) {
1107
1345
  return { path: esmEntry, namespace: "file" };
@@ -1200,8 +1438,8 @@ export function headers() { return new Headers(); }
1200
1438
  });
1201
1439
  }
1202
1440
  };
1203
- const shimPath = path9__default.default.join(outputDir, ".process-shim-preview.js");
1204
- await fs8__default.default.writeFile(shimPath, PROCESS_SHIM);
1441
+ const shimPath = path13__default.default.join(outputDir, ".process-shim-preview.js");
1442
+ await fs11__default.default.writeFile(shimPath, PROCESS_SHIM);
1205
1443
  await esbuild__namespace.build({
1206
1444
  entryPoints: [previewEntryPath],
1207
1445
  bundle: true,
@@ -1236,15 +1474,19 @@ export function headers() { return new Headers(); }
1236
1474
  }
1237
1475
  });
1238
1476
  try {
1239
- await fs8__default.default.unlink(shimPath);
1477
+ await fs11__default.default.unlink(shimPath);
1240
1478
  } catch {
1241
1479
  }
1242
1480
  return outputPath;
1243
1481
  }
1244
- var PROCESS_SHIM, reactGlobalPlugin, reactQueryGlobalPlugin;
1482
+ var PROCESS_SHIM, reactGlobalPlugin, reactQueryGlobalPlugin, nextShimPlugin;
1245
1483
  var init_compile_theme = __esm({
1246
1484
  "src/utils/compile-theme.ts"() {
1247
1485
  init_logger();
1486
+ init_extract_schemas();
1487
+ init_scan_theme_assets();
1488
+ init_detect_nextjs();
1489
+ init_nextjs_page_scanner();
1248
1490
  PROCESS_SHIM = `
1249
1491
  if (typeof process === "undefined") {
1250
1492
  globalThis.process = {
@@ -1390,6 +1632,145 @@ export const {
1390
1632
  }));
1391
1633
  }
1392
1634
  };
1635
+ nextShimPlugin = {
1636
+ name: "next-shim",
1637
+ setup(build2) {
1638
+ for (const serverModule of ["next/headers", "next/server", "next/cache"]) {
1639
+ build2.onResolve({ filter: new RegExp(`^${serverModule.replace("/", "\\/")}`) }, (args) => ({
1640
+ path: args.path,
1641
+ namespace: "next-server-error"
1642
+ }));
1643
+ }
1644
+ build2.onLoad({ filter: /.*/, namespace: "next-server-error" }, (args) => ({
1645
+ errors: [
1646
+ {
1647
+ text: `"${args.path}" is server-only and cannot be used in a OneX theme bundle. Use client-side equivalents or remove the import.`
1648
+ }
1649
+ ]
1650
+ }));
1651
+ build2.onResolve({ filter: /^next\/navigation$/ }, () => ({
1652
+ path: "next-navigation-shim",
1653
+ namespace: "next-shim"
1654
+ }));
1655
+ build2.onLoad({ filter: /^next-navigation-shim$/, namespace: "next-shim" }, () => ({
1656
+ contents: `
1657
+ export function usePathname() {
1658
+ if (typeof window === 'undefined') return '/';
1659
+ return window.location.pathname;
1660
+ }
1661
+ export function useSearchParams() {
1662
+ if (typeof window === 'undefined') return new URLSearchParams();
1663
+ return new URLSearchParams(window.location.search);
1664
+ }
1665
+ export function useParams() {
1666
+ if (typeof window === 'undefined') return {};
1667
+ return (globalThis.__ONEX_ROUTE_PARAMS__) ?? {};
1668
+ }
1669
+ export function useRouter() {
1670
+ return {
1671
+ push(url) { if (typeof window !== 'undefined') window.location.href = url; },
1672
+ replace(url) { if (typeof window !== 'undefined') window.location.replace(url); },
1673
+ back() { if (typeof window !== 'undefined') window.history.back(); },
1674
+ forward() { if (typeof window !== 'undefined') window.history.forward(); },
1675
+ refresh() { if (typeof window !== 'undefined') window.location.reload(); },
1676
+ prefetch() {},
1677
+ };
1678
+ }
1679
+ export function redirect(url) {
1680
+ if (typeof window !== 'undefined') window.location.href = url;
1681
+ throw new Error('redirect');
1682
+ }
1683
+ export function notFound() { throw new Error('not-found'); }
1684
+ `.trim(),
1685
+ loader: "js"
1686
+ }));
1687
+ build2.onResolve({ filter: /^next\/font\// }, () => ({
1688
+ path: "next-font-shim",
1689
+ namespace: "next-shim"
1690
+ }));
1691
+ build2.onLoad({ filter: /^next-font-shim$/, namespace: "next-shim" }, () => ({
1692
+ contents: `
1693
+ function makeFont(family) {
1694
+ return function(_opts) {
1695
+ return {
1696
+ className: '',
1697
+ style: { fontFamily: family + ', system-ui, sans-serif' },
1698
+ variable: '--font-' + family.toLowerCase().replace(/\\s+/g, '-'),
1699
+ };
1700
+ };
1701
+ }
1702
+ export const Inter = makeFont('Inter');
1703
+ export const Roboto = makeFont('Roboto');
1704
+ export const Open_Sans = makeFont('Open Sans');
1705
+ export const Lato = makeFont('Lato');
1706
+ export const Montserrat = makeFont('Montserrat');
1707
+ export const Poppins = makeFont('Poppins');
1708
+ export const Raleway = makeFont('Raleway');
1709
+ export const Nunito = makeFont('Nunito');
1710
+ export const Geist = makeFont('Geist');
1711
+ export const Geist_Mono = makeFont('Geist Mono');
1712
+ export const DM_Sans = makeFont('DM Sans');
1713
+ export const Plus_Jakarta_Sans = makeFont('Plus Jakarta Sans');
1714
+ export function localFont(_opts) {
1715
+ return { className: '', style: { fontFamily: 'system-ui, sans-serif' }, variable: '--font-local' };
1716
+ }
1717
+ `.trim(),
1718
+ loader: "js"
1719
+ }));
1720
+ build2.onResolve({ filter: /^next\/dynamic$/ }, () => ({
1721
+ path: "next-dynamic-shim",
1722
+ namespace: "next-shim"
1723
+ }));
1724
+ build2.onLoad({ filter: /^next-dynamic-shim$/, namespace: "next-shim" }, () => ({
1725
+ contents: `
1726
+ import { lazy, Suspense, createElement } from 'react';
1727
+ export default function dynamic(loader, opts) {
1728
+ const Lazy = lazy(loader);
1729
+ return function DynamicComponent(props) {
1730
+ return createElement(Suspense, { fallback: opts?.loading ? createElement(opts.loading) : null },
1731
+ createElement(Lazy, props));
1732
+ };
1733
+ }
1734
+ `.trim(),
1735
+ loader: "js"
1736
+ }));
1737
+ build2.onResolve({ filter: /^next\/image$/ }, () => ({
1738
+ path: "next-image-shim",
1739
+ namespace: "next-shim"
1740
+ }));
1741
+ build2.onLoad({ filter: /^next-image-shim$/, namespace: "next-shim" }, () => ({
1742
+ contents: `
1743
+ import { createElement } from 'react';
1744
+ export default function Image({ src, alt, width, height, style, className, ...rest }) {
1745
+ return createElement('img', { src, alt, width, height, style, className, ...rest });
1746
+ }
1747
+ `.trim(),
1748
+ loader: "js"
1749
+ }));
1750
+ build2.onResolve({ filter: /^next\/link$/ }, () => ({
1751
+ path: "next-link-shim",
1752
+ namespace: "next-shim"
1753
+ }));
1754
+ build2.onLoad({ filter: /^next-link-shim$/, namespace: "next-shim" }, () => ({
1755
+ contents: `
1756
+ import { createElement } from 'react';
1757
+ export default function Link({ href, children, className, style, ...rest }) {
1758
+ return createElement('a', { href, className, style, ...rest }, children);
1759
+ }
1760
+ `.trim(),
1761
+ loader: "js"
1762
+ }));
1763
+ build2.onResolve({ filter: /^next\// }, () => ({
1764
+ path: "next-noop-shim",
1765
+ namespace: "next-shim"
1766
+ }));
1767
+ build2.onLoad({ filter: /^next-noop-shim$/, namespace: "next-shim" }, () => ({
1768
+ contents: `export default {};
1769
+ `,
1770
+ loader: "js"
1771
+ }));
1772
+ }
1773
+ };
1393
1774
  }
1394
1775
  });
1395
1776
 
@@ -1400,7 +1781,7 @@ __export(dev_server_exports, {
1400
1781
  });
1401
1782
  function createDevServer(options) {
1402
1783
  const clients = /* @__PURE__ */ new Set();
1403
- const themeDataPath = path9__default.default.join(options.distDir, "theme-data.json");
1784
+ const themeDataPath = path13__default.default.join(options.distDir, "theme-data.json");
1404
1785
  const server = http__default.default.createServer((req, res) => {
1405
1786
  res.setHeader("Access-Control-Allow-Origin", "*");
1406
1787
  res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
@@ -1426,8 +1807,8 @@ function createDevServer(options) {
1426
1807
  if (pathname.startsWith("/_assets/")) {
1427
1808
  const parts = pathname.replace(/^\/_assets\//, "").split("/");
1428
1809
  const assetSubpath = parts.slice(1).join("/");
1429
- const assetPath = path9__default.default.join(options.themePath, "assets", assetSubpath);
1430
- if (!assetPath.startsWith(path9__default.default.join(options.themePath, "assets"))) {
1810
+ const assetPath = path13__default.default.join(options.themePath, "assets", assetSubpath);
1811
+ if (!assetPath.startsWith(path13__default.default.join(options.themePath, "assets"))) {
1431
1812
  res.writeHead(403);
1432
1813
  res.end("Forbidden");
1433
1814
  return;
@@ -1438,8 +1819,8 @@ function createDevServer(options) {
1438
1819
  if (pathname.startsWith("/themes/")) {
1439
1820
  const match = pathname.match(/^\/themes\/[^/]+\/assets\/(.+)/);
1440
1821
  if (match) {
1441
- const assetPath = path9__default.default.join(options.themePath, "assets", match[1]);
1442
- if (!assetPath.startsWith(path9__default.default.join(options.themePath, "assets"))) {
1822
+ const assetPath = path13__default.default.join(options.themePath, "assets", match[1]);
1823
+ if (!assetPath.startsWith(path13__default.default.join(options.themePath, "assets"))) {
1443
1824
  res.writeHead(403);
1444
1825
  res.end("Forbidden");
1445
1826
  return;
@@ -1451,26 +1832,26 @@ function createDevServer(options) {
1451
1832
  if (pathname.startsWith("/assets/")) {
1452
1833
  const subpath = pathname.replace(/^\/assets\//, "");
1453
1834
  const segments = subpath.split("/");
1454
- const assetsBase = path9__default.default.join(options.themePath, "assets");
1835
+ const assetsBase = path13__default.default.join(options.themePath, "assets");
1455
1836
  let assetPath;
1456
1837
  if (segments[0] === options.themeName || segments[0] === options.themeName.replace(/^my-/, "")) {
1457
- assetPath = path9__default.default.join(assetsBase, segments.slice(1).join("/"));
1838
+ assetPath = path13__default.default.join(assetsBase, segments.slice(1).join("/"));
1458
1839
  } else {
1459
- assetPath = path9__default.default.join(assetsBase, subpath);
1840
+ assetPath = path13__default.default.join(assetsBase, subpath);
1460
1841
  }
1461
1842
  if (assetPath.startsWith(assetsBase) && fs3__default.default.existsSync(assetPath)) {
1462
1843
  serveFile(res, assetPath);
1463
1844
  return;
1464
1845
  }
1465
1846
  if (segments.length > 1) {
1466
- const fallbackPath = path9__default.default.join(assetsBase, segments.slice(1).join("/"));
1847
+ const fallbackPath = path13__default.default.join(assetsBase, segments.slice(1).join("/"));
1467
1848
  if (fallbackPath.startsWith(assetsBase) && fs3__default.default.existsSync(fallbackPath)) {
1468
1849
  serveFile(res, fallbackPath);
1469
1850
  return;
1470
1851
  }
1471
1852
  }
1472
1853
  }
1473
- const filePath = path9__default.default.join(options.distDir, pathname);
1854
+ const filePath = path13__default.default.join(options.distDir, pathname);
1474
1855
  if (!filePath.startsWith(options.distDir)) {
1475
1856
  res.writeHead(403);
1476
1857
  res.end("Forbidden");
@@ -1513,7 +1894,7 @@ function serveFile(res, filePath) {
1513
1894
  res.end("Not Found");
1514
1895
  return;
1515
1896
  }
1516
- const ext = path9__default.default.extname(filePath);
1897
+ const ext = path13__default.default.extname(filePath);
1517
1898
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
1518
1899
  const content = fs3__default.default.readFileSync(filePath);
1519
1900
  res.writeHead(200, { "Content-Type": contentType });
@@ -1630,26 +2011,26 @@ var init_dev_server = __esm({
1630
2011
  // src/utils/file-helpers.ts
1631
2012
  init_logger();
1632
2013
  async function renderTemplate(templatePath, data) {
1633
- const template = await fs__default.default.readFile(templatePath, "utf-8");
2014
+ const template = await fs8__default.default.readFile(templatePath, "utf-8");
1634
2015
  return ejs__default.default.render(template, data);
1635
2016
  }
1636
2017
  async function writeFile(filePath, content) {
1637
- await fs__default.default.ensureDir(path9__default.default.dirname(filePath));
1638
- await fs__default.default.writeFile(filePath, content, "utf-8");
2018
+ await fs8__default.default.ensureDir(path13__default.default.dirname(filePath));
2019
+ await fs8__default.default.writeFile(filePath, content, "utf-8");
1639
2020
  }
1640
2021
  function getTemplatesDir() {
1641
2022
  const locations = [
1642
- path9__default.default.join(__dirname, "../../templates"),
2023
+ path13__default.default.join(__dirname, "../../templates"),
1643
2024
  // Development
1644
- path9__default.default.join(__dirname, "../templates"),
2025
+ path13__default.default.join(__dirname, "../templates"),
1645
2026
  // Production (dist/)
1646
- path9__default.default.join(process.cwd(), "templates"),
2027
+ path13__default.default.join(process.cwd(), "templates"),
1647
2028
  // Fallback
1648
- path9__default.default.join(process.cwd(), "packages/cli/templates")
2029
+ path13__default.default.join(process.cwd(), "packages/cli/templates")
1649
2030
  // Monorepo
1650
2031
  ];
1651
2032
  for (const location of locations) {
1652
- if (fs__default.default.existsSync(location)) {
2033
+ if (fs8__default.default.existsSync(location)) {
1653
2034
  return location;
1654
2035
  }
1655
2036
  }
@@ -1657,18 +2038,18 @@ function getTemplatesDir() {
1657
2038
  }
1658
2039
  async function copyTemplate(templateName, targetDir, data) {
1659
2040
  const templatesDir = getTemplatesDir();
1660
- const templateDir = path9__default.default.join(templatesDir, templateName);
1661
- if (!fs__default.default.existsSync(templateDir)) {
2041
+ const templateDir = path13__default.default.join(templatesDir, templateName);
2042
+ if (!fs8__default.default.existsSync(templateDir)) {
1662
2043
  throw new Error(
1663
- `Template "${templateName}" not found at ${templateDir}. Available templates: ${fs__default.default.readdirSync(templatesDir).join(", ")}`
2044
+ `Template "${templateName}" not found at ${templateDir}. Available templates: ${fs8__default.default.readdirSync(templatesDir).join(", ")}`
1664
2045
  );
1665
2046
  }
1666
- await fs__default.default.ensureDir(targetDir);
1667
- const files = await fs__default.default.readdir(templateDir);
2047
+ await fs8__default.default.ensureDir(targetDir);
2048
+ const files = await fs8__default.default.readdir(templateDir);
1668
2049
  for (const file of files) {
1669
- const templatePath = path9__default.default.join(templateDir, file);
1670
- const targetPath = path9__default.default.join(targetDir, file);
1671
- const stat = await fs__default.default.stat(templatePath);
2050
+ const templatePath = path13__default.default.join(templateDir, file);
2051
+ const targetPath = path13__default.default.join(targetDir, file);
2052
+ const stat = await fs8__default.default.stat(templatePath);
1672
2053
  if (stat.isDirectory()) {
1673
2054
  await copyTemplateDir(templatePath, targetPath, data);
1674
2055
  } else if (file.endsWith(".ejs")) {
@@ -1676,17 +2057,17 @@ async function copyTemplate(templateName, targetDir, data) {
1676
2057
  const outputPath = targetPath.replace(/\.ejs$/, "");
1677
2058
  await writeFile(outputPath, content);
1678
2059
  } else {
1679
- await fs__default.default.copy(templatePath, targetPath);
2060
+ await fs8__default.default.copy(templatePath, targetPath);
1680
2061
  }
1681
2062
  }
1682
2063
  }
1683
2064
  async function copyTemplateDir(templateDir, targetDir, data) {
1684
- await fs__default.default.ensureDir(targetDir);
1685
- const files = await fs__default.default.readdir(templateDir);
2065
+ await fs8__default.default.ensureDir(targetDir);
2066
+ const files = await fs8__default.default.readdir(templateDir);
1686
2067
  for (const file of files) {
1687
- const templatePath = path9__default.default.join(templateDir, file);
1688
- const targetPath = path9__default.default.join(targetDir, file);
1689
- const stat = await fs__default.default.stat(templatePath);
2068
+ const templatePath = path13__default.default.join(templateDir, file);
2069
+ const targetPath = path13__default.default.join(targetDir, file);
2070
+ const stat = await fs8__default.default.stat(templatePath);
1690
2071
  if (stat.isDirectory()) {
1691
2072
  await copyTemplateDir(templatePath, targetPath, data);
1692
2073
  } else if (file.endsWith(".ejs")) {
@@ -1694,38 +2075,38 @@ async function copyTemplateDir(templateDir, targetDir, data) {
1694
2075
  const outputPath = targetPath.replace(/\.ejs$/, "");
1695
2076
  await writeFile(outputPath, content);
1696
2077
  } else {
1697
- await fs__default.default.copy(templatePath, targetPath);
2078
+ await fs8__default.default.copy(templatePath, targetPath);
1698
2079
  }
1699
2080
  }
1700
2081
  }
1701
2082
  function getProjectRoot() {
1702
2083
  let currentDir = process.cwd();
1703
- while (currentDir !== path9__default.default.parse(currentDir).root) {
1704
- const packageJsonPath = path9__default.default.join(currentDir, "package.json");
1705
- if (fs__default.default.existsSync(packageJsonPath)) {
1706
- const packageJson = fs__default.default.readJsonSync(packageJsonPath);
1707
- if (packageJson.workspaces || fs__default.default.existsSync(path9__default.default.join(currentDir, "src/themes")) || fs__default.default.existsSync(path9__default.default.join(currentDir, "themes"))) {
2084
+ while (currentDir !== path13__default.default.parse(currentDir).root) {
2085
+ const packageJsonPath = path13__default.default.join(currentDir, "package.json");
2086
+ if (fs8__default.default.existsSync(packageJsonPath)) {
2087
+ const packageJson = fs8__default.default.readJsonSync(packageJsonPath);
2088
+ if (packageJson.workspaces || fs8__default.default.existsSync(path13__default.default.join(currentDir, "src/themes")) || fs8__default.default.existsSync(path13__default.default.join(currentDir, "themes"))) {
1708
2089
  return currentDir;
1709
2090
  }
1710
2091
  }
1711
- currentDir = path9__default.default.dirname(currentDir);
2092
+ currentDir = path13__default.default.dirname(currentDir);
1712
2093
  }
1713
2094
  return process.cwd();
1714
2095
  }
1715
2096
  function getThemesDir() {
1716
2097
  const root = getProjectRoot();
1717
- if (fs__default.default.existsSync(path9__default.default.join(root, "themes")))
1718
- return path9__default.default.join(root, "themes");
1719
- if (fs__default.default.existsSync(path9__default.default.join(root, "src/themes")))
1720
- return path9__default.default.join(root, "src/themes");
1721
- return path9__default.default.dirname(root);
2098
+ if (fs8__default.default.existsSync(path13__default.default.join(root, "themes")))
2099
+ return path13__default.default.join(root, "themes");
2100
+ if (fs8__default.default.existsSync(path13__default.default.join(root, "src/themes")))
2101
+ return path13__default.default.join(root, "src/themes");
2102
+ return path13__default.default.dirname(root);
1722
2103
  }
1723
2104
  function getFeaturesDir() {
1724
- return path9__default.default.join(getProjectRoot(), "src/features");
2105
+ return path13__default.default.join(getProjectRoot(), "src/features");
1725
2106
  }
1726
2107
  function isOneXProject() {
1727
2108
  const root = getProjectRoot();
1728
- return fs__default.default.existsSync(path9__default.default.join(root, "themes")) || fs__default.default.existsSync(path9__default.default.join(root, "src/themes")) || fs__default.default.existsSync(path9__default.default.join(root, "theme.config.ts")) || fs__default.default.existsSync(path9__default.default.join(root, "bundle-entry.ts"));
2109
+ return fs8__default.default.existsSync(path13__default.default.join(root, "themes")) || fs8__default.default.existsSync(path13__default.default.join(root, "src/themes")) || fs8__default.default.existsSync(path13__default.default.join(root, "theme.config.ts")) || fs8__default.default.existsSync(path13__default.default.join(root, "bundle-entry.ts"));
1729
2110
  }
1730
2111
  function ensureOneXProject() {
1731
2112
  if (!isOneXProject()) {
@@ -1737,17 +2118,17 @@ function ensureOneXProject() {
1737
2118
  }
1738
2119
  function listThemes() {
1739
2120
  const themesDir = getThemesDir();
1740
- if (!fs__default.default.existsSync(themesDir)) {
2121
+ if (!fs8__default.default.existsSync(themesDir)) {
1741
2122
  return [];
1742
2123
  }
1743
- return fs__default.default.readdirSync(themesDir).filter((name) => {
1744
- const themePath = path9__default.default.join(themesDir, name);
1745
- return fs__default.default.statSync(themePath).isDirectory() && (fs__default.default.existsSync(path9__default.default.join(themePath, "theme.config.ts")) || fs__default.default.existsSync(path9__default.default.join(themePath, "bundle-entry.ts")) || fs__default.default.existsSync(path9__default.default.join(themePath, "manifest.ts")));
2124
+ return fs8__default.default.readdirSync(themesDir).filter((name) => {
2125
+ const themePath = path13__default.default.join(themesDir, name);
2126
+ return fs8__default.default.statSync(themePath).isDirectory() && (fs8__default.default.existsSync(path13__default.default.join(themePath, "theme.config.ts")) || fs8__default.default.existsSync(path13__default.default.join(themePath, "bundle-entry.ts")) || fs8__default.default.existsSync(path13__default.default.join(themePath, "manifest.ts")));
1746
2127
  });
1747
2128
  }
1748
2129
  function themeExists(themeName) {
1749
- const themePath = path9__default.default.join(getThemesDir(), themeName);
1750
- return fs__default.default.existsSync(themePath) && (fs__default.default.existsSync(path9__default.default.join(themePath, "theme.config.ts")) || fs__default.default.existsSync(path9__default.default.join(themePath, "bundle-entry.ts")) || fs__default.default.existsSync(path9__default.default.join(themePath, "manifest.ts")));
2130
+ const themePath = path13__default.default.join(getThemesDir(), themeName);
2131
+ return fs8__default.default.existsSync(themePath) && (fs8__default.default.existsSync(path13__default.default.join(themePath, "theme.config.ts")) || fs8__default.default.existsSync(path13__default.default.join(themePath, "bundle-entry.ts")) || fs8__default.default.existsSync(path13__default.default.join(themePath, "manifest.ts")));
1751
2132
  }
1752
2133
  function detectPackageManager() {
1753
2134
  const userAgent = process.env.npm_config_user_agent || "";
@@ -1755,9 +2136,9 @@ function detectPackageManager() {
1755
2136
  if (userAgent.includes("yarn")) return "yarn";
1756
2137
  if (userAgent.includes("bun")) return "bun";
1757
2138
  const cwd = process.cwd();
1758
- if (fs__default.default.existsSync(path9__default.default.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
1759
- if (fs__default.default.existsSync(path9__default.default.join(cwd, "yarn.lock"))) return "yarn";
1760
- if (fs__default.default.existsSync(path9__default.default.join(cwd, "bun.lockb"))) return "bun";
2139
+ if (fs8__default.default.existsSync(path13__default.default.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
2140
+ if (fs8__default.default.existsSync(path13__default.default.join(cwd, "yarn.lock"))) return "yarn";
2141
+ if (fs8__default.default.existsSync(path13__default.default.join(cwd, "bun.lockb"))) return "bun";
1761
2142
  return "npm";
1762
2143
  }
1763
2144
  async function installDependencies(projectPath, packageManager = "npm") {
@@ -1806,22 +2187,23 @@ function getValidCategories() {
1806
2187
  "contact"
1807
2188
  ];
1808
2189
  }
1809
- var AUTH_DIR = path9__default.default.join(os__default.default.homedir(), ".onexthm");
2190
+ var AUTH_DIR = path13__default.default.join(os__default.default.homedir(), ".onexthm");
1810
2191
  var ENV_URLS = {
1811
2192
  dev: "https://platform-dev.onexeos.com",
1812
- prod: "https://platform-staging.onexeos.com"
2193
+ staging: "https://platform-staging.onexeos.com",
2194
+ prod: "https://platform-apis.onexeos.com"
1813
2195
  };
1814
2196
  function getAuthFile(env = "dev") {
1815
- const newFile = path9__default.default.join(AUTH_DIR, `auth-${env}.json`);
2197
+ const newFile = path13__default.default.join(AUTH_DIR, `auth-${env}.json`);
1816
2198
  if (env === "dev") {
1817
- const legacyFile = path9__default.default.join(AUTH_DIR, "auth.json");
1818
- if (fs__default.default.existsSync(legacyFile) && !fs__default.default.existsSync(newFile)) {
2199
+ const legacyFile = path13__default.default.join(AUTH_DIR, "auth.json");
2200
+ if (fs8__default.default.existsSync(legacyFile) && !fs8__default.default.existsSync(newFile)) {
1819
2201
  try {
1820
- fs__default.default.moveSync(legacyFile, newFile);
2202
+ fs8__default.default.moveSync(legacyFile, newFile);
1821
2203
  } catch {
1822
2204
  try {
1823
- fs__default.default.copySync(legacyFile, newFile);
1824
- fs__default.default.removeSync(legacyFile);
2205
+ fs8__default.default.copySync(legacyFile, newFile);
2206
+ fs8__default.default.removeSync(legacyFile);
1825
2207
  } catch {
1826
2208
  }
1827
2209
  }
@@ -1833,17 +2215,17 @@ function getApiUrl(env = "dev") {
1833
2215
  return process.env.ONEXTHM_API_URL || ENV_URLS[env];
1834
2216
  }
1835
2217
  async function saveAuthTokens(tokens, env = "dev") {
1836
- await fs__default.default.ensureDir(AUTH_DIR);
2218
+ await fs8__default.default.ensureDir(AUTH_DIR);
1837
2219
  const key = getMachineKey();
1838
2220
  const data = JSON.stringify(tokens);
1839
2221
  const encrypted = encrypt(data, key);
1840
- await fs__default.default.writeFile(getAuthFile(env), encrypted, "utf-8");
2222
+ await fs8__default.default.writeFile(getAuthFile(env), encrypted, "utf-8");
1841
2223
  }
1842
2224
  function loadAuthTokens(env = "dev") {
1843
2225
  try {
1844
2226
  const file = getAuthFile(env);
1845
- if (!fs__default.default.existsSync(file)) return null;
1846
- const encrypted = fs__default.default.readFileSync(file, "utf-8");
2227
+ if (!fs8__default.default.existsSync(file)) return null;
2228
+ const encrypted = fs8__default.default.readFileSync(file, "utf-8");
1847
2229
  const key = getMachineKey();
1848
2230
  const data = decrypt(encrypted, key);
1849
2231
  return JSON.parse(data);
@@ -1853,7 +2235,7 @@ function loadAuthTokens(env = "dev") {
1853
2235
  }
1854
2236
  async function clearAuthTokens(env = "dev") {
1855
2237
  try {
1856
- await fs__default.default.remove(getAuthFile(env));
2238
+ await fs8__default.default.remove(getAuthFile(env));
1857
2239
  } catch {
1858
2240
  }
1859
2241
  }
@@ -1912,7 +2294,7 @@ function getMachineKey() {
1912
2294
  seed = `onexthm:${os__default.default.hostname()}:${os__default.default.userInfo().username}`;
1913
2295
  } else if (process.platform === "linux") {
1914
2296
  try {
1915
- seed = `onexthm:${fs__default.default.readFileSync("/etc/machine-id", "utf-8").trim()}`;
2297
+ seed = `onexthm:${fs8__default.default.readFileSync("/etc/machine-id", "utf-8").trim()}`;
1916
2298
  } catch {
1917
2299
  seed = `onexthm:${os__default.default.hostname()}:${os__default.default.userInfo().username}`;
1918
2300
  }
@@ -1950,7 +2332,7 @@ function parseJwtClaims(idToken) {
1950
2332
  }
1951
2333
 
1952
2334
  // src/commands/init.ts
1953
- async function initCommand(projectName, options = {}) {
2335
+ async function initCommand(projectName, options) {
1954
2336
  logger.header("Create New OneX Theme Project");
1955
2337
  let name;
1956
2338
  if (!projectName) {
@@ -1965,7 +2347,7 @@ async function initCommand(projectName, options = {}) {
1965
2347
  if (!validateThemeName(kebabName)) {
1966
2348
  return "Invalid project name. Use lowercase letters, numbers, and hyphens only.";
1967
2349
  }
1968
- if (fs3__default.default.existsSync(path9__default.default.join(process.cwd(), kebabName))) {
2350
+ if (fs3__default.default.existsSync(path13__default.default.join(process.cwd(), kebabName))) {
1969
2351
  return `Directory "${kebabName}" already exists`;
1970
2352
  }
1971
2353
  return true;
@@ -1976,14 +2358,14 @@ async function initCommand(projectName, options = {}) {
1976
2358
  } else {
1977
2359
  name = toKebabCase(projectName);
1978
2360
  }
1979
- const projectPath = path9__default.default.join(process.cwd(), name);
2361
+ const projectPath = path13__default.default.join(process.cwd(), name);
1980
2362
  if (fs3__default.default.existsSync(projectPath)) {
1981
2363
  logger.error(`Directory "${name}" already exists.`);
1982
2364
  process.exit(1);
1983
2365
  }
1984
2366
  if (!options.yes) {
1985
2367
  try {
1986
- const apiUrl = getApiUrl(options.env ?? "dev");
2368
+ const apiUrl = getApiUrl(options.env);
1987
2369
  const controller = new AbortController();
1988
2370
  const timeout = setTimeout(() => controller.abort(), 3e3);
1989
2371
  const response = await fetch(
@@ -2092,7 +2474,7 @@ async function initCommand(projectName, options = {}) {
2092
2474
  description,
2093
2475
  author
2094
2476
  );
2095
- const mcpJsonPath = path9__default.default.join(projectPath, ".mcp.json");
2477
+ const mcpJsonPath = path13__default.default.join(projectPath, ".mcp.json");
2096
2478
  if (fs3__default.default.existsSync(mcpJsonPath)) {
2097
2479
  let mcpContent = fs3__default.default.readFileSync(mcpJsonPath, "utf-8");
2098
2480
  if (figmaApiKey) {
@@ -2172,7 +2554,7 @@ async function initCommand(projectName, options = {}) {
2172
2554
  }
2173
2555
  }
2174
2556
  async function renameThemeInFiles(projectPath, themeName, displayName, description, author) {
2175
- const configPath = path9__default.default.join(projectPath, "theme.config.ts");
2557
+ const configPath = path13__default.default.join(projectPath, "theme.config.ts");
2176
2558
  if (fs3__default.default.existsSync(configPath)) {
2177
2559
  let content = fs3__default.default.readFileSync(configPath, "utf-8");
2178
2560
  content = content.replace(
@@ -2185,7 +2567,7 @@ async function renameThemeInFiles(projectPath, themeName, displayName, descripti
2185
2567
  );
2186
2568
  fs3__default.default.writeFileSync(configPath, content, "utf-8");
2187
2569
  }
2188
- const pkgPath = path9__default.default.join(projectPath, "package.json");
2570
+ const pkgPath = path13__default.default.join(projectPath, "package.json");
2189
2571
  if (fs3__default.default.existsSync(pkgPath)) {
2190
2572
  let content = fs3__default.default.readFileSync(pkgPath, "utf-8");
2191
2573
  content = content.replace(
@@ -2207,10 +2589,10 @@ async function createSectionCommand(name, options) {
2207
2589
  ensureOneXProject();
2208
2590
  if (!options.theme) {
2209
2591
  const isStandaloneTheme = ["theme.config.ts", "bundle-entry.ts"].some(
2210
- (f) => fs__default.default.existsSync(path9__default.default.join(process.cwd(), f))
2592
+ (f) => fs8__default.default.existsSync(path13__default.default.join(process.cwd(), f))
2211
2593
  );
2212
2594
  if (isStandaloneTheme) {
2213
- options.theme = path9__default.default.basename(process.cwd());
2595
+ options.theme = path13__default.default.basename(process.cwd());
2214
2596
  }
2215
2597
  }
2216
2598
  const sectionName = toKebabCase(name);
@@ -2273,35 +2655,35 @@ async function createSectionCommand(name, options) {
2273
2655
  };
2274
2656
  logger.startSpinner("Creating section files...");
2275
2657
  try {
2276
- const themePath = path9__default.default.join(getThemesDir(), themeName);
2277
- const sectionPath = path9__default.default.join(themePath, "sections", sectionName);
2658
+ const themePath = path13__default.default.join(getThemesDir(), themeName);
2659
+ const sectionPath = path13__default.default.join(themePath, "sections", sectionName);
2278
2660
  const schemaContent = generateSectionSchema(data);
2279
2661
  await writeFile(
2280
- path9__default.default.join(sectionPath, `${sectionName}.schema.ts`),
2662
+ path13__default.default.join(sectionPath, `${sectionName}.schema.ts`),
2281
2663
  schemaContent
2282
2664
  );
2283
2665
  if (createTemplate) {
2284
2666
  const templateContent = generateSectionTemplate(data);
2285
2667
  await writeFile(
2286
- path9__default.default.join(sectionPath, `${sectionName}-default.tsx`),
2668
+ path13__default.default.join(sectionPath, `${sectionName}-default.tsx`),
2287
2669
  templateContent
2288
2670
  );
2289
2671
  }
2290
2672
  const indexContent = generateSectionIndex(data, createTemplate);
2291
- await writeFile(path9__default.default.join(sectionPath, "index.ts"), indexContent);
2673
+ await writeFile(path13__default.default.join(sectionPath, "index.ts"), indexContent);
2292
2674
  logger.stopSpinner(true, "Section files created successfully!");
2293
2675
  logger.newLine();
2294
2676
  logger.section("Next steps:");
2295
2677
  logger.log(
2296
- ` 1. Edit schema: ${path9__default.default.relative(process.cwd(), path9__default.default.join(sectionPath, `${sectionName}.schema.ts`))}`
2678
+ ` 1. Edit schema: ${path13__default.default.relative(process.cwd(), path13__default.default.join(sectionPath, `${sectionName}.schema.ts`))}`
2297
2679
  );
2298
2680
  if (createTemplate) {
2299
2681
  logger.log(
2300
- ` 2. Edit template: ${path9__default.default.relative(process.cwd(), path9__default.default.join(sectionPath, `${sectionName}-default.tsx`))}`
2682
+ ` 2. Edit template: ${path13__default.default.relative(process.cwd(), path13__default.default.join(sectionPath, `${sectionName}-default.tsx`))}`
2301
2683
  );
2302
2684
  }
2303
2685
  logger.log(
2304
- ` 3. Add to theme manifest: ${path9__default.default.relative(process.cwd(), path9__default.default.join(themePath, "manifest.ts"))}`
2686
+ ` 3. Add to theme manifest: ${path13__default.default.relative(process.cwd(), path13__default.default.join(themePath, "manifest.ts"))}`
2305
2687
  );
2306
2688
  logger.newLine();
2307
2689
  logger.success("Section created successfully!");
@@ -2449,10 +2831,10 @@ async function createBlockCommand(name, options) {
2449
2831
  ensureOneXProject();
2450
2832
  if (!options.theme) {
2451
2833
  const isStandaloneTheme = ["theme.config.ts", "bundle-entry.ts"].some(
2452
- (f) => fs__default.default.existsSync(path9__default.default.join(process.cwd(), f))
2834
+ (f) => fs8__default.default.existsSync(path13__default.default.join(process.cwd(), f))
2453
2835
  );
2454
2836
  if (isStandaloneTheme) {
2455
- options.theme = path9__default.default.basename(process.cwd());
2837
+ options.theme = path13__default.default.basename(process.cwd());
2456
2838
  }
2457
2839
  }
2458
2840
  const blockName = toKebabCase(name);
@@ -2527,24 +2909,24 @@ async function createBlockCommand(name, options) {
2527
2909
  };
2528
2910
  logger.startSpinner("Creating block files...");
2529
2911
  try {
2530
- const blockPath = scope === "shared" ? path9__default.default.join(getFeaturesDir(), "blocks", blockName) : path9__default.default.join(getThemesDir(), themeName, "blocks", blockName);
2912
+ const blockPath = scope === "shared" ? path13__default.default.join(getFeaturesDir(), "blocks", blockName) : path13__default.default.join(getThemesDir(), themeName, "blocks", blockName);
2531
2913
  const schemaContent = generateBlockSchema(data);
2532
2914
  await writeFile(
2533
- path9__default.default.join(blockPath, `${blockName}.schema.ts`),
2915
+ path13__default.default.join(blockPath, `${blockName}.schema.ts`),
2534
2916
  schemaContent
2535
2917
  );
2536
2918
  const componentContent = generateBlockComponent(data);
2537
- await writeFile(path9__default.default.join(blockPath, `${blockName}.tsx`), componentContent);
2919
+ await writeFile(path13__default.default.join(blockPath, `${blockName}.tsx`), componentContent);
2538
2920
  const indexContent = generateBlockIndex(data);
2539
- await writeFile(path9__default.default.join(blockPath, "index.ts"), indexContent);
2921
+ await writeFile(path13__default.default.join(blockPath, "index.ts"), indexContent);
2540
2922
  logger.stopSpinner(true, "Block files created successfully!");
2541
2923
  logger.newLine();
2542
2924
  logger.section("Next steps:");
2543
2925
  logger.log(
2544
- ` 1. Edit schema: ${path9__default.default.relative(process.cwd(), path9__default.default.join(blockPath, `${blockName}.schema.ts`))}`
2926
+ ` 1. Edit schema: ${path13__default.default.relative(process.cwd(), path13__default.default.join(blockPath, `${blockName}.schema.ts`))}`
2545
2927
  );
2546
2928
  logger.log(
2547
- ` 2. Edit component: ${path9__default.default.relative(process.cwd(), path9__default.default.join(blockPath, `${blockName}.tsx`))}`
2929
+ ` 2. Edit component: ${path13__default.default.relative(process.cwd(), path13__default.default.join(blockPath, `${blockName}.tsx`))}`
2548
2930
  );
2549
2931
  logger.log(
2550
2932
  ` 3. Register in block registry: src/lib/registry/block-registry.ts`
@@ -2722,31 +3104,31 @@ async function createComponentCommand(name, options) {
2722
3104
  };
2723
3105
  logger.startSpinner("Creating component files...");
2724
3106
  try {
2725
- const componentPath = path9__default.default.join(
3107
+ const componentPath = path13__default.default.join(
2726
3108
  getFeaturesDir(),
2727
3109
  "components",
2728
3110
  componentName
2729
3111
  );
2730
3112
  const schemaContent = generateComponentSchema(data);
2731
3113
  await writeFile(
2732
- path9__default.default.join(componentPath, `${componentName}.schema.ts`),
3114
+ path13__default.default.join(componentPath, `${componentName}.schema.ts`),
2733
3115
  schemaContent
2734
3116
  );
2735
3117
  const componentContent = generateComponent(data);
2736
3118
  await writeFile(
2737
- path9__default.default.join(componentPath, `${componentName}.tsx`),
3119
+ path13__default.default.join(componentPath, `${componentName}.tsx`),
2738
3120
  componentContent
2739
3121
  );
2740
3122
  const indexContent = generateComponentIndex(data);
2741
- await writeFile(path9__default.default.join(componentPath, "index.ts"), indexContent);
3123
+ await writeFile(path13__default.default.join(componentPath, "index.ts"), indexContent);
2742
3124
  logger.stopSpinner(true, "Component files created successfully!");
2743
3125
  logger.newLine();
2744
3126
  logger.section("Next steps:");
2745
3127
  logger.log(
2746
- ` 1. Edit schema: ${path9__default.default.relative(process.cwd(), path9__default.default.join(componentPath, `${componentName}.schema.ts`))}`
3128
+ ` 1. Edit schema: ${path13__default.default.relative(process.cwd(), path13__default.default.join(componentPath, `${componentName}.schema.ts`))}`
2747
3129
  );
2748
3130
  logger.log(
2749
- ` 2. Edit component: ${path9__default.default.relative(process.cwd(), path9__default.default.join(componentPath, `${componentName}.tsx`))}`
3131
+ ` 2. Edit component: ${path13__default.default.relative(process.cwd(), path13__default.default.join(componentPath, `${componentName}.tsx`))}`
2750
3132
  );
2751
3133
  logger.log(
2752
3134
  ` 3. Register in component registry: src/lib/registry/component-registry.ts`
@@ -2903,13 +3285,13 @@ async function listSections(themeFilter) {
2903
3285
  return;
2904
3286
  }
2905
3287
  for (const theme of themes) {
2906
- const sectionsDir = path9__default.default.join(getThemesDir(), theme, "sections");
2907
- if (!fs__default.default.existsSync(sectionsDir)) {
3288
+ const sectionsDir = path13__default.default.join(getThemesDir(), theme, "sections");
3289
+ if (!fs8__default.default.existsSync(sectionsDir)) {
2908
3290
  continue;
2909
3291
  }
2910
- const sections = fs__default.default.readdirSync(sectionsDir).filter((name) => {
2911
- const sectionPath = path9__default.default.join(sectionsDir, name);
2912
- return fs__default.default.statSync(sectionPath).isDirectory() && fs__default.default.existsSync(path9__default.default.join(sectionPath, "index.ts"));
3292
+ const sections = fs8__default.default.readdirSync(sectionsDir).filter((name) => {
3293
+ const sectionPath = path13__default.default.join(sectionsDir, name);
3294
+ return fs8__default.default.statSync(sectionPath).isDirectory() && fs8__default.default.existsSync(path13__default.default.join(sectionPath, "index.ts"));
2913
3295
  });
2914
3296
  if (sections.length > 0) {
2915
3297
  logger.log(chalk4__default.default.cyan(`
@@ -2923,11 +3305,11 @@ async function listSections(themeFilter) {
2923
3305
  }
2924
3306
  async function listBlocks(themeFilter) {
2925
3307
  logger.section("\u{1F9F1} Blocks");
2926
- const sharedBlocksDir = path9__default.default.join(getFeaturesDir(), "blocks");
2927
- if (fs__default.default.existsSync(sharedBlocksDir)) {
2928
- const sharedBlocks = fs__default.default.readdirSync(sharedBlocksDir).filter((name) => {
2929
- const blockPath = path9__default.default.join(sharedBlocksDir, name);
2930
- return fs__default.default.statSync(blockPath).isDirectory() && fs__default.default.existsSync(path9__default.default.join(blockPath, "index.ts"));
3308
+ const sharedBlocksDir = path13__default.default.join(getFeaturesDir(), "blocks");
3309
+ if (fs8__default.default.existsSync(sharedBlocksDir)) {
3310
+ const sharedBlocks = fs8__default.default.readdirSync(sharedBlocksDir).filter((name) => {
3311
+ const blockPath = path13__default.default.join(sharedBlocksDir, name);
3312
+ return fs8__default.default.statSync(blockPath).isDirectory() && fs8__default.default.existsSync(path13__default.default.join(blockPath, "index.ts"));
2931
3313
  });
2932
3314
  if (sharedBlocks.length > 0) {
2933
3315
  logger.log(chalk4__default.default.cyan("\n Shared:"));
@@ -2938,13 +3320,13 @@ async function listBlocks(themeFilter) {
2938
3320
  }
2939
3321
  const themes = themeFilter ? [themeFilter] : listThemes();
2940
3322
  for (const theme of themes) {
2941
- const blocksDir = path9__default.default.join(getThemesDir(), theme, "blocks");
2942
- if (!fs__default.default.existsSync(blocksDir)) {
3323
+ const blocksDir = path13__default.default.join(getThemesDir(), theme, "blocks");
3324
+ if (!fs8__default.default.existsSync(blocksDir)) {
2943
3325
  continue;
2944
3326
  }
2945
- const blocks = fs__default.default.readdirSync(blocksDir).filter((name) => {
2946
- const blockPath = path9__default.default.join(blocksDir, name);
2947
- return fs__default.default.statSync(blockPath).isDirectory() && fs__default.default.existsSync(path9__default.default.join(blockPath, "index.ts"));
3327
+ const blocks = fs8__default.default.readdirSync(blocksDir).filter((name) => {
3328
+ const blockPath = path13__default.default.join(blocksDir, name);
3329
+ return fs8__default.default.statSync(blockPath).isDirectory() && fs8__default.default.existsSync(path13__default.default.join(blockPath, "index.ts"));
2948
3330
  });
2949
3331
  if (blocks.length > 0) {
2950
3332
  logger.log(chalk4__default.default.cyan(`
@@ -2958,14 +3340,14 @@ async function listBlocks(themeFilter) {
2958
3340
  }
2959
3341
  async function listComponents() {
2960
3342
  logger.section("\u2699\uFE0F Components");
2961
- const componentsDir = path9__default.default.join(getFeaturesDir(), "components");
2962
- if (!fs__default.default.existsSync(componentsDir)) {
3343
+ const componentsDir = path13__default.default.join(getFeaturesDir(), "components");
3344
+ if (!fs8__default.default.existsSync(componentsDir)) {
2963
3345
  logger.warning("No components directory found");
2964
3346
  return;
2965
3347
  }
2966
- const components = fs__default.default.readdirSync(componentsDir).filter((name) => {
2967
- const componentPath = path9__default.default.join(componentsDir, name);
2968
- return fs__default.default.statSync(componentPath).isDirectory() && fs__default.default.existsSync(path9__default.default.join(componentPath, "index.ts"));
3348
+ const components = fs8__default.default.readdirSync(componentsDir).filter((name) => {
3349
+ const componentPath = path13__default.default.join(componentsDir, name);
3350
+ return fs8__default.default.statSync(componentPath).isDirectory() && fs8__default.default.existsSync(path13__default.default.join(componentPath, "index.ts"));
2969
3351
  });
2970
3352
  if (components.length === 0) {
2971
3353
  logger.warning("No components found");
@@ -2986,13 +3368,13 @@ async function listThemesInfo() {
2986
3368
  }
2987
3369
  logger.log("");
2988
3370
  for (const theme of themes) {
2989
- const themeDir = path9__default.default.join(getThemesDir(), theme);
3371
+ const themeDir = path13__default.default.join(getThemesDir(), theme);
2990
3372
  const candidates = ["theme.config.ts", "bundle-entry.ts", "manifest.ts"];
2991
3373
  let manifestContent = "";
2992
3374
  for (const candidate of candidates) {
2993
- const candidatePath = path9__default.default.join(themeDir, candidate);
2994
- if (fs__default.default.existsSync(candidatePath)) {
2995
- manifestContent = fs__default.default.readFileSync(candidatePath, "utf-8");
3375
+ const candidatePath = path13__default.default.join(themeDir, candidate);
3376
+ if (fs8__default.default.existsSync(candidatePath)) {
3377
+ manifestContent = fs8__default.default.readFileSync(candidatePath, "utf-8");
2996
3378
  break;
2997
3379
  }
2998
3380
  }
@@ -3012,6 +3394,7 @@ async function listThemesInfo() {
3012
3394
 
3013
3395
  // src/commands/validate.ts
3014
3396
  init_logger();
3397
+ init_detect_nextjs();
3015
3398
  async function validateCommand(options) {
3016
3399
  logger.header("Validate Theme");
3017
3400
  ensureOneXProject();
@@ -3027,10 +3410,13 @@ async function validateCommand(options) {
3027
3410
  const isThemeDir2 = [
3028
3411
  "theme.config.ts",
3029
3412
  "bundle-entry.ts",
3030
- "manifest.ts"
3031
- ].some((f) => fs__default.default.existsSync(path9__default.default.join(process.cwd(), f)));
3413
+ "manifest.ts",
3414
+ "next.config.ts",
3415
+ "next.config.js",
3416
+ "next.config.mjs"
3417
+ ].some((f) => fs8__default.default.existsSync(path13__default.default.join(process.cwd(), f)));
3032
3418
  if (isThemeDir2) {
3033
- themeToValidate = path9__default.default.basename(process.cwd());
3419
+ themeToValidate = path13__default.default.basename(process.cwd());
3034
3420
  logger.info(`Validating current theme: ${themeToValidate}`);
3035
3421
  } else {
3036
3422
  logger.error(
@@ -3039,11 +3425,11 @@ async function validateCommand(options) {
3039
3425
  process.exit(1);
3040
3426
  }
3041
3427
  }
3042
- const themePath = path9__default.default.join(getThemesDir(), themeToValidate);
3428
+ const themePath = path13__default.default.join(getThemesDir(), themeToValidate);
3043
3429
  logger.startSpinner("Running validation checks...");
3044
3430
  const entryFiles = ["manifest.ts", "theme.config.ts", "bundle-entry.ts"];
3045
3431
  const foundEntry = entryFiles.find(
3046
- (f) => fs__default.default.existsSync(path9__default.default.join(themePath, f))
3432
+ (f) => fs8__default.default.existsSync(path13__default.default.join(themePath, f))
3047
3433
  );
3048
3434
  if (!foundEntry) {
3049
3435
  issues.push({
@@ -3052,8 +3438,8 @@ async function validateCommand(options) {
3052
3438
  message: "No theme entry file found (need at least one of: manifest.ts, theme.config.ts, bundle-entry.ts)"
3053
3439
  });
3054
3440
  } else if (foundEntry === "manifest.ts") {
3055
- const manifestContent = fs__default.default.readFileSync(
3056
- path9__default.default.join(themePath, foundEntry),
3441
+ const manifestContent = fs8__default.default.readFileSync(
3442
+ path13__default.default.join(themePath, foundEntry),
3057
3443
  "utf-8"
3058
3444
  );
3059
3445
  if (!manifestContent.includes("export const") && !manifestContent.includes("export default") && !manifestContent.includes("export interface")) {
@@ -3064,56 +3450,56 @@ async function validateCommand(options) {
3064
3450
  });
3065
3451
  }
3066
3452
  }
3067
- const configPath = path9__default.default.join(themePath, "theme.config.ts");
3068
- if (!fs__default.default.existsSync(configPath)) {
3453
+ const configPath = path13__default.default.join(themePath, "theme.config.ts");
3454
+ if (!fs8__default.default.existsSync(configPath)) {
3069
3455
  issues.push({
3070
3456
  type: "warning",
3071
3457
  file: "theme.config.ts",
3072
3458
  message: "Theme config file not found (recommended)"
3073
3459
  });
3074
3460
  }
3075
- const indexPath = path9__default.default.join(themePath, "index.ts");
3076
- if (!fs__default.default.existsSync(indexPath)) {
3461
+ const indexPath = path13__default.default.join(themePath, "index.ts");
3462
+ if (!fs8__default.default.existsSync(indexPath)) {
3077
3463
  issues.push({
3078
3464
  type: "warning",
3079
3465
  file: "index.ts",
3080
3466
  message: "Index file not found (recommended)"
3081
3467
  });
3082
3468
  }
3083
- const sectionsDir = path9__default.default.join(themePath, "sections");
3084
- if (!fs__default.default.existsSync(sectionsDir)) {
3469
+ const sectionsDir = path13__default.default.join(themePath, "sections");
3470
+ if (!fs8__default.default.existsSync(sectionsDir)) {
3085
3471
  issues.push({
3086
3472
  type: "warning",
3087
3473
  file: "sections/",
3088
3474
  message: "Sections directory not found"
3089
3475
  });
3090
3476
  } else {
3091
- const sections = fs__default.default.readdirSync(sectionsDir).filter(
3092
- (name) => fs__default.default.statSync(path9__default.default.join(sectionsDir, name)).isDirectory()
3477
+ const sections = fs8__default.default.readdirSync(sectionsDir).filter(
3478
+ (name) => fs8__default.default.statSync(path13__default.default.join(sectionsDir, name)).isDirectory()
3093
3479
  );
3094
3480
  for (const sectionName of sections) {
3095
- const sectionPath = path9__default.default.join(sectionsDir, sectionName);
3096
- const schemaFile = path9__default.default.join(sectionPath, `${sectionName}.schema.ts`);
3097
- const defaultTemplate = path9__default.default.join(
3481
+ const sectionPath = path13__default.default.join(sectionsDir, sectionName);
3482
+ const schemaFile = path13__default.default.join(sectionPath, `${sectionName}.schema.ts`);
3483
+ const defaultTemplate = path13__default.default.join(
3098
3484
  sectionPath,
3099
3485
  `${sectionName}-default.tsx`
3100
3486
  );
3101
- const indexFile = path9__default.default.join(sectionPath, "index.ts");
3102
- if (!fs__default.default.existsSync(schemaFile)) {
3487
+ const indexFile = path13__default.default.join(sectionPath, "index.ts");
3488
+ if (!fs8__default.default.existsSync(schemaFile)) {
3103
3489
  issues.push({
3104
3490
  type: "error",
3105
3491
  file: `sections/${sectionName}/${sectionName}.schema.ts`,
3106
3492
  message: "Section schema file missing"
3107
3493
  });
3108
3494
  }
3109
- if (!fs__default.default.existsSync(indexFile)) {
3495
+ if (!fs8__default.default.existsSync(indexFile)) {
3110
3496
  issues.push({
3111
3497
  type: "error",
3112
3498
  file: `sections/${sectionName}/index.ts`,
3113
3499
  message: "Section index file missing"
3114
3500
  });
3115
3501
  }
3116
- if (!fs__default.default.existsSync(defaultTemplate)) {
3502
+ if (!fs8__default.default.existsSync(defaultTemplate)) {
3117
3503
  issues.push({
3118
3504
  type: "warning",
3119
3505
  file: `sections/${sectionName}/${sectionName}-default.tsx`,
@@ -3122,29 +3508,29 @@ async function validateCommand(options) {
3122
3508
  }
3123
3509
  }
3124
3510
  }
3125
- const blocksDir = path9__default.default.join(themePath, "blocks");
3126
- if (fs__default.default.existsSync(blocksDir)) {
3127
- const blocks = fs__default.default.readdirSync(blocksDir).filter((name) => fs__default.default.statSync(path9__default.default.join(blocksDir, name)).isDirectory());
3511
+ const blocksDir = path13__default.default.join(themePath, "blocks");
3512
+ if (fs8__default.default.existsSync(blocksDir)) {
3513
+ const blocks = fs8__default.default.readdirSync(blocksDir).filter((name) => fs8__default.default.statSync(path13__default.default.join(blocksDir, name)).isDirectory());
3128
3514
  for (const blockName of blocks) {
3129
- const blockPath = path9__default.default.join(blocksDir, blockName);
3130
- const schemaFile = path9__default.default.join(blockPath, `${blockName}.schema.ts`);
3131
- const componentFile = path9__default.default.join(blockPath, `${blockName}.tsx`);
3132
- const indexFile = path9__default.default.join(blockPath, "index.ts");
3133
- if (!fs__default.default.existsSync(schemaFile)) {
3515
+ const blockPath = path13__default.default.join(blocksDir, blockName);
3516
+ const schemaFile = path13__default.default.join(blockPath, `${blockName}.schema.ts`);
3517
+ const componentFile = path13__default.default.join(blockPath, `${blockName}.tsx`);
3518
+ const indexFile = path13__default.default.join(blockPath, "index.ts");
3519
+ if (!fs8__default.default.existsSync(schemaFile)) {
3134
3520
  issues.push({
3135
3521
  type: "error",
3136
3522
  file: `blocks/${blockName}/${blockName}.schema.ts`,
3137
3523
  message: "Block schema file missing"
3138
3524
  });
3139
3525
  }
3140
- if (!fs__default.default.existsSync(componentFile)) {
3526
+ if (!fs8__default.default.existsSync(componentFile)) {
3141
3527
  issues.push({
3142
3528
  type: "error",
3143
3529
  file: `blocks/${blockName}/${blockName}.tsx`,
3144
3530
  message: "Block component file missing"
3145
3531
  });
3146
3532
  }
3147
- if (!fs__default.default.existsSync(indexFile)) {
3533
+ if (!fs8__default.default.existsSync(indexFile)) {
3148
3534
  issues.push({
3149
3535
  type: "error",
3150
3536
  file: `blocks/${blockName}/index.ts`,
@@ -3153,16 +3539,16 @@ async function validateCommand(options) {
3153
3539
  }
3154
3540
  }
3155
3541
  }
3156
- if (fs__default.default.existsSync(sectionsDir)) {
3157
- const sections = fs__default.default.readdirSync(sectionsDir).filter(
3158
- (name) => fs__default.default.statSync(path9__default.default.join(sectionsDir, name)).isDirectory()
3542
+ if (fs8__default.default.existsSync(sectionsDir)) {
3543
+ const sections = fs8__default.default.readdirSync(sectionsDir).filter(
3544
+ (name) => fs8__default.default.statSync(path13__default.default.join(sectionsDir, name)).isDirectory()
3159
3545
  );
3160
3546
  for (const sectionName of sections) {
3161
- const sectionPath = path9__default.default.join(sectionsDir, sectionName);
3162
- const tsxFiles = fs__default.default.readdirSync(sectionPath).filter((f) => f.endsWith(".tsx") && !f.endsWith(".schema.ts"));
3547
+ const sectionPath = path13__default.default.join(sectionsDir, sectionName);
3548
+ const tsxFiles = fs8__default.default.readdirSync(sectionPath).filter((f) => f.endsWith(".tsx") && !f.endsWith(".schema.ts"));
3163
3549
  for (const tsxFile of tsxFiles) {
3164
- const filePath = path9__default.default.join(sectionPath, tsxFile);
3165
- const content = fs__default.default.readFileSync(filePath, "utf-8");
3550
+ const filePath = path13__default.default.join(sectionPath, tsxFile);
3551
+ const content = fs8__default.default.readFileSync(filePath, "utf-8");
3166
3552
  const relPath = `sections/${sectionName}/${tsxFile}`;
3167
3553
  if (!content.includes('"use client"') && !content.includes("'use client'")) {
3168
3554
  issues.push({
@@ -3209,12 +3595,12 @@ async function validateCommand(options) {
3209
3595
  }
3210
3596
  }
3211
3597
  }
3212
- const registryPath = path9__default.default.join(themePath, "sections-registry.ts");
3213
- const bundleEntryPath = path9__default.default.join(themePath, "bundle-entry.ts");
3214
- const registryContent = fs__default.default.existsSync(registryPath) ? fs__default.default.readFileSync(registryPath, "utf-8") : fs__default.default.existsSync(bundleEntryPath) ? fs__default.default.readFileSync(bundleEntryPath, "utf-8") : "";
3215
- if (fs__default.default.existsSync(sectionsDir) && registryContent) {
3216
- const sections = fs__default.default.readdirSync(sectionsDir).filter(
3217
- (name) => fs__default.default.statSync(path9__default.default.join(sectionsDir, name)).isDirectory()
3598
+ const registryPath = path13__default.default.join(themePath, "sections-registry.ts");
3599
+ const bundleEntryPath = path13__default.default.join(themePath, "bundle-entry.ts");
3600
+ const registryContent = fs8__default.default.existsSync(registryPath) ? fs8__default.default.readFileSync(registryPath, "utf-8") : fs8__default.default.existsSync(bundleEntryPath) ? fs8__default.default.readFileSync(bundleEntryPath, "utf-8") : "";
3601
+ if (fs8__default.default.existsSync(sectionsDir) && registryContent) {
3602
+ const sections = fs8__default.default.readdirSync(sectionsDir).filter(
3603
+ (name) => fs8__default.default.statSync(path13__default.default.join(sectionsDir, name)).isDirectory()
3218
3604
  );
3219
3605
  for (const sectionName of sections) {
3220
3606
  if (!registryContent.includes(`sections/${sectionName}`) && !registryContent.includes(`"${sectionName}"`)) {
@@ -3226,7 +3612,7 @@ async function validateCommand(options) {
3226
3612
  }
3227
3613
  }
3228
3614
  }
3229
- if (fs__default.default.existsSync(sectionsDir)) {
3615
+ if (fs8__default.default.existsSync(sectionsDir)) {
3230
3616
  const schemaTypes = await loadSchemaTypes(themePath, sectionsDir);
3231
3617
  for (const { folderName, schemaType } of schemaTypes) {
3232
3618
  if (schemaType && schemaType !== folderName) {
@@ -3237,8 +3623,8 @@ async function validateCommand(options) {
3237
3623
  });
3238
3624
  }
3239
3625
  }
3240
- const pagesDir = path9__default.default.join(themePath, "pages");
3241
- if (fs__default.default.existsSync(pagesDir)) {
3626
+ const pagesDir = path13__default.default.join(themePath, "pages");
3627
+ if (fs8__default.default.existsSync(pagesDir)) {
3242
3628
  const allSchemaTypeSet = new Set(
3243
3629
  schemaTypes.map((s) => s.schemaType || s.folderName)
3244
3630
  );
@@ -3249,6 +3635,10 @@ async function validateCommand(options) {
3249
3635
  issues.push(...pageIssues);
3250
3636
  }
3251
3637
  }
3638
+ if (isNextjsProject(themePath)) {
3639
+ const nextjsIssues = await validateNextjsComponents(themePath);
3640
+ issues.push(...nextjsIssues);
3641
+ }
3252
3642
  logger.stopSpinner(true, "Validation complete");
3253
3643
  const errors = issues.filter((i) => i.type === "error");
3254
3644
  const warnings = issues.filter((i) => i.type === "warning");
@@ -3289,18 +3679,18 @@ async function validateCommand(options) {
3289
3679
  }
3290
3680
  async function loadSchemaTypes(themePath, sectionsDir) {
3291
3681
  const results = [];
3292
- const sections = fs__default.default.readdirSync(sectionsDir).filter((name) => fs__default.default.statSync(path9__default.default.join(sectionsDir, name)).isDirectory());
3682
+ const sections = fs8__default.default.readdirSync(sectionsDir).filter((name) => fs8__default.default.statSync(path13__default.default.join(sectionsDir, name)).isDirectory());
3293
3683
  for (const sectionName of sections) {
3294
- const schemaFile = path9__default.default.join(
3684
+ const schemaFile = path13__default.default.join(
3295
3685
  sectionsDir,
3296
3686
  sectionName,
3297
3687
  `${sectionName}.schema.ts`
3298
3688
  );
3299
- if (!fs__default.default.existsSync(schemaFile)) {
3689
+ if (!fs8__default.default.existsSync(schemaFile)) {
3300
3690
  results.push({ folderName: sectionName, schemaType: null });
3301
3691
  continue;
3302
3692
  }
3303
- const content = fs__default.default.readFileSync(schemaFile, "utf-8");
3693
+ const content = fs8__default.default.readFileSync(schemaFile, "utf-8");
3304
3694
  let schemaType = null;
3305
3695
  const schemaExportMatch = content.match(
3306
3696
  /:\s*SectionSchema\s*=\s*\{[\s\S]*?\btype:\s*["']([^"']+)["']/
@@ -3327,9 +3717,9 @@ async function loadSchemaTypes(themePath, sectionsDir) {
3327
3717
  }
3328
3718
  async function validatePageSectionTypes(pagesDir, validTypes) {
3329
3719
  const issues = [];
3330
- const files = fs__default.default.readdirSync(pagesDir).filter((f) => f.match(/\.(ts|js)$/));
3720
+ const files = fs8__default.default.readdirSync(pagesDir).filter((f) => f.match(/\.(ts|js)$/));
3331
3721
  for (const file of files) {
3332
- const content = fs__default.default.readFileSync(path9__default.default.join(pagesDir, file), "utf-8");
3722
+ const content = fs8__default.default.readFileSync(path13__default.default.join(pagesDir, file), "utf-8");
3333
3723
  const pageName = file.replace(/\.(ts|js)$/, "");
3334
3724
  const sectionsMatch = content.match(/\bsections:\s*\[/);
3335
3725
  if (!sectionsMatch || sectionsMatch.index === void 0) continue;
@@ -3342,9 +3732,13 @@ async function validatePageSectionTypes(pagesDir, validTypes) {
3342
3732
  endIdx = i;
3343
3733
  }
3344
3734
  const sectionsBlock = content.slice(startIdx, endIdx);
3345
- const typeMatches = sectionsBlock.matchAll(/\btype:\s*["']([^"']+)["']/g);
3346
- for (const match of typeMatches) {
3735
+ const sectionTypeMatches = sectionsBlock.matchAll(
3736
+ /\bid:\s*["'][^"']*["'],\s*\n?\s*type:\s*["']([^"']+)["']/g
3737
+ );
3738
+ for (const match of sectionTypeMatches) {
3347
3739
  const sectionType = match[1];
3740
+ if (COMPONENT_TYPES.has(sectionType)) continue;
3741
+ if (BLOCK_TYPES.has(sectionType)) continue;
3348
3742
  if (!validTypes.has(sectionType)) {
3349
3743
  issues.push({
3350
3744
  type: "error",
@@ -3397,9 +3791,128 @@ var FIELD_TYPES = /* @__PURE__ */ new Set([
3397
3791
  "inline_richtext",
3398
3792
  "repeater"
3399
3793
  ]);
3794
+ var COMPONENT_TYPES = /* @__PURE__ */ new Set([
3795
+ "heading",
3796
+ "paragraph",
3797
+ "button",
3798
+ "image",
3799
+ "link",
3800
+ "icon",
3801
+ "badge",
3802
+ "divider",
3803
+ "spacer",
3804
+ "container",
3805
+ "grid",
3806
+ "columns",
3807
+ "card",
3808
+ "quote",
3809
+ "input",
3810
+ "textarea",
3811
+ "checkbox",
3812
+ "select",
3813
+ "video",
3814
+ "gallery",
3815
+ "alert",
3816
+ "progress",
3817
+ "rating",
3818
+ "timer",
3819
+ "list",
3820
+ "table",
3821
+ "accordion",
3822
+ "tabs",
3823
+ "code",
3824
+ "map",
3825
+ "product-card",
3826
+ "blog-card",
3827
+ "social-links",
3828
+ "hotline-contacts",
3829
+ "company-info",
3830
+ "torn-separator"
3831
+ ]);
3832
+ var BLOCK_TYPES = /* @__PURE__ */ new Set([
3833
+ "brand-feature",
3834
+ "collection-item",
3835
+ "crafting-step",
3836
+ "testimonial-item",
3837
+ "stat-item",
3838
+ "footer-link",
3839
+ "navigation-links-block",
3840
+ "policy-section",
3841
+ "core-value-card",
3842
+ "faq-item",
3843
+ "feature-item",
3844
+ "gallery-item",
3845
+ "logo-item",
3846
+ "pricing-tier",
3847
+ "service-item",
3848
+ "stat-card",
3849
+ "team-member",
3850
+ "hero-content"
3851
+ ]);
3852
+ var SERVER_ONLY_APIS = [
3853
+ "next/headers",
3854
+ "next/server",
3855
+ "next/cache",
3856
+ "cookies()",
3857
+ "headers()",
3858
+ "draftMode()"
3859
+ ];
3860
+ async function validateNextjsComponents(themePath) {
3861
+ const issues = [];
3862
+ const { glob: glob6 } = await import('glob');
3863
+ const componentFiles = await glob6("components/**/*.{tsx,ts}", {
3864
+ cwd: themePath,
3865
+ ignore: ["**/node_modules/**"]
3866
+ });
3867
+ for (const relFile of componentFiles) {
3868
+ const absFile = path13__default.default.join(themePath, relFile);
3869
+ const content = fs8__default.default.readFileSync(absFile, "utf-8");
3870
+ const dir = path13__default.default.dirname(absFile);
3871
+ const hasSectionJson = fs8__default.default.existsSync(path13__default.default.join(dir, "section.json"));
3872
+ if (!hasSectionJson) continue;
3873
+ for (const api of SERVER_ONLY_APIS) {
3874
+ if (content.includes(`"${api}"`) || content.includes(`'${api}'`)) {
3875
+ issues.push({
3876
+ type: "error",
3877
+ file: relFile,
3878
+ message: `"${api}" is server-only and cannot be used in a OneX theme bundle. Remove this import or move data fetching to a client-side useEffect/useQuery.`
3879
+ });
3880
+ }
3881
+ if (content.includes(api.replace("()", "") + "(")) {
3882
+ issues.push({
3883
+ type: "error",
3884
+ file: relFile,
3885
+ message: `${api} is server-only and cannot be called in a browser bundle. Use useQuery or fetch() inside useEffect instead.`
3886
+ });
3887
+ }
3888
+ }
3889
+ const isAsyncComponent = /export\s+default\s+async\s+function/.test(content) || /export\s+default\s+async\s+\(/.test(content);
3890
+ if (isAsyncComponent) {
3891
+ const hasUseClient2 = content.includes('"use client"') || content.includes("'use client'");
3892
+ if (!hasUseClient2) {
3893
+ issues.push({
3894
+ type: "error",
3895
+ file: relFile,
3896
+ message: 'Async Server Components cannot be compiled to a browser bundle. Add "use client" at the top and convert data fetching to useQuery or useEffect.'
3897
+ });
3898
+ }
3899
+ }
3900
+ const usesHooks = /\buse[A-Z]\w+\s*\(/.test(content);
3901
+ const hasUseClient = content.includes('"use client"') || content.includes("'use client'");
3902
+ if (usesHooks && !hasUseClient) {
3903
+ issues.push({
3904
+ type: "warning",
3905
+ file: relFile,
3906
+ message: 'Component uses React hooks but is missing "use client" directive. Add "use client" at the top of the file.'
3907
+ });
3908
+ }
3909
+ }
3910
+ return issues;
3911
+ }
3400
3912
 
3401
3913
  // src/commands/build.ts
3402
3914
  init_logger();
3915
+ init_detect_nextjs();
3403
3916
  async function buildCommand(options) {
3404
3917
  logger.header("Build Theme");
3405
3918
  let themePath;
@@ -3407,16 +3920,16 @@ async function buildCommand(options) {
3407
3920
  if (options.theme) {
3408
3921
  themeName = options.theme;
3409
3922
  try {
3410
- const workspaceThemePath = path9__default.default.join(getThemesDir(), themeName);
3411
- if (fs__default.default.existsSync(workspaceThemePath)) {
3923
+ const workspaceThemePath = path13__default.default.join(getThemesDir(), themeName);
3924
+ if (fs8__default.default.existsSync(workspaceThemePath)) {
3412
3925
  themePath = workspaceThemePath;
3413
3926
  } else {
3414
- themePath = path9__default.default.join(process.cwd(), themeName);
3927
+ themePath = path13__default.default.join(process.cwd(), themeName);
3415
3928
  }
3416
3929
  } catch {
3417
- themePath = path9__default.default.join(process.cwd(), themeName);
3930
+ themePath = path13__default.default.join(process.cwd(), themeName);
3418
3931
  }
3419
- if (!fs__default.default.existsSync(themePath)) {
3932
+ if (!fs8__default.default.existsSync(themePath)) {
3420
3933
  logger.error(`Theme "${themeName}" not found.`);
3421
3934
  process.exit(1);
3422
3935
  }
@@ -3424,11 +3937,14 @@ async function buildCommand(options) {
3424
3937
  const isThemeDir2 = [
3425
3938
  "theme.config.ts",
3426
3939
  "bundle-entry.ts",
3427
- "manifest.ts"
3428
- ].some((f) => fs__default.default.existsSync(path9__default.default.join(process.cwd(), f)));
3940
+ "manifest.ts",
3941
+ "next.config.ts",
3942
+ "next.config.js",
3943
+ "next.config.mjs"
3944
+ ].some((f) => fs8__default.default.existsSync(path13__default.default.join(process.cwd(), f)));
3429
3945
  if (isThemeDir2) {
3430
3946
  themePath = process.cwd();
3431
- themeName = path9__default.default.basename(themePath);
3947
+ themeName = path13__default.default.basename(themePath);
3432
3948
  logger.info(`Building current theme: ${themeName}`);
3433
3949
  } else {
3434
3950
  logger.error(
@@ -3437,8 +3953,8 @@ async function buildCommand(options) {
3437
3953
  process.exit(1);
3438
3954
  }
3439
3955
  }
3440
- const packageJsonPath = path9__default.default.join(themePath, "package.json");
3441
- const hasPkgJson = fs__default.default.existsSync(packageJsonPath);
3956
+ const packageJsonPath = path13__default.default.join(themePath, "package.json");
3957
+ const hasPkgJson = fs8__default.default.existsSync(packageJsonPath);
3442
3958
  if (!hasPkgJson) {
3443
3959
  logger.warning(
3444
3960
  "No package.json found in theme. Skipping build (themes in monorepo are built via turbo)."
@@ -3453,30 +3969,42 @@ async function buildCommand(options) {
3453
3969
  }
3454
3970
  logger.newLine();
3455
3971
  logger.section("Build Steps");
3456
- logger.startSpinner("Running type check...");
3457
- const typeCheckSuccess = await runCommand("pnpm", ["type-check"], themePath);
3458
- if (!typeCheckSuccess) {
3459
- logger.stopSpinner(false, "Type check failed");
3460
- logger.error("Fix type errors before building.");
3461
- process.exit(1);
3462
- }
3463
- logger.stopSpinner(true, "Type check passed");
3464
- logger.startSpinner("Running linter...");
3465
- const lintSuccess = await runCommand("pnpm", ["lint"], themePath);
3466
- if (!lintSuccess) {
3467
- logger.stopSpinner(false, "Lint failed");
3468
- logger.error("Fix lint errors before building.");
3469
- process.exit(1);
3972
+ const pkgJson = fs8__default.default.readJsonSync(packageJsonPath);
3973
+ if (pkgJson.scripts?.["type-check"]) {
3974
+ logger.startSpinner("Running type check...");
3975
+ const typeCheckSuccess = await runCommand("pnpm", ["type-check"], themePath);
3976
+ if (!typeCheckSuccess) {
3977
+ logger.stopSpinner(false, "Type check failed");
3978
+ logger.error("Fix type errors before building.");
3979
+ process.exit(1);
3980
+ }
3981
+ logger.stopSpinner(true, "Type check passed");
3982
+ } else {
3983
+ logger.info("Skipping type check (no type-check script in package.json)");
3984
+ }
3985
+ const isNextjsForLint = isNextjsProject(themePath);
3986
+ if (!isNextjsForLint && pkgJson.scripts?.lint) {
3987
+ logger.startSpinner("Running linter...");
3988
+ const lintSuccess = await runCommand("pnpm", ["lint"], themePath);
3989
+ if (!lintSuccess) {
3990
+ logger.stopSpinner(false, "Lint failed");
3991
+ logger.error("Fix lint errors before building.");
3992
+ process.exit(1);
3993
+ }
3994
+ logger.stopSpinner(true, "Lint passed");
3995
+ } else if (isNextjsForLint) {
3996
+ logger.info("Skipping lint (Next.js project compiled via esbuild)");
3997
+ } else {
3998
+ logger.info("Skipping lint (no lint script in package.json)");
3470
3999
  }
3471
- logger.stopSpinner(true, "Lint passed");
3472
- const pkgJson = fs__default.default.readJsonSync(packageJsonPath);
3473
4000
  const buildScript = pkgJson.scripts?.build || "";
3474
4001
  const isRecursive = buildScript.includes("onexthm build") || buildScript.includes("onex build") || buildScript.includes("onex-cli build");
4002
+ const isNextjs = isNextjsProject(themePath);
3475
4003
  logger.startSpinner(
3476
4004
  options.watch ? "Building (watch mode)..." : "Building..."
3477
4005
  );
3478
4006
  let buildSuccess;
3479
- if (isRecursive) {
4007
+ if (isRecursive || isNextjs) {
3480
4008
  const { compileStandaloneTheme: compileStandaloneTheme2 } = await Promise.resolve().then(() => (init_compile_theme(), compile_theme_exports));
3481
4009
  buildSuccess = await compileStandaloneTheme2(themePath, themeName);
3482
4010
  } else {
@@ -3493,10 +4021,10 @@ async function buildCommand(options) {
3493
4021
  logger.success("\u2713 Theme built successfully!");
3494
4022
  logger.newLine();
3495
4023
  logger.info(`Theme: ${themeName}`);
3496
- const distPath = path9__default.default.join(themePath, "dist");
3497
- if (fs__default.default.existsSync(distPath)) {
3498
- logger.log(`Output: ${path9__default.default.relative(process.cwd(), distPath)}`);
3499
- const files = fs__default.default.readdirSync(distPath);
4024
+ const distPath = path13__default.default.join(themePath, "dist");
4025
+ if (fs8__default.default.existsSync(distPath)) {
4026
+ logger.log(`Output: ${path13__default.default.relative(process.cwd(), distPath)}`);
4027
+ const files = fs8__default.default.readdirSync(distPath);
3500
4028
  logger.log(`Files: ${files.length}`);
3501
4029
  }
3502
4030
  }
@@ -3551,8 +4079,8 @@ async function packageCommand(options) {
3551
4079
  let themeName;
3552
4080
  if (options.theme) {
3553
4081
  themeName = options.theme;
3554
- themePath = path9__default.default.join(getThemesDir(), themeName);
3555
- if (!fs__default.default.existsSync(themePath)) {
4082
+ themePath = path13__default.default.join(getThemesDir(), themeName);
4083
+ if (!fs8__default.default.existsSync(themePath)) {
3556
4084
  logger.error(`Theme "${themeName}" not found.`);
3557
4085
  process.exit(1);
3558
4086
  }
@@ -3560,11 +4088,14 @@ async function packageCommand(options) {
3560
4088
  const isThemeDir2 = [
3561
4089
  "theme.config.ts",
3562
4090
  "bundle-entry.ts",
3563
- "manifest.ts"
3564
- ].some((f) => fs__default.default.existsSync(path9__default.default.join(process.cwd(), f)));
4091
+ "manifest.ts",
4092
+ "next.config.ts",
4093
+ "next.config.js",
4094
+ "next.config.mjs"
4095
+ ].some((f) => fs8__default.default.existsSync(path13__default.default.join(process.cwd(), f)));
3565
4096
  if (isThemeDir2) {
3566
4097
  themePath = process.cwd();
3567
- themeName = path9__default.default.basename(themePath);
4098
+ themeName = path13__default.default.basename(themePath);
3568
4099
  logger.info(`Packaging current theme: ${themeName}`);
3569
4100
  } else {
3570
4101
  logger.error(
@@ -3573,22 +4104,19 @@ async function packageCommand(options) {
3573
4104
  process.exit(1);
3574
4105
  }
3575
4106
  }
3576
- const packageJsonPath = path9__default.default.join(themePath, "package.json");
4107
+ const packageJsonPath = path13__default.default.join(themePath, "package.json");
3577
4108
  let version2 = "1.0.0";
3578
- if (fs__default.default.existsSync(packageJsonPath)) {
3579
- const packageJson = await fs__default.default.readJson(packageJsonPath);
4109
+ if (fs8__default.default.existsSync(packageJsonPath)) {
4110
+ const packageJson = await fs8__default.default.readJson(packageJsonPath);
3580
4111
  version2 = packageJson.version || "1.0.0";
3581
4112
  }
3582
4113
  logger.newLine();
3583
4114
  logger.info(`Theme: ${themeName}`);
3584
4115
  logger.info(`Version: ${version2}`);
3585
4116
  logger.newLine();
3586
- const compiledThemePath = path9__default.default.join(
3587
- process.cwd(),
3588
- "themes",
3589
- themeName,
3590
- "dist"
3591
- );
4117
+ const standaloneDistPath = path13__default.default.join(themePath, "dist");
4118
+ const monorepoDistPath = path13__default.default.join(process.cwd(), "themes", themeName, "dist");
4119
+ const compiledThemePath = fs8__default.default.existsSync(standaloneDistPath) ? standaloneDistPath : monorepoDistPath;
3592
4120
  if (!options.skipBuild) {
3593
4121
  logger.section("Step 1: Compile Theme");
3594
4122
  logger.startSpinner("Compiling theme with esbuild...");
@@ -3609,7 +4137,7 @@ async function packageCommand(options) {
3609
4137
  } else {
3610
4138
  logger.info("Skipping build (--skip-build flag)");
3611
4139
  }
3612
- if (!fs__default.default.existsSync(compiledThemePath)) {
4140
+ if (!fs8__default.default.existsSync(compiledThemePath)) {
3613
4141
  logger.error(`Compiled theme not found at: ${compiledThemePath}`);
3614
4142
  logger.info("Run without --skip-build to compile first.");
3615
4143
  process.exit(1);
@@ -3617,25 +4145,25 @@ async function packageCommand(options) {
3617
4145
  logger.newLine();
3618
4146
  logger.section("Step 2: Create Package");
3619
4147
  const packageName = options.name || `${themeName}-${version2}`;
3620
- const outputDir = options.output || path9__default.default.join(process.cwd(), "dist");
3621
- const outputPath = path9__default.default.join(outputDir, `${packageName}.zip`);
3622
- await fs__default.default.ensureDir(outputDir);
4148
+ const outputDir = options.output || path13__default.default.join(process.cwd(), "dist");
4149
+ const outputPath = path13__default.default.join(outputDir, `${packageName}.zip`);
4150
+ await fs8__default.default.ensureDir(outputDir);
3623
4151
  logger.startSpinner("Creating zip archive...");
3624
4152
  try {
3625
4153
  await createZipArchive(compiledThemePath, outputPath);
3626
4154
  logger.stopSpinner(true, "Package created");
3627
- const stats = await fs__default.default.stat(outputPath);
4155
+ const stats = await fs8__default.default.stat(outputPath);
3628
4156
  const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
3629
4157
  logger.newLine();
3630
4158
  logger.success("\u2713 Theme packaged successfully!");
3631
4159
  logger.newLine();
3632
4160
  logger.info(`Package: ${packageName}.zip`);
3633
4161
  logger.log(`Size: ${sizeMB} MB`);
3634
- logger.log(`Location: ${path9__default.default.relative(process.cwd(), outputPath)}`);
4162
+ logger.log(`Location: ${path13__default.default.relative(process.cwd(), outputPath)}`);
3635
4163
  logger.newLine();
3636
4164
  logger.section("Next steps:");
3637
4165
  logger.log(
3638
- ` onexthm deploy --package ${path9__default.default.relative(process.cwd(), outputPath)}`
4166
+ ` onexthm deploy --package ${path13__default.default.relative(process.cwd(), outputPath)}`
3639
4167
  );
3640
4168
  } catch (error) {
3641
4169
  logger.stopSpinner(false, "Failed to create package");
@@ -3669,7 +4197,7 @@ function runCommand2(command, args) {
3669
4197
  }
3670
4198
  async function createZipArchive(compiledThemePath, outputPath) {
3671
4199
  return new Promise((resolve, reject) => {
3672
- const output = fs__default.default.createWriteStream(outputPath);
4200
+ const output = fs8__default.default.createWriteStream(outputPath);
3673
4201
  const archive = archiver__default.default("zip", {
3674
4202
  zlib: { level: 9 }
3675
4203
  // Maximum compression
@@ -3693,14 +4221,14 @@ async function deployCommand(options) {
3693
4221
  ensureOneXProject();
3694
4222
  let packagePath;
3695
4223
  if (options.package) {
3696
- packagePath = path9__default.default.resolve(options.package);
4224
+ packagePath = path13__default.default.resolve(options.package);
3697
4225
  } else if (options.theme) {
3698
- const distDir = path9__default.default.join(process.cwd(), "dist");
3699
- if (!fs__default.default.existsSync(distDir)) {
4226
+ const distDir = path13__default.default.join(process.cwd(), "dist");
4227
+ if (!fs8__default.default.existsSync(distDir)) {
3700
4228
  logger.error("No dist/ directory found. Run 'onexthm package' first.");
3701
4229
  process.exit(1);
3702
4230
  }
3703
- const files = fs__default.default.readdirSync(distDir);
4231
+ const files = fs8__default.default.readdirSync(distDir);
3704
4232
  const packageFiles = files.filter(
3705
4233
  (f) => f.startsWith(options.theme) && f.endsWith(".zip")
3706
4234
  );
@@ -3710,7 +4238,7 @@ async function deployCommand(options) {
3710
4238
  process.exit(1);
3711
4239
  }
3712
4240
  packageFiles.sort().reverse();
3713
- packagePath = path9__default.default.join(distDir, packageFiles[0]);
4241
+ packagePath = path13__default.default.join(distDir, packageFiles[0]);
3714
4242
  } else {
3715
4243
  logger.error("Either --package or --theme must be specified.");
3716
4244
  logger.info("Examples:");
@@ -3718,17 +4246,17 @@ async function deployCommand(options) {
3718
4246
  logger.log(" onexthm deploy --theme tinan");
3719
4247
  process.exit(1);
3720
4248
  }
3721
- if (!fs__default.default.existsSync(packagePath)) {
4249
+ if (!fs8__default.default.existsSync(packagePath)) {
3722
4250
  logger.error(`Package not found: ${packagePath}`);
3723
4251
  process.exit(1);
3724
4252
  }
3725
- const stats = await fs__default.default.stat(packagePath);
4253
+ const stats = await fs8__default.default.stat(packagePath);
3726
4254
  const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
3727
- const fileName = path9__default.default.basename(packagePath);
4255
+ const fileName = path13__default.default.basename(packagePath);
3728
4256
  logger.newLine();
3729
4257
  logger.info(`Package: ${fileName}`);
3730
4258
  logger.log(`Size: ${sizeMB} MB`);
3731
- logger.log(`Path: ${path9__default.default.relative(process.cwd(), packagePath)}`);
4259
+ logger.log(`Path: ${path13__default.default.relative(process.cwd(), packagePath)}`);
3732
4260
  logger.newLine();
3733
4261
  const apiUrl = options.apiUrl || process.env.ONEX_API_URL || "http://localhost:3001";
3734
4262
  const uploadEndpoint = `${apiUrl}/website-api/themes/upload`;
@@ -3738,7 +4266,7 @@ async function deployCommand(options) {
3738
4266
  logger.startSpinner("Uploading theme package...");
3739
4267
  try {
3740
4268
  const formData = new FormData__default.default();
3741
- formData.append("theme", fs__default.default.createReadStream(packagePath), {
4269
+ formData.append("theme", fs8__default.default.createReadStream(packagePath), {
3742
4270
  filename: fileName,
3743
4271
  contentType: "application/zip"
3744
4272
  });
@@ -3885,24 +4413,24 @@ async function downloadBundleZip(apiUrl, themeId, version2) {
3885
4413
  async function createCompatibilityFiles(outputDir, manifest) {
3886
4414
  const entryFile = manifest.output?.entry || "bundle-entry.js";
3887
4415
  if (entryFile !== "bundle-entry.js" && entryFile.startsWith("bundle-entry-")) {
3888
- const hashedPath = path9__default.default.join(outputDir, entryFile);
3889
- const stablePath = path9__default.default.join(outputDir, "bundle-entry.js");
3890
- if (await fs__default.default.pathExists(hashedPath)) {
3891
- await fs__default.default.copy(hashedPath, stablePath);
4416
+ const hashedPath = path13__default.default.join(outputDir, entryFile);
4417
+ const stablePath = path13__default.default.join(outputDir, "bundle-entry.js");
4418
+ if (await fs8__default.default.pathExists(hashedPath)) {
4419
+ await fs8__default.default.copy(hashedPath, stablePath);
3892
4420
  const mapPath = hashedPath + ".map";
3893
- if (await fs__default.default.pathExists(mapPath)) {
3894
- await fs__default.default.copy(mapPath, stablePath + ".map");
4421
+ if (await fs8__default.default.pathExists(mapPath)) {
4422
+ await fs8__default.default.copy(mapPath, stablePath + ".map");
3895
4423
  }
3896
4424
  }
3897
4425
  }
3898
- const sectionsRegistryPath = path9__default.default.join(outputDir, "sections-registry.js");
4426
+ const sectionsRegistryPath = path13__default.default.join(outputDir, "sections-registry.js");
3899
4427
  const content = `// Re-export all sections from bundle-entry
3900
4428
  // This file exists to maintain compatibility with the import path
3901
4429
  export * from './bundle-entry.js';
3902
4430
  `;
3903
- await fs__default.default.writeFile(sectionsRegistryPath, content, "utf-8");
3904
- const pkgJsonPath = path9__default.default.join(outputDir, "package.json");
3905
- await fs__default.default.writeFile(pkgJsonPath, '{\n "type": "module"\n}\n', "utf-8");
4431
+ await fs8__default.default.writeFile(sectionsRegistryPath, content, "utf-8");
4432
+ const pkgJsonPath = path13__default.default.join(outputDir, "package.json");
4433
+ await fs8__default.default.writeFile(pkgJsonPath, '{\n "type": "module"\n}\n', "utf-8");
3906
4434
  }
3907
4435
  function showDownloadFailureHelp(themeId, apiUrl) {
3908
4436
  console.log();
@@ -3942,7 +4470,7 @@ function showDownloadFailureHelp(themeId, apiUrl) {
3942
4470
  }
3943
4471
  async function downloadCommand(options) {
3944
4472
  logger.header("Download Theme");
3945
- const env = options.env ?? "dev";
4473
+ const env = options.env;
3946
4474
  const apiUrl = getApiUrl(env);
3947
4475
  logger.info(`Environment: ${env} (${apiUrl})`);
3948
4476
  const spinner = ora__default.default("Initializing download...").start();
@@ -3994,14 +4522,14 @@ async function downloadCommand(options) {
3994
4522
  const sizeMB = (zipBuffer.length / 1024 / 1024).toFixed(2);
3995
4523
  spinner.succeed(`Downloaded bundle.zip (${sizeMB} MB)`);
3996
4524
  spinner.start("Extracting bundle...");
3997
- await fs__default.default.remove(outputDir);
3998
- await fs__default.default.ensureDir(outputDir);
4525
+ await fs8__default.default.remove(outputDir);
4526
+ await fs8__default.default.ensureDir(outputDir);
3999
4527
  const zip = new AdmZip__default.default(zipBuffer);
4000
4528
  zip.extractAllTo(outputDir, true);
4001
4529
  const entries = zip.getEntries().filter((e) => !e.isDirectory);
4002
4530
  spinner.succeed(`Extracted ${entries.length} files to ${outputDir}`);
4003
- const manifestPath = path9__default.default.join(outputDir, "manifest.json");
4004
- const manifest = await fs__default.default.readJson(manifestPath);
4531
+ const manifestPath = path13__default.default.join(outputDir, "manifest.json");
4532
+ const manifest = await fs8__default.default.readJson(manifestPath);
4005
4533
  await createCompatibilityFiles(outputDir, manifest);
4006
4534
  console.log();
4007
4535
  logger.success(chalk4__default.default.green.bold("Theme downloaded successfully!"));
@@ -4121,9 +4649,9 @@ async function renameTheme(themeDir, oldName, newName) {
4121
4649
  const oldPrefix = `${oldName}-`;
4122
4650
  const newPrefix = `${newName}-`;
4123
4651
  const newDisplayName = newName.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
4124
- const pkgPath = path9__default.default.join(themeDir, "package.json");
4125
- if (await fs__default.default.pathExists(pkgPath)) {
4126
- const pkg = await fs__default.default.readJson(pkgPath);
4652
+ const pkgPath = path13__default.default.join(themeDir, "package.json");
4653
+ if (await fs8__default.default.pathExists(pkgPath)) {
4654
+ const pkg = await fs8__default.default.readJson(pkgPath);
4127
4655
  pkg.name = `@onex-themes/${newName}`;
4128
4656
  if (pkg.description) {
4129
4657
  pkg.description = pkg.description.replace(
@@ -4135,33 +4663,33 @@ async function renameTheme(themeDir, oldName, newName) {
4135
4663
  if (pkg.devDependencies?.["@onexapis/cli"]) {
4136
4664
  delete pkg.devDependencies["@onexapis/cli"];
4137
4665
  }
4138
- await fs__default.default.writeJson(pkgPath, pkg, { spaces: 2 });
4666
+ await fs8__default.default.writeJson(pkgPath, pkg, { spaces: 2 });
4139
4667
  }
4140
- const configPath = path9__default.default.join(themeDir, "theme.config.ts");
4141
- if (await fs__default.default.pathExists(configPath)) {
4142
- let content = await fs__default.default.readFile(configPath, "utf-8");
4668
+ const configPath = path13__default.default.join(themeDir, "theme.config.ts");
4669
+ if (await fs8__default.default.pathExists(configPath)) {
4670
+ let content = await fs8__default.default.readFile(configPath, "utf-8");
4143
4671
  content = content.replace(/id:\s*"[^"]*"/, `id: "${newName}"`);
4144
4672
  content = content.replace(
4145
4673
  /name:\s*"[^"]*Theme"/,
4146
4674
  `name: "${newDisplayName} Theme"`
4147
4675
  );
4148
- await fs__default.default.writeFile(configPath, content);
4676
+ await fs8__default.default.writeFile(configPath, content);
4149
4677
  }
4150
- const layoutPath = path9__default.default.join(themeDir, "theme.layout.ts");
4151
- if (await fs__default.default.pathExists(layoutPath)) {
4152
- let content = await fs__default.default.readFile(layoutPath, "utf-8");
4678
+ const layoutPath = path13__default.default.join(themeDir, "theme.layout.ts");
4679
+ if (await fs8__default.default.pathExists(layoutPath)) {
4680
+ let content = await fs8__default.default.readFile(layoutPath, "utf-8");
4153
4681
  content = content.replace(/id:\s*"[^"]*"/, `id: "${newName}"`);
4154
4682
  content = content.replace(
4155
4683
  /name:\s*"[^"]*Theme"/,
4156
4684
  `name: "${newDisplayName} Theme"`
4157
4685
  );
4158
- await fs__default.default.writeFile(layoutPath, content);
4686
+ await fs8__default.default.writeFile(layoutPath, content);
4159
4687
  }
4160
4688
  const oldDisplayName = oldName.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
4161
4689
  const tsFiles = await glob.glob("**/*.ts", { cwd: themeDir, nodir: true });
4162
4690
  for (const file of tsFiles) {
4163
- const filePath = path9__default.default.join(themeDir, file);
4164
- let content = await fs__default.default.readFile(filePath, "utf-8");
4691
+ const filePath = path13__default.default.join(themeDir, file);
4692
+ let content = await fs8__default.default.readFile(filePath, "utf-8");
4165
4693
  const original = content;
4166
4694
  content = content.replace(
4167
4695
  new RegExp(`"${oldPrefix}`, "g"),
@@ -4176,13 +4704,13 @@ async function renameTheme(themeDir, oldName, newName) {
4176
4704
  `${newDisplayName} Theme`
4177
4705
  );
4178
4706
  if (content !== original) {
4179
- await fs__default.default.writeFile(filePath, content);
4707
+ await fs8__default.default.writeFile(filePath, content);
4180
4708
  }
4181
4709
  }
4182
4710
  }
4183
4711
  async function cloneCommand(themeName, options) {
4184
4712
  logger.header("Clone Theme Source");
4185
- const env = options.env ?? "dev";
4713
+ const env = options.env;
4186
4714
  const apiUrl = getApiUrl(env);
4187
4715
  logger.info(`Environment: ${env} (${apiUrl})`);
4188
4716
  if (options.bucket) {
@@ -4203,8 +4731,8 @@ async function cloneCommand(themeName, options) {
4203
4731
  }
4204
4732
  const spinner = ora__default.default("Initializing clone...").start();
4205
4733
  try {
4206
- const outputDir = options.output || path9__default.default.resolve(process.cwd(), newName);
4207
- if (await fs__default.default.pathExists(outputDir)) {
4734
+ const outputDir = options.output || path13__default.default.resolve(process.cwd(), newName);
4735
+ if (await fs8__default.default.pathExists(outputDir)) {
4208
4736
  spinner.fail(chalk4__default.default.red(`Directory already exists: ${outputDir}`));
4209
4737
  logger.info(
4210
4738
  chalk4__default.default.gray(
@@ -4237,7 +4765,7 @@ async function cloneCommand(themeName, options) {
4237
4765
  const sizeMB = (zipBuffer.length / 1024 / 1024).toFixed(2);
4238
4766
  spinner.succeed(`Downloaded source.zip (${sizeMB} MB)`);
4239
4767
  spinner.start(`Extracting to ${outputDir}...`);
4240
- await fs__default.default.ensureDir(outputDir);
4768
+ await fs8__default.default.ensureDir(outputDir);
4241
4769
  const zip = new AdmZip__default.default(zipBuffer);
4242
4770
  zip.extractAllTo(outputDir, true);
4243
4771
  const entries = zip.getEntries().filter((e) => !e.isDirectory);
@@ -4249,9 +4777,9 @@ async function cloneCommand(themeName, options) {
4249
4777
  spinner.succeed(
4250
4778
  `Renamed theme: ${chalk4__default.default.gray(themeName)} \u2192 ${chalk4__default.default.cyan(newName)}`
4251
4779
  );
4252
- const envExamplePath = path9__default.default.join(outputDir, ".env.example");
4253
- if (!await fs__default.default.pathExists(envExamplePath)) {
4254
- await fs__default.default.writeFile(
4780
+ const envExamplePath = path13__default.default.join(outputDir, ".env.example");
4781
+ if (!await fs8__default.default.pathExists(envExamplePath)) {
4782
+ await fs8__default.default.writeFile(
4255
4783
  envExamplePath,
4256
4784
  [
4257
4785
  "# API Configuration (enables real data in preview)",
@@ -4262,8 +4790,8 @@ async function cloneCommand(themeName, options) {
4262
4790
  ].join("\n")
4263
4791
  );
4264
4792
  }
4265
- const mcpJsonPath = path9__default.default.join(outputDir, ".mcp.json");
4266
- if (await fs__default.default.pathExists(mcpJsonPath)) {
4793
+ const mcpJsonPath = path13__default.default.join(outputDir, ".mcp.json");
4794
+ if (await fs8__default.default.pathExists(mcpJsonPath)) {
4267
4795
  const { default: inquirerMod } = await import('inquirer');
4268
4796
  const { figmaApiKey } = await inquirerMod.prompt([
4269
4797
  {
@@ -4272,7 +4800,7 @@ async function cloneCommand(themeName, options) {
4272
4800
  message: "Figma API Key (optional, for Figma-to-code MCP \u2014 press Enter to skip):"
4273
4801
  }
4274
4802
  ]);
4275
- let mcpContent = await fs__default.default.readFile(mcpJsonPath, "utf-8");
4803
+ let mcpContent = await fs8__default.default.readFile(mcpJsonPath, "utf-8");
4276
4804
  if (figmaApiKey) {
4277
4805
  mcpContent = mcpContent.replace("__FIGMA_API_KEY__", figmaApiKey);
4278
4806
  } else {
@@ -4283,11 +4811,11 @@ async function cloneCommand(themeName, options) {
4283
4811
  } catch {
4284
4812
  }
4285
4813
  }
4286
- await fs__default.default.writeFile(mcpJsonPath, mcpContent, "utf-8");
4814
+ await fs8__default.default.writeFile(mcpJsonPath, mcpContent, "utf-8");
4287
4815
  }
4288
4816
  if (options.install !== false) {
4289
- const hasPkgJson = await fs__default.default.pathExists(
4290
- path9__default.default.join(outputDir, "package.json")
4817
+ const hasPkgJson = await fs8__default.default.pathExists(
4818
+ path13__default.default.join(outputDir, "package.json")
4291
4819
  );
4292
4820
  if (hasPkgJson) {
4293
4821
  spinner.start("Installing dependencies...");
@@ -4315,7 +4843,7 @@ async function cloneCommand(themeName, options) {
4315
4843
  console.log(chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(entries.length));
4316
4844
  console.log();
4317
4845
  console.log(chalk4__default.default.cyan("Next steps:"));
4318
- console.log(chalk4__default.default.gray(` cd ${path9__default.default.relative(process.cwd(), outputDir)}`));
4846
+ console.log(chalk4__default.default.gray(` cd ${path13__default.default.relative(process.cwd(), outputDir)}`));
4319
4847
  console.log(
4320
4848
  chalk4__default.default.gray(" cp .env.example .env # then add your Company ID")
4321
4849
  );
@@ -4342,16 +4870,16 @@ async function devCommand(options) {
4342
4870
  if (options.theme) {
4343
4871
  themeName = options.theme;
4344
4872
  try {
4345
- const workspaceThemePath = path9__default.default.join(getThemesDir(), themeName);
4346
- if (fs__default.default.existsSync(workspaceThemePath)) {
4873
+ const workspaceThemePath = path13__default.default.join(getThemesDir(), themeName);
4874
+ if (fs8__default.default.existsSync(workspaceThemePath)) {
4347
4875
  themePath = workspaceThemePath;
4348
4876
  } else {
4349
- themePath = path9__default.default.join(process.cwd(), themeName);
4877
+ themePath = path13__default.default.join(process.cwd(), themeName);
4350
4878
  }
4351
4879
  } catch {
4352
- themePath = path9__default.default.join(process.cwd(), themeName);
4880
+ themePath = path13__default.default.join(process.cwd(), themeName);
4353
4881
  }
4354
- if (!fs__default.default.existsSync(themePath)) {
4882
+ if (!fs8__default.default.existsSync(themePath)) {
4355
4883
  logger.error(`Theme "${themeName}" not found.`);
4356
4884
  process.exit(1);
4357
4885
  }
@@ -4359,11 +4887,14 @@ async function devCommand(options) {
4359
4887
  const isThemeDir2 = [
4360
4888
  "theme.config.ts",
4361
4889
  "bundle-entry.ts",
4362
- "manifest.ts"
4363
- ].some((f) => fs__default.default.existsSync(path9__default.default.join(process.cwd(), f)));
4890
+ "manifest.ts",
4891
+ "next.config.ts",
4892
+ "next.config.js",
4893
+ "next.config.mjs"
4894
+ ].some((f) => fs8__default.default.existsSync(path13__default.default.join(process.cwd(), f)));
4364
4895
  if (isThemeDir2) {
4365
4896
  themePath = process.cwd();
4366
- themeName = path9__default.default.basename(themePath);
4897
+ themeName = path13__default.default.basename(themePath);
4367
4898
  } else {
4368
4899
  logger.error(
4369
4900
  "Not in a theme directory and no --theme specified. Run from theme root or use --theme flag."
@@ -4432,9 +4963,9 @@ async function devCommand(options) {
4432
4963
  watcher.close();
4433
4964
  await context2.dispose();
4434
4965
  server.close();
4435
- const shimPath = path9__default.default.join(outputDir, ".process-shim.js");
4966
+ const shimPath = path13__default.default.join(outputDir, ".process-shim.js");
4436
4967
  try {
4437
- await fs8__default.default.unlink(shimPath);
4968
+ await fs11__default.default.unlink(shimPath);
4438
4969
  } catch {
4439
4970
  }
4440
4971
  process.exit(0);
@@ -4443,8 +4974,8 @@ async function devCommand(options) {
4443
4974
 
4444
4975
  // src/commands/config.ts
4445
4976
  init_logger();
4446
- var CONFIG_DIR = path9__default.default.join(os__default.default.homedir(), ".onexthm");
4447
- var CONFIG_FILE = path9__default.default.join(CONFIG_DIR, ".env");
4977
+ var CONFIG_DIR = path13__default.default.join(os__default.default.homedir(), ".onexthm");
4978
+ var CONFIG_FILE = path13__default.default.join(CONFIG_DIR, ".env");
4448
4979
  var CONFIG_ENTRIES = [
4449
4980
  {
4450
4981
  key: "AWS_ACCESS_KEY_ID",
@@ -4530,7 +5061,7 @@ async function configCommand() {
4530
5061
  logger.header("OneX CLI Configuration");
4531
5062
  let existing = {};
4532
5063
  try {
4533
- const content = await fs__default.default.readFile(CONFIG_FILE, "utf-8");
5064
+ const content = await fs8__default.default.readFile(CONFIG_FILE, "utf-8");
4534
5065
  existing = parseEnvFile(content);
4535
5066
  logger.info(`Existing config found at: ${CONFIG_FILE}`);
4536
5067
  logger.newLine();
@@ -4566,8 +5097,8 @@ async function configCommand() {
4566
5097
  for (const key of Object.keys(merged)) {
4567
5098
  if (!merged[key]) delete merged[key];
4568
5099
  }
4569
- await fs__default.default.ensureDir(CONFIG_DIR);
4570
- await fs__default.default.writeFile(CONFIG_FILE, serializeEnv(merged));
5100
+ await fs8__default.default.ensureDir(CONFIG_DIR);
5101
+ await fs8__default.default.writeFile(CONFIG_FILE, serializeEnv(merged));
4571
5102
  logger.newLine();
4572
5103
  logger.success(`Config saved to: ${CONFIG_FILE}`);
4573
5104
  logger.newLine();
@@ -4586,8 +5117,8 @@ async function configCommand() {
4586
5117
 
4587
5118
  // src/commands/login.ts
4588
5119
  init_logger();
4589
- async function loginCommand(options = {}) {
4590
- const env = options.env ?? "dev";
5120
+ async function loginCommand(options) {
5121
+ const env = options.env;
4591
5122
  const apiUrl = getApiUrl(env);
4592
5123
  logger.header("OneX Theme Developer Login");
4593
5124
  logger.info(`Environment: ${env} (${apiUrl})`);
@@ -4676,8 +5207,8 @@ async function loginCommand(options = {}) {
4676
5207
 
4677
5208
  // src/commands/logout.ts
4678
5209
  init_logger();
4679
- async function logoutCommand(options = {}) {
4680
- const env = options.env ?? "dev";
5210
+ async function logoutCommand(options) {
5211
+ const env = options.env;
4681
5212
  const tokens = loadAuthTokens(env);
4682
5213
  if (!tokens) {
4683
5214
  logger.info(`Not logged in to ${env} environment.`);
@@ -4689,8 +5220,8 @@ async function logoutCommand(options = {}) {
4689
5220
 
4690
5221
  // src/commands/whoami.ts
4691
5222
  init_logger();
4692
- async function whoamiCommand(options = {}) {
4693
- const env = options.env ?? "dev";
5223
+ async function whoamiCommand(options) {
5224
+ const env = options.env;
4694
5225
  const tokens = loadAuthTokens(env);
4695
5226
  if (!tokens) {
4696
5227
  logger.error(
@@ -4712,96 +5243,362 @@ async function whoamiCommand(options = {}) {
4712
5243
 
4713
5244
  // src/commands/publish.ts
4714
5245
  init_logger();
4715
- var MIME_MAP = {
4716
- ".png": "image/png",
4717
- ".jpg": "image/jpeg",
4718
- ".jpeg": "image/jpeg",
4719
- ".gif": "image/gif",
4720
- ".webp": "image/webp",
4721
- ".avif": "image/avif",
4722
- ".svg": "image/svg+xml",
4723
- ".ico": "image/x-icon",
4724
- ".bmp": "image/bmp",
4725
- ".woff": "font/woff",
4726
- ".woff2": "font/woff2",
4727
- ".ttf": "font/ttf",
4728
- ".otf": "font/otf",
4729
- ".eot": "application/vnd.ms-fontobject",
4730
- ".mp4": "video/mp4",
4731
- ".webm": "video/webm",
4732
- ".mov": "video/quicktime",
4733
- ".ogg": "video/ogg",
4734
- ".json": "application/json"
4735
- };
4736
- var HASH_LEN = 8;
4737
- var VIDEO_EXTENSIONS = [
4738
- ".mp4",
4739
- ".webm",
4740
- ".ogg",
4741
- ".mov",
4742
- ".avi",
4743
- ".mkv"
4744
- ];
4745
- function isVideoAsset(filePath) {
4746
- const lower = filePath.toLowerCase();
4747
- return VIDEO_EXTENSIONS.some((ext) => lower.endsWith(ext));
5246
+ init_scan_theme_assets();
5247
+
5248
+ // src/utils/fetch-prior-schemas.ts
5249
+ async function fetchPriorGateManifests(themeId, env) {
5250
+ const apiUrl = getApiUrl(env);
5251
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/gate-manifests/latest`;
5252
+ let res;
5253
+ try {
5254
+ res = await authenticatedFetch(url, { method: "GET" }, env);
5255
+ } catch (err) {
5256
+ return {
5257
+ result: null,
5258
+ reason: `network error: ${err instanceof Error ? err.message : "unknown"}`
5259
+ };
5260
+ }
5261
+ if (res.status === 404) {
5262
+ return { result: null, reason: "no-prior" };
5263
+ }
5264
+ if (!res.ok) {
5265
+ return {
5266
+ result: null,
5267
+ reason: `server returned ${res.status} ${res.statusText}`
5268
+ };
5269
+ }
5270
+ let data;
5271
+ try {
5272
+ data = await res.json();
5273
+ } catch {
5274
+ return { result: null, reason: "non-JSON response from server" };
5275
+ }
5276
+ const body = data.statusCode ? data.body : data;
5277
+ if (!body || typeof body.version !== "string" || !body.schemas || !body.assets) {
5278
+ return { result: null, reason: "malformed response (missing fields)" };
5279
+ }
5280
+ return {
5281
+ result: {
5282
+ version: body.version,
5283
+ schemas: body.schemas,
5284
+ assets: body.assets
5285
+ },
5286
+ reason: null
5287
+ };
4748
5288
  }
4749
- function mimeFor(filename) {
4750
- const ext = path9__default.default.extname(filename).toLowerCase();
4751
- return MIME_MAP[ext] || "application/octet-stream";
5289
+
5290
+ // src/utils/schema-diff.ts
5291
+ var SEVERITY = {
5292
+ safe: 0,
5293
+ "safe-rename": 1,
5294
+ "defaults-only": 2,
5295
+ additive: 3,
5296
+ breaking: 4,
5297
+ "breaking-asset": 5,
5298
+ "breaking-severe": 6
5299
+ };
5300
+ var BUMP_FOR = {
5301
+ safe: "patch",
5302
+ "safe-rename": "patch",
5303
+ "defaults-only": "patch",
5304
+ additive: "minor",
5305
+ breaking: "major",
5306
+ "breaking-asset": "major",
5307
+ "breaking-severe": "major"
5308
+ };
5309
+ function bumpFor(kind) {
5310
+ return BUMP_FOR[kind];
5311
+ }
5312
+ function maxSeverity(a, b) {
5313
+ return SEVERITY[a] >= SEVERITY[b] ? a : b;
5314
+ }
5315
+ function classify(changes) {
5316
+ let highest = "safe";
5317
+ for (const c of changes) {
5318
+ highest = maxSeverity(highest, c.kind);
5319
+ }
5320
+ return { bump: bumpFor(highest), highest, changes };
5321
+ }
5322
+ function diffManifests(prior, current) {
5323
+ const changes = [];
5324
+ const priorSections = prior.schemas.sections;
5325
+ const currentSections = current.schemas.sections;
5326
+ const sectionTypes = /* @__PURE__ */ new Set([
5327
+ ...Object.keys(priorSections),
5328
+ ...Object.keys(currentSections)
5329
+ ]);
5330
+ for (const type of [...sectionTypes].sort()) {
5331
+ const p = priorSections[type];
5332
+ const c = currentSections[type];
5333
+ if (p && !c) {
5334
+ changes.push({
5335
+ kind: "breaking-severe",
5336
+ path: `sections.${type}`,
5337
+ detail: `Section type "${type}" removed. Pages using this section will render empty.`
5338
+ });
5339
+ continue;
5340
+ }
5341
+ if (!p && c) {
5342
+ changes.push({
5343
+ kind: "additive",
5344
+ path: `sections.${type}`,
5345
+ detail: `Section type "${type}" added.`
5346
+ });
5347
+ continue;
5348
+ }
5349
+ if (p && c) diffSection(p, c, changes);
5350
+ }
5351
+ diffAssets(prior.assets, current.assets, changes);
5352
+ return changes;
4752
5353
  }
4753
- async function sha256Prefix(absPath, len) {
4754
- const buf = await fs__default.default.readFile(absPath);
4755
- return crypto__default.default.createHash("sha256").update(buf).digest("hex").slice(0, len);
5354
+ function diffSection(prior, current, out) {
5355
+ const type = current.type;
5356
+ if (JSON.stringify(prior.dataRequirements) !== JSON.stringify(current.dataRequirements)) {
5357
+ out.push({
5358
+ kind: "breaking",
5359
+ path: `sections.${type}.dataRequirements`,
5360
+ detail: "dataRequirements changed."
5361
+ });
5362
+ }
5363
+ diffFieldList(
5364
+ prior.settings,
5365
+ current.settings,
5366
+ `sections.${type}.settings`,
5367
+ out
5368
+ );
5369
+ diffDefaults(
5370
+ prior.defaults,
5371
+ current.defaults,
5372
+ `sections.${type}.defaults`,
5373
+ out
5374
+ );
5375
+ diffBlocks(prior.blocks, current.blocks, `sections.${type}.blocks`, out);
5376
+ }
5377
+ function diffBlocks(prior, current, pathPrefix, out) {
5378
+ const priorByType = new Map(prior.map((b) => [b.type, b]));
5379
+ const currentByType = new Map(current.map((b) => [b.type, b]));
5380
+ for (const type of /* @__PURE__ */ new Set([
5381
+ ...priorByType.keys(),
5382
+ ...currentByType.keys()
5383
+ ])) {
5384
+ const p = priorByType.get(type);
5385
+ const c = currentByType.get(type);
5386
+ if (p && !c) {
5387
+ out.push({
5388
+ kind: "breaking",
5389
+ path: `${pathPrefix}.${type}`,
5390
+ detail: `Block type "${type}" removed.`
5391
+ });
5392
+ continue;
5393
+ }
5394
+ if (!p && c) {
5395
+ out.push({
5396
+ kind: "additive",
5397
+ path: `${pathPrefix}.${type}`,
5398
+ detail: `Block type "${type}" added.`
5399
+ });
5400
+ continue;
5401
+ }
5402
+ if (p && c) {
5403
+ diffFieldList(
5404
+ p.settings,
5405
+ c.settings,
5406
+ `${pathPrefix}.${type}.settings`,
5407
+ out
5408
+ );
5409
+ diffDefaults(
5410
+ p.defaults,
5411
+ c.defaults,
5412
+ `${pathPrefix}.${type}.defaults`,
5413
+ out
5414
+ );
5415
+ }
5416
+ }
4756
5417
  }
4757
- function insertHashIntoName(relPath, hash) {
4758
- const dir = path9__default.default.posix.dirname(relPath);
4759
- const base = path9__default.default.posix.basename(relPath);
4760
- const ext = path9__default.default.posix.extname(base);
4761
- const stem = ext ? base.slice(0, -ext.length) : base;
4762
- const hashed = `${stem}-${hash}${ext}`;
4763
- return dir === "." ? hashed : `${dir}/${hashed}`;
5418
+ function diffFieldList(prior, current, pathPrefix, out) {
5419
+ const priorById = new Map(prior.map((f) => [f.id, f]));
5420
+ const currentById = new Map(current.map((f) => [f.id, f]));
5421
+ const aliasToCurrent = /* @__PURE__ */ new Map();
5422
+ for (const f of current) {
5423
+ if (f.aliases) {
5424
+ for (const alias of f.aliases) {
5425
+ aliasToCurrent.set(alias, f);
5426
+ }
5427
+ }
5428
+ }
5429
+ for (const [id, p] of priorById) {
5430
+ const c = currentById.get(id);
5431
+ if (c) {
5432
+ diffFieldPair(p, c, `${pathPrefix}.${id}`, out);
5433
+ continue;
5434
+ }
5435
+ const renamed = aliasToCurrent.get(id);
5436
+ if (renamed) {
5437
+ if (renamed.type === p.type) {
5438
+ out.push({
5439
+ kind: "safe-rename",
5440
+ path: `${pathPrefix}.${id}`,
5441
+ detail: `Field "${id}" renamed to "${renamed.id}" (alias preserved).`
5442
+ });
5443
+ } else {
5444
+ out.push({
5445
+ kind: "breaking",
5446
+ path: `${pathPrefix}.${id}`,
5447
+ detail: `Field "${id}" renamed to "${renamed.id}" but type changed (${p.type} \u2192 ${renamed.type}).`
5448
+ });
5449
+ }
5450
+ } else {
5451
+ out.push({
5452
+ kind: "breaking",
5453
+ path: `${pathPrefix}.${id}`,
5454
+ detail: `Field "${id}" removed. Consider adding aliases: ["${id}"] to the replacement field if this was a rename.`
5455
+ });
5456
+ }
5457
+ }
5458
+ for (const [id, c] of currentById) {
5459
+ if (priorById.has(id)) continue;
5460
+ const coveredByAlias = c.aliases?.some((a) => priorById.has(a)) ?? false;
5461
+ if (coveredByAlias) continue;
5462
+ if (c.required && c.default === void 0) {
5463
+ out.push({
5464
+ kind: "breaking",
5465
+ path: `${pathPrefix}.${id}`,
5466
+ detail: `Required field "${id}" added with no default. Existing instances cannot satisfy it.`
5467
+ });
5468
+ } else {
5469
+ out.push({
5470
+ kind: "additive",
5471
+ path: `${pathPrefix}.${id}`,
5472
+ detail: `Field "${id}" added.`
5473
+ });
5474
+ }
5475
+ }
4764
5476
  }
4765
- async function scanThemeAssets(distDir) {
4766
- const assetsDir = path9__default.default.join(distDir, "theme-assets");
4767
- if (!await fs__default.default.pathExists(assetsDir)) return [];
4768
- const files = await glob.glob("**/*", {
4769
- cwd: assetsDir,
4770
- nodir: true,
4771
- dot: false
4772
- });
4773
- const results = [];
4774
- for (const rel of files) {
4775
- const absPath = path9__default.default.join(assetsDir, rel);
4776
- const stat = await fs__default.default.stat(absPath);
4777
- if (!stat.isFile()) continue;
4778
- const originalPath = rel.split(path9__default.default.sep).join("/");
4779
- const hash = await sha256Prefix(absPath, HASH_LEN);
4780
- const hashedPath = insertHashIntoName(originalPath, hash);
4781
- const contentType = mimeFor(rel);
4782
- results.push({
4783
- originalPath,
4784
- hashedPath,
4785
- hash,
4786
- size: stat.size,
4787
- contentType,
4788
- absPath
5477
+ function diffFieldPair(p, c, path25, out) {
5478
+ if (p.type !== c.type) {
5479
+ out.push({
5480
+ kind: "breaking",
5481
+ path: path25,
5482
+ detail: `Type changed (${p.type} \u2192 ${c.type}). Saved values may misrender.`
5483
+ });
5484
+ return;
5485
+ }
5486
+ if (p.required !== true && c.required === true) {
5487
+ out.push({
5488
+ kind: "breaking",
5489
+ path: path25,
5490
+ detail: "Field became required. Existing empty instances now invalid."
5491
+ });
5492
+ }
5493
+ if (typeof p.maxLength === "number" || typeof c.maxLength === "number") {
5494
+ if ((c.maxLength ?? Infinity) < (p.maxLength ?? Infinity)) {
5495
+ out.push({
5496
+ kind: "breaking",
5497
+ path: path25,
5498
+ detail: `maxLength tightened (${p.maxLength ?? "\u221E"} \u2192 ${c.maxLength}).`
5499
+ });
5500
+ }
5501
+ }
5502
+ if (typeof p.min === "number" || typeof c.min === "number") {
5503
+ if ((c.min ?? -Infinity) > (p.min ?? -Infinity)) {
5504
+ out.push({
5505
+ kind: "breaking",
5506
+ path: path25,
5507
+ detail: `min raised (${p.min ?? "-\u221E"} \u2192 ${c.min}).`
5508
+ });
5509
+ }
5510
+ }
5511
+ if (typeof p.max === "number" || typeof c.max === "number") {
5512
+ if ((c.max ?? Infinity) < (p.max ?? Infinity)) {
5513
+ out.push({
5514
+ kind: "breaking",
5515
+ path: path25,
5516
+ detail: `max lowered (${p.max ?? "\u221E"} \u2192 ${c.max}).`
5517
+ });
5518
+ }
5519
+ }
5520
+ if (p.options || c.options) {
5521
+ const priorOpts = new Set(p.options ?? []);
5522
+ const currentOpts = new Set(c.options ?? []);
5523
+ const removed = [...priorOpts].filter((o) => !currentOpts.has(o));
5524
+ const added = [...currentOpts].filter((o) => !priorOpts.has(o));
5525
+ if (removed.length > 0) {
5526
+ out.push({
5527
+ kind: "breaking",
5528
+ path: path25,
5529
+ detail: `Option(s) removed: ${removed.join(", ")}. Existing saved values may be orphaned.`
5530
+ });
5531
+ }
5532
+ if (added.length > 0) {
5533
+ out.push({
5534
+ kind: "additive",
5535
+ path: path25,
5536
+ detail: `Option(s) added: ${added.join(", ")}.`
5537
+ });
5538
+ }
5539
+ }
5540
+ if (!deepEqual(p.default, c.default)) {
5541
+ out.push({
5542
+ kind: "defaults-only",
5543
+ path: path25,
5544
+ detail: `Default changed: ${JSON.stringify(p.default)} \u2192 ${JSON.stringify(c.default)}.`
4789
5545
  });
4790
5546
  }
4791
- results.sort((a, b) => a.originalPath.localeCompare(b.originalPath));
4792
- return results;
4793
5547
  }
4794
- function buildAssetMap(entries) {
4795
- const map = {};
4796
- for (const e of entries) {
4797
- map[e.originalPath] = e.hashedPath;
5548
+ function diffDefaults(prior, current, pathPrefix, out) {
5549
+ const keys = /* @__PURE__ */ new Set([...Object.keys(prior), ...Object.keys(current)]);
5550
+ for (const key of [...keys].sort()) {
5551
+ if (!(key in prior) || !(key in current)) continue;
5552
+ if (!deepEqual(prior[key], current[key])) {
5553
+ out.push({
5554
+ kind: "defaults-only",
5555
+ path: `${pathPrefix}.${key}`,
5556
+ detail: `Default value changed.`
5557
+ });
5558
+ }
4798
5559
  }
4799
- return map;
5560
+ }
5561
+ function diffAssets(prior, current, out) {
5562
+ const currentPaths = new Set(current.assets.map((a) => a.path));
5563
+ for (const a of prior.assets) {
5564
+ if (!currentPaths.has(a.path)) {
5565
+ out.push({
5566
+ kind: "breaking-asset",
5567
+ path: `theme-assets/${a.path}`,
5568
+ detail: `Asset "${a.path}" was present in the prior version and is now missing. Code that references it hardcoded will break.`
5569
+ });
5570
+ }
5571
+ }
5572
+ }
5573
+ function deepEqual(a, b) {
5574
+ if (a === b) return true;
5575
+ if (a === null || b === null) return false;
5576
+ if (typeof a !== typeof b) return false;
5577
+ if (typeof a !== "object") return false;
5578
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
5579
+ if (Array.isArray(a) && Array.isArray(b)) {
5580
+ if (a.length !== b.length) return false;
5581
+ for (let i = 0; i < a.length; i++) {
5582
+ if (!deepEqual(a[i], b[i])) return false;
5583
+ }
5584
+ return true;
5585
+ }
5586
+ const ak = Object.keys(a);
5587
+ const bk = Object.keys(b);
5588
+ if (ak.length !== bk.length) return false;
5589
+ for (const k of ak) {
5590
+ if (!deepEqual(
5591
+ a[k],
5592
+ b[k]
5593
+ ))
5594
+ return false;
5595
+ }
5596
+ return true;
4800
5597
  }
4801
5598
 
4802
5599
  // src/commands/publish.ts
4803
5600
  async function publishCommand(options) {
4804
- const env = options.env ?? "dev";
5601
+ const env = options.env;
4805
5602
  logger.header("OneX Theme Publish");
4806
5603
  logger.info(`Environment: ${env} (${getApiUrl(env)})`);
4807
5604
  logger.newLine();
@@ -4815,13 +5612,13 @@ async function publishCommand(options) {
4815
5612
  logger.info(`Logged in as: ${tokens.user.email}`);
4816
5613
  let themePath;
4817
5614
  if (options.theme) {
4818
- themePath = path9__default.default.resolve(options.theme);
5615
+ themePath = path13__default.default.resolve(options.theme);
4819
5616
  } else {
4820
5617
  const isThemeDir2 = [
4821
5618
  "theme.config.ts",
4822
5619
  "bundle-entry.ts",
4823
5620
  "manifest.ts"
4824
- ].some((f) => fs__default.default.existsSync(path9__default.default.join(process.cwd(), f)));
5621
+ ].some((f) => fs8__default.default.existsSync(path13__default.default.join(process.cwd(), f)));
4825
5622
  if (isThemeDir2) {
4826
5623
  themePath = process.cwd();
4827
5624
  } else {
@@ -4831,13 +5628,13 @@ async function publishCommand(options) {
4831
5628
  process.exit(1);
4832
5629
  }
4833
5630
  }
4834
- const pkgPath = path9__default.default.join(themePath, "package.json");
4835
- if (!fs__default.default.existsSync(pkgPath)) {
5631
+ const pkgPath = path13__default.default.join(themePath, "package.json");
5632
+ if (!fs8__default.default.existsSync(pkgPath)) {
4836
5633
  logger.error("No package.json found in theme directory");
4837
5634
  process.exit(1);
4838
5635
  }
4839
- const pkg = fs__default.default.readJsonSync(pkgPath);
4840
- const themeId = pkg.name?.replace("@onex-themes/", "") || path9__default.default.basename(themePath);
5636
+ const pkg = fs8__default.default.readJsonSync(pkgPath);
5637
+ const themeId = pkg.name?.replace("@onex-themes/", "") || path13__default.default.basename(themePath);
4841
5638
  if (options.bump) {
4842
5639
  const currentVersion = pkg.version || "1.0.0";
4843
5640
  const newVersion = semver__default.default.inc(currentVersion, options.bump);
@@ -4846,7 +5643,7 @@ async function publishCommand(options) {
4846
5643
  process.exit(1);
4847
5644
  }
4848
5645
  pkg.version = newVersion;
4849
- fs__default.default.writeJsonSync(pkgPath, pkg, { spaces: 2 });
5646
+ fs8__default.default.writeJsonSync(pkgPath, pkg, { spaces: 2 });
4850
5647
  logger.info(`Bumped version: ${currentVersion} -> ${newVersion}`);
4851
5648
  }
4852
5649
  const version2 = pkg.version || "1.0.0";
@@ -4861,57 +5658,62 @@ async function publishCommand(options) {
4861
5658
  logger.info(`Version: ${version2}`);
4862
5659
  logger.newLine();
4863
5660
  const apiUrl = getApiUrl(env);
4864
- logger.startSpinner("Registering theme...");
4865
- try {
4866
- const regResponse = await authenticatedFetch(
4867
- `${apiUrl}/website-api/themes/register`,
4868
- {
4869
- method: "POST",
4870
- body: JSON.stringify({
4871
- themeId,
4872
- name: pkg.displayName || themeId,
4873
- description: pkg.description || "",
4874
- email: tokens.user.email,
4875
- author: typeof pkg.author === "string" ? pkg.author : pkg.author?.name || tokens.user.name || "",
4876
- category: pkg.onex?.category || "MINIMAL",
4877
- tags: pkg.keywords || [],
4878
- thumbnail_url: pkg.onex?.thumbnail || ""
4879
- })
4880
- },
4881
- env
4882
- );
4883
- const regData = await regResponse.json();
4884
- const regBody = regData.statusCode ? regData.body : regData;
4885
- if (!regResponse.ok) {
4886
- const errMsg = regBody.error || regBody.message || "Registration failed";
4887
- if (!errMsg.includes("already registered")) {
4888
- logger.stopSpinner(false, "Registration failed");
4889
- logger.error(errMsg);
4890
- process.exit(1);
5661
+ if (!options.dryRun) {
5662
+ logger.startSpinner("Registering theme...");
5663
+ try {
5664
+ const regResponse = await authenticatedFetch(
5665
+ `${apiUrl}/website-api/themes/register`,
5666
+ {
5667
+ method: "POST",
5668
+ body: JSON.stringify({
5669
+ themeId,
5670
+ name: pkg.displayName || themeId,
5671
+ description: pkg.description || "",
5672
+ email: tokens.user.email,
5673
+ author: typeof pkg.author === "string" ? pkg.author : pkg.author?.name || tokens.user.name || "",
5674
+ category: pkg.onex?.category || "MINIMAL",
5675
+ tags: pkg.keywords || [],
5676
+ thumbnail_url: pkg.onex?.thumbnail || ""
5677
+ })
5678
+ },
5679
+ env
5680
+ );
5681
+ const regData = await regResponse.json();
5682
+ const regBody = regData.statusCode ? regData.body : regData;
5683
+ if (!regResponse.ok) {
5684
+ const errMsg = regBody.error || regBody.message || "Registration failed";
5685
+ if (!errMsg.includes("already registered")) {
5686
+ logger.stopSpinner(false, "Registration failed");
5687
+ logger.error(errMsg);
5688
+ process.exit(1);
5689
+ }
4891
5690
  }
5691
+ logger.stopSpinner(true, regBody.message || "Theme registered");
5692
+ } catch (error) {
5693
+ logger.stopSpinner(false, "Registration failed");
5694
+ logger.error(
5695
+ error instanceof Error ? error.message : "Connection failed"
5696
+ );
5697
+ process.exit(1);
4892
5698
  }
4893
- logger.stopSpinner(true, regBody.message || "Theme registered");
4894
- } catch (error) {
4895
- logger.stopSpinner(false, "Registration failed");
4896
- logger.error(error instanceof Error ? error.message : "Connection failed");
4897
- process.exit(1);
4898
5699
  }
4899
- logger.startSpinner("Checking version availability...");
4900
- try {
4901
- const checkResponse = await authenticatedFetch(
4902
- `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(version2)}/exists`,
4903
- { method: "GET" },
4904
- env
4905
- );
4906
- const checkData = await checkResponse.json();
4907
- const checkBody = checkData.statusCode ? checkData.body : checkData;
4908
- if (checkBody.exists) {
4909
- logger.stopSpinner(false, "Version already published");
4910
- const patchVer = semver__default.default.inc(version2, "patch") || "?";
4911
- const minorVer = semver__default.default.inc(version2, "minor") || "?";
4912
- const majorVer = semver__default.default.inc(version2, "major") || "?";
4913
- logger.error(
4914
- `
5700
+ if (!options.dryRun) {
5701
+ logger.startSpinner("Checking version availability...");
5702
+ try {
5703
+ const checkResponse = await authenticatedFetch(
5704
+ `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(version2)}/exists`,
5705
+ { method: "GET" },
5706
+ env
5707
+ );
5708
+ const checkData = await checkResponse.json();
5709
+ const checkBody = checkData.statusCode ? checkData.body : checkData;
5710
+ if (checkBody.exists) {
5711
+ logger.stopSpinner(false, "Version already published");
5712
+ const patchVer = semver__default.default.inc(version2, "patch") || "?";
5713
+ const minorVer = semver__default.default.inc(version2, "minor") || "?";
5714
+ const majorVer = semver__default.default.inc(version2, "major") || "?";
5715
+ logger.error(
5716
+ `
4915
5717
  Version ${version2} of "${themeId}" is already published and cannot be overwritten.
4916
5718
 
4917
5719
  To publish a new version:
@@ -4922,12 +5724,16 @@ Or use the --bump flag:
4922
5724
  onexthm publish --bump patch (${version2} -> ${patchVer})
4923
5725
  onexthm publish --bump minor (${version2} -> ${minorVer})
4924
5726
  onexthm publish --bump major (${version2} -> ${majorVer})`
5727
+ );
5728
+ process.exit(1);
5729
+ }
5730
+ logger.stopSpinner(true, `Version ${version2} is available`);
5731
+ } catch (error) {
5732
+ logger.stopSpinner(
5733
+ true,
5734
+ "Version check skipped (endpoint not available)"
4925
5735
  );
4926
- process.exit(1);
4927
5736
  }
4928
- logger.stopSpinner(true, `Version ${version2} is available`);
4929
- } catch (error) {
4930
- logger.stopSpinner(true, "Version check skipped (endpoint not available)");
4931
5737
  }
4932
5738
  logger.startSpinner("Building theme...");
4933
5739
  try {
@@ -4944,7 +5750,19 @@ Or use the --bump flag:
4944
5750
  logger.error(error instanceof Error ? error.message : "Build error");
4945
5751
  process.exit(1);
4946
5752
  }
4947
- const distDir = path9__default.default.join(themePath, "dist");
5753
+ const distDir = path13__default.default.join(themePath, "dist");
5754
+ const classification = await runSchemaDiffGate(
5755
+ themeId,
5756
+ distDir,
5757
+ env,
5758
+ options
5759
+ );
5760
+ if (options.dryRun) {
5761
+ const exitCode = classification?.highest === "breaking" || classification?.highest === "breaking-severe" || classification?.highest === "breaking-asset" ? 2 : 0;
5762
+ logger.newLine();
5763
+ logger.info(`Dry run complete (exit ${exitCode}). No files uploaded.`);
5764
+ process.exit(exitCode);
5765
+ }
4948
5766
  let assetEntries = [];
4949
5767
  try {
4950
5768
  assetEntries = await scanThemeAssets(distDir);
@@ -4981,8 +5799,8 @@ Or use the --bump flag:
4981
5799
  for (const [originalPath, url] of Object.entries(videoUrls)) {
4982
5800
  assetMap[originalPath] = url;
4983
5801
  }
4984
- const assetMapPath = path9__default.default.join(distDir, "asset-map.json");
4985
- await fs__default.default.writeFile(assetMapPath, JSON.stringify(assetMap, null, 2));
5802
+ const assetMapPath = path13__default.default.join(distDir, "asset-map.json");
5803
+ await fs8__default.default.writeFile(assetMapPath, JSON.stringify(assetMap, null, 2));
4986
5804
  } catch (error) {
4987
5805
  logger.error(
4988
5806
  `Failed to write asset-map.json: ${error instanceof Error ? error.message : "unknown"}`
@@ -5057,7 +5875,7 @@ Or use the --bump flag:
5057
5875
  continue;
5058
5876
  }
5059
5877
  try {
5060
- const buf = await fs__default.default.promises.readFile(entry.absPath);
5878
+ const buf = await fs8__default.default.promises.readFile(entry.absPath);
5061
5879
  const res = await fetch(item.upload_url, {
5062
5880
  method: "PUT",
5063
5881
  headers: {
@@ -5099,12 +5917,12 @@ Or use the --bump flag:
5099
5917
  }
5100
5918
  logger.startSpinner("Uploading bundle...");
5101
5919
  try {
5102
- if (!fs__default.default.existsSync(distDir)) {
5920
+ if (!fs8__default.default.existsSync(distDir)) {
5103
5921
  logger.stopSpinner(false, "No dist/ directory");
5104
5922
  logger.error("Build the theme first: onexthm build");
5105
5923
  process.exit(1);
5106
5924
  }
5107
- const bundleZipPath = path9__default.default.join(themePath, "dist", "bundle.zip");
5925
+ const bundleZipPath = path13__default.default.join(themePath, "dist", "bundle.zip");
5108
5926
  await createZip(distDir, bundleZipPath, [
5109
5927
  "bundle.zip",
5110
5928
  "source.zip",
@@ -5113,7 +5931,7 @@ Or use the --bump flag:
5113
5931
  "theme-assets",
5114
5932
  "theme-assets/**"
5115
5933
  ]);
5116
- const bundleBuffer = fs__default.default.readFileSync(bundleZipPath);
5934
+ const bundleBuffer = fs8__default.default.readFileSync(bundleZipPath);
5117
5935
  const bundleRes = await fetch(bundleUploadUrl, {
5118
5936
  method: "PUT",
5119
5937
  headers: { "Content-Type": "application/zip" },
@@ -5131,7 +5949,7 @@ Or use the --bump flag:
5131
5949
  }
5132
5950
  logger.startSpinner("Uploading source...");
5133
5951
  try {
5134
- const sourceZipPath = path9__default.default.join(themePath, "dist", "source.zip");
5952
+ const sourceZipPath = path13__default.default.join(themePath, "dist", "source.zip");
5135
5953
  await createZip(themePath, sourceZipPath, [
5136
5954
  "node_modules",
5137
5955
  "dist",
@@ -5139,7 +5957,7 @@ Or use the --bump flag:
5139
5957
  ".env",
5140
5958
  ".env.local"
5141
5959
  ]);
5142
- const sourceBuffer = fs__default.default.readFileSync(sourceZipPath);
5960
+ const sourceBuffer = fs8__default.default.readFileSync(sourceZipPath);
5143
5961
  const sourceRes = await fetch(sourceUploadUrl, {
5144
5962
  method: "PUT",
5145
5963
  headers: { "Content-Type": "application/zip" },
@@ -5217,9 +6035,9 @@ async function uploadThumbnail(apiUrl, themeId, themePath, distDir, env = "dev")
5217
6035
  let imageBase64 = null;
5218
6036
  let mimeType = "image/png";
5219
6037
  for (const { file, mime } of THUMBNAIL_CANDIDATES) {
5220
- const candidate = path9__default.default.join(themePath, file);
5221
- if (fs__default.default.existsSync(candidate)) {
5222
- const buf = fs__default.default.readFileSync(candidate);
6038
+ const candidate = path13__default.default.join(themePath, file);
6039
+ if (fs8__default.default.existsSync(candidate)) {
6040
+ const buf = fs8__default.default.readFileSync(candidate);
5223
6041
  imageBase64 = `data:${mime};base64,${buf.toString("base64")}`;
5224
6042
  mimeType = mime;
5225
6043
  logger.info(`Using local thumbnail: ${file}`);
@@ -5319,7 +6137,7 @@ async function screenshotHomePage(themePath, distDir) {
5319
6137
  const { compilePreviewRuntime: compilePreviewRuntime2 } = await Promise.resolve().then(() => (init_compile_theme(), compile_theme_exports));
5320
6138
  const { createDevServer: createDevServer2 } = await Promise.resolve().then(() => (init_dev_server(), dev_server_exports));
5321
6139
  const previewRuntimePath = await compilePreviewRuntime2(themePath);
5322
- const themeName = path9__default.default.basename(themePath);
6140
+ const themeName = path13__default.default.basename(themePath);
5323
6141
  const port = await findFreePort(4500);
5324
6142
  const server = createDevServer2({
5325
6143
  port,
@@ -5376,7 +6194,7 @@ async function findFreePort(start) {
5376
6194
  });
5377
6195
  }
5378
6196
  async function uploadVideoMultipart(apiUrl, themeId, video, env = "dev") {
5379
- const fileName = path9__default.default.basename(video.originalPath);
6197
+ const fileName = path13__default.default.basename(video.originalPath);
5380
6198
  const videoInitUrl = `${apiUrl}/media/videos/multipart/init`;
5381
6199
  const videoInitBody = {
5382
6200
  file_name: fileName,
@@ -5412,7 +6230,7 @@ async function uploadVideoMultipart(apiUrl, themeId, video, env = "dev") {
5412
6230
  );
5413
6231
  }
5414
6232
  const { upload_id, file_key, chunk_size, chunk_urls } = initBody;
5415
- const fileBuffer = await fs__default.default.promises.readFile(video.absPath);
6233
+ const fileBuffer = await fs8__default.default.promises.readFile(video.absPath);
5416
6234
  const CHUNK_CONCURRENCY = 4;
5417
6235
  const queue = [...chunk_urls];
5418
6236
  const parts = [];
@@ -5505,6 +6323,91 @@ async function createZip(sourceDir, outputPath, exclude) {
5505
6323
  archive.finalize();
5506
6324
  });
5507
6325
  }
6326
+ async function runSchemaDiffGate(themeId, distDir, env, options) {
6327
+ logger.startSpinner("Fetching prior version for diff...");
6328
+ const { result: prior, reason } = await fetchPriorGateManifests(themeId, env);
6329
+ if (!prior) {
6330
+ if (reason === "no-prior") {
6331
+ logger.stopSpinner(true, "First publish \u2014 no prior version to diff");
6332
+ } else {
6333
+ logger.stopSpinner(true, `Gate skipped (${reason})`);
6334
+ }
6335
+ return null;
6336
+ }
6337
+ logger.stopSpinner(true, `Fetched prior version ${prior.version}`);
6338
+ let currentSchemas;
6339
+ let currentAssets;
6340
+ try {
6341
+ currentSchemas = JSON.parse(
6342
+ await fs8__default.default.readFile(path13__default.default.join(distDir, "schemas.json"), "utf-8")
6343
+ );
6344
+ } catch (err) {
6345
+ logger.warning(
6346
+ `Gate skipped: dist/schemas.json missing or unreadable (${err instanceof Error ? err.message : "unknown"})`
6347
+ );
6348
+ return null;
6349
+ }
6350
+ try {
6351
+ currentAssets = JSON.parse(
6352
+ await fs8__default.default.readFile(path13__default.default.join(distDir, "asset-manifest.json"), "utf-8")
6353
+ );
6354
+ } catch {
6355
+ currentAssets = { manifestVersion: 1, assets: [] };
6356
+ }
6357
+ const changes = diffManifests(
6358
+ { schemas: prior.schemas, assets: prior.assets },
6359
+ { schemas: currentSchemas, assets: currentAssets }
6360
+ );
6361
+ const classification = classify(changes);
6362
+ printGateReport(prior.version, classification);
6363
+ if (options.dryRun) return classification;
6364
+ const isBreaking = classification.highest === "breaking" || classification.highest === "breaking-severe" || classification.highest === "breaking-asset";
6365
+ if (isBreaking && !options.force) {
6366
+ logger.error(
6367
+ "\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)."
6368
+ );
6369
+ process.exit(1);
6370
+ }
6371
+ if (classification.highest === "defaults-only" && !options.confirmDefaults) {
6372
+ logger.error(
6373
+ "\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."
6374
+ );
6375
+ process.exit(1);
6376
+ }
6377
+ return classification;
6378
+ }
6379
+ function printGateReport(priorVersion, classification) {
6380
+ logger.newLine();
6381
+ logger.info(`Schema diff vs. v${priorVersion}:`);
6382
+ if (classification.changes.length === 0) {
6383
+ logger.log(" \u2713 Safe \u2014 no schema changes detected");
6384
+ return;
6385
+ }
6386
+ for (const change of classification.changes) {
6387
+ const icon = iconFor(change.kind);
6388
+ logger.log(` ${icon} [${change.kind}] ${change.path} \u2014 ${change.detail}`);
6389
+ }
6390
+ logger.log(
6391
+ `
6392
+ \u2192 Classification: ${classification.highest}. Suggested bump: ${classification.bump}.`
6393
+ );
6394
+ }
6395
+ function iconFor(kind) {
6396
+ switch (kind) {
6397
+ case "safe":
6398
+ case "safe-rename":
6399
+ return "\u2713";
6400
+ case "additive":
6401
+ return "+";
6402
+ case "defaults-only":
6403
+ return "\u26A0";
6404
+ case "breaking":
6405
+ case "breaking-asset":
6406
+ return "\u2717";
6407
+ case "breaking-severe":
6408
+ return "\u2717\u2717";
6409
+ }
6410
+ }
5508
6411
 
5509
6412
  // src/commands/mcp.ts
5510
6413
  init_logger();
@@ -5516,24 +6419,24 @@ var AI_CONTEXT_FILES = [
5516
6419
  ".mcp.json"
5517
6420
  ];
5518
6421
  function resolveTargetDir(opts) {
5519
- return path9__default.default.resolve(opts.cwd ?? process.cwd());
6422
+ return path13__default.default.resolve(opts.cwd ?? process.cwd());
5520
6423
  }
5521
6424
  function resolveDefaultTemplateDir() {
5522
- return path9__default.default.join(getTemplatesDir(), "default");
6425
+ return path13__default.default.join(getTemplatesDir(), "default");
5523
6426
  }
5524
6427
  function isThemeDir(dir) {
5525
- return fs__default.default.existsSync(path9__default.default.join(dir, "theme.config.ts")) || fs__default.default.existsSync(path9__default.default.join(dir, "theme.config.js"));
6428
+ return fs8__default.default.existsSync(path13__default.default.join(dir, "theme.config.ts")) || fs8__default.default.existsSync(path13__default.default.join(dir, "theme.config.js"));
5526
6429
  }
5527
6430
  function inspectFiles(templateDir, targetDir) {
5528
6431
  return AI_CONTEXT_FILES.map((name) => {
5529
- const templatePath = path9__default.default.join(templateDir, name);
5530
- const targetPath = path9__default.default.join(targetDir, name);
5531
- const exists = fs__default.default.existsSync(targetPath);
6432
+ const templatePath = path13__default.default.join(templateDir, name);
6433
+ const targetPath = path13__default.default.join(targetDir, name);
6434
+ const exists = fs8__default.default.existsSync(targetPath);
5532
6435
  let identical = false;
5533
- if (exists && fs__default.default.existsSync(templatePath)) {
6436
+ if (exists && fs8__default.default.existsSync(templatePath)) {
5534
6437
  try {
5535
- const a = fs__default.default.readFileSync(templatePath, "utf-8");
5536
- const b = fs__default.default.readFileSync(targetPath, "utf-8");
6438
+ const a = fs8__default.default.readFileSync(templatePath, "utf-8");
6439
+ const b = fs8__default.default.readFileSync(targetPath, "utf-8");
5537
6440
  identical = a.replace(/\r\n/g, "\n") === b.replace(/\r\n/g, "\n");
5538
6441
  } catch {
5539
6442
  identical = false;
@@ -5583,11 +6486,11 @@ async function mcpSetupCommand(options = {}) {
5583
6486
  }
5584
6487
  }
5585
6488
  for (const s of missing) {
5586
- if (!fs__default.default.existsSync(s.templatePath)) {
6489
+ if (!fs8__default.default.existsSync(s.templatePath)) {
5587
6490
  logger.warning(` ! ${s.name} not in template \u2014 skipped`);
5588
6491
  continue;
5589
6492
  }
5590
- await fs__default.default.copy(s.templatePath, s.targetPath);
6493
+ await fs8__default.default.copy(s.templatePath, s.targetPath);
5591
6494
  logger.success(` \u2713 ${s.name}`);
5592
6495
  }
5593
6496
  logger.log("");
@@ -5648,11 +6551,11 @@ async function mcpUpgradeCommand(options = {}) {
5648
6551
  }
5649
6552
  }
5650
6553
  for (const s of toUpgrade) {
5651
- if (!fs__default.default.existsSync(s.templatePath)) {
6554
+ if (!fs8__default.default.existsSync(s.templatePath)) {
5652
6555
  logger.warning(` ! ${s.name} not in template \u2014 skipped`);
5653
6556
  continue;
5654
6557
  }
5655
- await fs__default.default.copy(s.templatePath, s.targetPath, { overwrite: true });
6558
+ await fs8__default.default.copy(s.templatePath, s.targetPath, { overwrite: true });
5656
6559
  logger.success(` \u2713 ${s.name}`);
5657
6560
  }
5658
6561
  logger.log("");
@@ -5670,12 +6573,12 @@ async function mcpDoctorCommand(options = {}) {
5670
6573
  return;
5671
6574
  }
5672
6575
  logger.success("theme.config.ts present");
5673
- const mcpJsonPath = path9__default.default.join(targetDir, ".mcp.json");
5674
- if (!fs__default.default.existsSync(mcpJsonPath)) {
6576
+ const mcpJsonPath = path13__default.default.join(targetDir, ".mcp.json");
6577
+ if (!fs8__default.default.existsSync(mcpJsonPath)) {
5675
6578
  logger.error(".mcp.json missing \u2014 run `onexthm mcp setup`");
5676
6579
  } else {
5677
6580
  try {
5678
- const mcpJson = JSON.parse(fs__default.default.readFileSync(mcpJsonPath, "utf-8"));
6581
+ const mcpJson = JSON.parse(fs8__default.default.readFileSync(mcpJsonPath, "utf-8"));
5679
6582
  const servers = mcpJson?.mcpServers ?? {};
5680
6583
  if (servers.onexthm) {
5681
6584
  logger.success(".mcp.json registers `onexthm`");
@@ -5708,8 +6611,8 @@ async function mcpDoctorCommand(options = {}) {
5708
6611
  logger.success(`${s.name} up to date`);
5709
6612
  }
5710
6613
  }
5711
- const registryPath = path9__default.default.join(targetDir, "sections-registry.ts");
5712
- if (fs__default.default.existsSync(registryPath)) {
6614
+ const registryPath = path13__default.default.join(targetDir, "sections-registry.ts");
6615
+ if (fs8__default.default.existsSync(registryPath)) {
5713
6616
  logger.success("sections-registry.ts present");
5714
6617
  } else {
5715
6618
  logger.warning(
@@ -5720,24 +6623,27 @@ async function mcpDoctorCommand(options = {}) {
5720
6623
 
5721
6624
  // src/cli.ts
5722
6625
  dotenv__default.default.config({
5723
- path: path9__default.default.join(process.cwd(), ".env.local"),
6626
+ path: path13__default.default.join(process.cwd(), ".env.local"),
5724
6627
  override: true
5725
6628
  });
5726
- dotenv__default.default.config({ path: path9__default.default.join(process.cwd(), ".env") });
6629
+ dotenv__default.default.config({ path: path13__default.default.join(process.cwd(), ".env") });
5727
6630
  try {
5728
6631
  const projectRoot = getProjectRoot();
5729
- if (path9__default.default.resolve(projectRoot) !== path9__default.default.resolve(process.cwd())) {
6632
+ if (path13__default.default.resolve(projectRoot) !== path13__default.default.resolve(process.cwd())) {
5730
6633
  dotenv__default.default.config({
5731
- path: path9__default.default.join(projectRoot, ".env.local")
6634
+ path: path13__default.default.join(projectRoot, ".env.local")
5732
6635
  });
5733
- dotenv__default.default.config({ path: path9__default.default.join(projectRoot, ".env") });
6636
+ dotenv__default.default.config({ path: path13__default.default.join(projectRoot, ".env") });
5734
6637
  }
5735
6638
  } catch {
5736
6639
  }
5737
6640
  dotenv__default.default.config({
5738
- path: path9__default.default.join(os__default.default.homedir(), ".onexthm", ".env"),
6641
+ path: path13__default.default.join(os__default.default.homedir(), ".onexthm", ".env"),
5739
6642
  quiet: true
5740
6643
  });
6644
+ function envOpt() {
6645
+ return new commander.Option("--env <env>", "Target environment: dev, staging, or prod").choices(["dev", "staging", "prod"]).makeOptionMandatory();
6646
+ }
5741
6647
  var require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.js', document.baseURI).href)));
5742
6648
  var { version } = require2("../package.json");
5743
6649
  var program = new commander.Command();
@@ -5746,11 +6652,7 @@ program.command("init").description("Create a new OneX theme project").argument(
5746
6652
  "-t, --template <template>",
5747
6653
  "Template to use (default, minimal)",
5748
6654
  "default"
5749
- ).option("--no-install", "Skip installing dependencies").option("--git", "Initialize git repository").option("-y, --yes", "Skip prompts and use defaults").option(
5750
- "--env <env>",
5751
- "Target environment: dev or prod (default: dev)",
5752
- "dev"
5753
- ).action(initCommand);
6655
+ ).option("--no-install", "Skip installing dependencies").option("--git", "Initialize git repository").option("-y, --yes", "Skip prompts and use defaults").addOption(envOpt()).action(initCommand);
5754
6656
  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(
5755
6657
  "-c, --category <category>",
5756
6658
  "Section category (headers, content, footers)"
@@ -5777,36 +6679,16 @@ program.command("download").description("Download a published theme via the webs
5777
6679
  "-v, --version <version>",
5778
6680
  "Theme version (default: latest)",
5779
6681
  "latest"
5780
- ).option(
5781
- "--env <env>",
5782
- "Target environment: dev or prod (default: dev)",
5783
- "dev"
5784
- ).option("-b, --bucket <name>", "[deprecated] ignored").option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
6682
+ ).addOption(envOpt()).option("-b, --bucket <name>", "[deprecated] ignored").option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
5785
6683
  program.command("clone").description("Clone theme source code via the website-api").argument("<theme-name>", "Theme to clone").option(
5786
6684
  "-v, --version <version>",
5787
6685
  "Theme version (default: latest)",
5788
6686
  "latest"
5789
- ).option("-n, --name <name>", "New theme name (skips interactive prompt)").option("-o, --output <dir>", "Output directory").option(
5790
- "--env <env>",
5791
- "Target environment: dev or prod (default: dev)",
5792
- "dev"
5793
- ).option("-b, --bucket <name>", "[deprecated] ignored").option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
6687
+ ).option("-n, --name <name>", "New theme name (skips interactive prompt)").option("-o, --output <dir>", "Output directory").addOption(envOpt()).option("-b, --bucket <name>", "[deprecated] ignored").option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
5794
6688
  program.command("config").description("Configure OneX CLI credentials (AWS, API keys)").action(configCommand);
5795
- program.command("login").description("Login to OneX platform").option(
5796
- "--env <env>",
5797
- "Target environment: dev or prod (default: dev)",
5798
- "dev"
5799
- ).action(loginCommand);
5800
- program.command("logout").description("Logout from OneX platform").option(
5801
- "--env <env>",
5802
- "Target environment: dev or prod (default: dev)",
5803
- "dev"
5804
- ).action(logoutCommand);
5805
- program.command("whoami").description("Show current logged-in developer").option(
5806
- "--env <env>",
5807
- "Target environment: dev or prod (default: dev)",
5808
- "dev"
5809
- ).action(whoamiCommand);
6689
+ program.command("login").description("Login to OneX platform").addOption(envOpt()).action(loginCommand);
6690
+ program.command("logout").description("Logout from OneX platform").addOption(envOpt()).action(logoutCommand);
6691
+ program.command("whoami").description("Show current logged-in developer").addOption(envOpt()).action(whoamiCommand);
5810
6692
  var mcpCmd = program.command("mcp").description("Manage MCP server registration and AI-context files");
5811
6693
  mcpCmd.command("setup").description(
5812
6694
  "Install .mcp.json + CLAUDE.md + AGENTS.md + .cursorrules into the current theme"
@@ -5818,10 +6700,15 @@ mcpCmd.command("doctor").description("Diagnose MCP setup in the current theme di
5818
6700
  program.command("publish").description("Build, scan, and publish theme to marketplace (requires login)").option("-t, --theme <path>", "Theme directory path").option(
5819
6701
  "--bump <type>",
5820
6702
  "Auto-bump version before publish (patch|minor|major)"
6703
+ ).addOption(envOpt()).option(
6704
+ "--dry-run",
6705
+ "Build locally and print the schema-diff classification without publishing"
6706
+ ).option(
6707
+ "--confirm-defaults",
6708
+ "Confirm that changed section/block defaults should propagate to live sites"
5821
6709
  ).option(
5822
- "--env <env>",
5823
- "Target environment: dev or prod (default: dev)",
5824
- "dev"
6710
+ "--force",
6711
+ "Publish even when the diff gate detects a breaking change"
5825
6712
  ).action(publishCommand);
5826
6713
  program.configureOutput({
5827
6714
  writeErr: (str) => process.stderr.write(chalk4__default.default.red(str))