@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.mjs CHANGED
@@ -1,21 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import chalk4 from 'chalk';
3
3
  import ora from 'ora';
4
- import * as esbuild from 'esbuild';
5
- import path9 from 'path';
6
- import fs8 from 'fs/promises';
7
- import crypto from 'crypto';
4
+ import fs3 from 'fs';
5
+ import path13 from 'path';
8
6
  import { glob } from 'glob';
7
+ import fs8 from 'fs-extra';
8
+ import crypto from 'crypto';
9
+ import fs11 from 'fs/promises';
10
+ import * as esbuild from 'esbuild';
9
11
  import { createRequire } from 'module';
10
12
  import http from 'http';
11
- import fs3 from 'fs';
12
13
  import { WebSocketServer, WebSocket } from 'ws';
13
14
  import os from 'os';
14
15
  import dotenv from 'dotenv';
15
- import fs from 'fs-extra';
16
16
  import ejs from 'ejs';
17
17
  import { execSync, spawn } from 'child_process';
18
- import { Command } from 'commander';
18
+ import { Command, Option } from 'commander';
19
19
  import inquirer from 'inquirer';
20
20
  import archiver from 'archiver';
21
21
  import FormData from 'form-data';
@@ -89,6 +89,308 @@ var init_logger = __esm({
89
89
  logger = new Logger();
90
90
  }
91
91
  });
92
+ function isNextjsProject(dir) {
93
+ return fs3.existsSync(path13.join(dir, "next.config.ts")) || fs3.existsSync(path13.join(dir, "next.config.js")) || fs3.existsSync(path13.join(dir, "next.config.mjs"));
94
+ }
95
+ var init_detect_nextjs = __esm({
96
+ "src/utils/detect-nextjs.ts"() {
97
+ }
98
+ });
99
+ function sortedCopy(value) {
100
+ if (Array.isArray(value)) {
101
+ return value.map((v) => sortedCopy(v));
102
+ }
103
+ if (value && typeof value === "object") {
104
+ const sorted = {};
105
+ for (const key of Object.keys(value).sort()) {
106
+ sorted[key] = sortedCopy(value[key]);
107
+ }
108
+ return sorted;
109
+ }
110
+ return value;
111
+ }
112
+ function normalizeField(raw) {
113
+ const out = {
114
+ id: String(raw.id),
115
+ type: String(raw.type)
116
+ };
117
+ if (raw.required === true) out.required = true;
118
+ if (raw.default !== void 0) out.default = raw.default;
119
+ if (Array.isArray(raw.aliases) && raw.aliases.length > 0) {
120
+ out.aliases = [...raw.aliases].map(String).sort();
121
+ }
122
+ if (typeof raw.maxLength === "number") out.maxLength = raw.maxLength;
123
+ if (typeof raw.min === "number") out.min = raw.min;
124
+ if (typeof raw.max === "number") out.max = raw.max;
125
+ if (typeof raw.step === "number") out.step = raw.step;
126
+ if (Array.isArray(raw.accept)) {
127
+ out.accept = [...raw.accept].map(String).sort();
128
+ }
129
+ if (Array.isArray(raw.options)) {
130
+ out.options = raw.options.map((o) => String(o?.value ?? o)).sort();
131
+ }
132
+ return out;
133
+ }
134
+ function normalizeBlock(raw) {
135
+ return {
136
+ type: String(raw.type),
137
+ settings: Array.isArray(raw.settings) ? raw.settings.map(normalizeField).sort(sortFieldsById) : [],
138
+ defaults: raw.defaults && typeof raw.defaults === "object" ? sortedCopy(raw.defaults) : {},
139
+ ...typeof raw.limit === "number" ? { limit: raw.limit } : {},
140
+ ...typeof raw.min === "number" ? { min: raw.min } : {},
141
+ ...raw.sortable === true ? { sortable: true } : {},
142
+ ...raw.baseType ? { baseType: String(raw.baseType) } : {}
143
+ };
144
+ }
145
+ function normalizeTemplate(raw) {
146
+ const out = { id: String(raw.id) };
147
+ if (raw.isDefault === true) out.isDefault = true;
148
+ if (Array.isArray(raw.settings)) {
149
+ out.settings = raw.settings.map(normalizeField).sort(sortFieldsById);
150
+ }
151
+ if (raw.defaults && typeof raw.defaults === "object") {
152
+ out.defaults = sortedCopy(raw.defaults);
153
+ }
154
+ return out;
155
+ }
156
+ function sortFieldsById(a, b) {
157
+ return a.id.localeCompare(b.id);
158
+ }
159
+ function sortByType(a, b) {
160
+ return a.type.localeCompare(b.type);
161
+ }
162
+ function normalizeSection(raw) {
163
+ return {
164
+ type: String(raw.type),
165
+ settings: Array.isArray(raw.settings) ? raw.settings.map(normalizeField).sort(sortFieldsById) : [],
166
+ defaults: raw.defaults && typeof raw.defaults === "object" ? sortedCopy(raw.defaults) : {},
167
+ blocks: Array.isArray(raw.blocks) ? raw.blocks.map(normalizeBlock).sort(sortByType) : [],
168
+ templates: Array.isArray(raw.templates) ? raw.templates.map(normalizeTemplate).sort(sortFieldsById) : [],
169
+ dataRequirements: raw.dataRequirements && typeof raw.dataRequirements === "object" ? sortedCopy(raw.dataRequirements) : null,
170
+ ...raw.global === true ? { global: true } : {},
171
+ ...typeof raw.maxBlocks === "number" ? { maxBlocks: raw.maxBlocks } : {}
172
+ };
173
+ }
174
+ async function extractSchemas(themePath) {
175
+ const { createJiti } = await import('jiti');
176
+ const jiti = createJiti(import.meta.url);
177
+ const schemaFiles = await glob("sections/**/*.schema.ts", { cwd: themePath });
178
+ const sections = {};
179
+ for (const file of schemaFiles) {
180
+ try {
181
+ const mod = await jiti.import(path13.join(themePath, file));
182
+ const exports$1 = mod;
183
+ for (const value of Object.values(exports$1)) {
184
+ if (value && typeof value === "object" && typeof value.type === "string" && Array.isArray(value.settings)) {
185
+ const section = normalizeSection(value);
186
+ sections[section.type] = section;
187
+ }
188
+ }
189
+ } catch {
190
+ }
191
+ }
192
+ const manifest = {
193
+ manifestVersion: 1,
194
+ sections: {}
195
+ };
196
+ for (const type of Object.keys(sections).sort()) {
197
+ manifest.sections[type] = sections[type];
198
+ }
199
+ return manifest;
200
+ }
201
+ function serializeManifest(manifest) {
202
+ return JSON.stringify(sortedCopy(manifest), null, 2);
203
+ }
204
+ var init_extract_schemas = __esm({
205
+ "src/utils/extract-schemas.ts"() {
206
+ }
207
+ });
208
+ function isVideoAsset(filePath) {
209
+ const lower = filePath.toLowerCase();
210
+ return VIDEO_EXTENSIONS.some((ext) => lower.endsWith(ext));
211
+ }
212
+ function mimeFor(filename) {
213
+ const ext = path13.extname(filename).toLowerCase();
214
+ return MIME_MAP[ext] || "application/octet-stream";
215
+ }
216
+ async function sha256Prefix(absPath, len) {
217
+ const buf = await fs8.readFile(absPath);
218
+ return crypto.createHash("sha256").update(buf).digest("hex").slice(0, len);
219
+ }
220
+ function insertHashIntoName(relPath, hash) {
221
+ const dir = path13.posix.dirname(relPath);
222
+ const base = path13.posix.basename(relPath);
223
+ const ext = path13.posix.extname(base);
224
+ const stem = ext ? base.slice(0, -ext.length) : base;
225
+ const hashed = `${stem}-${hash}${ext}`;
226
+ return dir === "." ? hashed : `${dir}/${hashed}`;
227
+ }
228
+ async function scanThemeAssets(distDir) {
229
+ const assetsDir = path13.join(distDir, "theme-assets");
230
+ if (!await fs8.pathExists(assetsDir)) return [];
231
+ const files = await glob("**/*", {
232
+ cwd: assetsDir,
233
+ nodir: true,
234
+ dot: false
235
+ });
236
+ const results = [];
237
+ for (const rel of files) {
238
+ const absPath = path13.join(assetsDir, rel);
239
+ const stat = await fs8.stat(absPath);
240
+ if (!stat.isFile()) continue;
241
+ const originalPath = rel.split(path13.sep).join("/");
242
+ const hash = await sha256Prefix(absPath, HASH_LEN);
243
+ const hashedPath = insertHashIntoName(originalPath, hash);
244
+ const contentType = mimeFor(rel);
245
+ results.push({
246
+ originalPath,
247
+ hashedPath,
248
+ hash,
249
+ size: stat.size,
250
+ contentType,
251
+ absPath
252
+ });
253
+ }
254
+ results.sort((a, b) => a.originalPath.localeCompare(b.originalPath));
255
+ return results;
256
+ }
257
+ function buildAssetMap(entries) {
258
+ const map = {};
259
+ for (const e of entries) {
260
+ map[e.originalPath] = e.hashedPath;
261
+ }
262
+ return map;
263
+ }
264
+ var MIME_MAP, HASH_LEN, VIDEO_EXTENSIONS;
265
+ var init_scan_theme_assets = __esm({
266
+ "src/utils/scan-theme-assets.ts"() {
267
+ MIME_MAP = {
268
+ ".png": "image/png",
269
+ ".jpg": "image/jpeg",
270
+ ".jpeg": "image/jpeg",
271
+ ".gif": "image/gif",
272
+ ".webp": "image/webp",
273
+ ".avif": "image/avif",
274
+ ".svg": "image/svg+xml",
275
+ ".ico": "image/x-icon",
276
+ ".bmp": "image/bmp",
277
+ ".woff": "font/woff",
278
+ ".woff2": "font/woff2",
279
+ ".ttf": "font/ttf",
280
+ ".otf": "font/otf",
281
+ ".eot": "application/vnd.ms-fontobject",
282
+ ".mp4": "video/mp4",
283
+ ".webm": "video/webm",
284
+ ".mov": "video/quicktime",
285
+ ".ogg": "video/ogg",
286
+ ".json": "application/json"
287
+ };
288
+ HASH_LEN = 8;
289
+ VIDEO_EXTENSIONS = [
290
+ ".mp4",
291
+ ".webm",
292
+ ".ogg",
293
+ ".mov",
294
+ ".avi",
295
+ ".mkv"
296
+ ];
297
+ }
298
+ });
299
+ async function scanAppDirectory(themePath) {
300
+ const appDir = path13.join(themePath, "app");
301
+ let pageFiles;
302
+ try {
303
+ pageFiles = await glob("**/page.{tsx,ts,jsx,js}", { cwd: appDir });
304
+ } catch {
305
+ return [];
306
+ }
307
+ if (pageFiles.length === 0) return [];
308
+ const pages = [];
309
+ for (const pageFile of pageFiles) {
310
+ const routePath = deriveRoutePath(pageFile);
311
+ const absPageFile = path13.join(appDir, pageFile);
312
+ let source;
313
+ try {
314
+ source = await fs11.readFile(absPageFile, "utf-8");
315
+ } catch {
316
+ continue;
317
+ }
318
+ const sections = await extractSectionsFromPage(source, themePath);
319
+ pages.push({
320
+ routePath,
321
+ sourceFile: path13.join("app", pageFile),
322
+ sections
323
+ });
324
+ }
325
+ return pages;
326
+ }
327
+ function deriveRoutePath(pageFile) {
328
+ const dir = path13.dirname(pageFile);
329
+ if (dir === ".") return "/";
330
+ return "/" + dir;
331
+ }
332
+ async function extractSectionsFromPage(source, themePath) {
333
+ const importRegex = /import\s+\w+\s+from\s+["'](@\/|\.\.?\/)(components\/[^"']+)["']/g;
334
+ const sections = [];
335
+ const seen = /* @__PURE__ */ new Set();
336
+ for (const match of source.matchAll(importRegex)) {
337
+ const rawImportPath = match[2];
338
+ const componentDir = path13.dirname(rawImportPath);
339
+ const absComponentDir = path13.join(themePath, componentDir);
340
+ if (seen.has(componentDir)) continue;
341
+ seen.add(componentDir);
342
+ const sectionJsonPath = path13.join(absComponentDir, "section.json");
343
+ let sectionJson;
344
+ try {
345
+ const raw = await fs11.readFile(sectionJsonPath, "utf-8");
346
+ sectionJson = JSON.parse(raw);
347
+ } catch {
348
+ continue;
349
+ }
350
+ if (sectionJson.type !== "opaque-react") continue;
351
+ if (!sectionJson.entry) continue;
352
+ sections.push({
353
+ type: "opaque-react",
354
+ name: sectionJson.name ?? path13.basename(componentDir),
355
+ entry: path13.join(componentDir, sectionJson.entry),
356
+ componentDir
357
+ });
358
+ }
359
+ return sections;
360
+ }
361
+ function buildNextjsPagesMap(pages, themeId) {
362
+ const result = {};
363
+ for (const page of pages) {
364
+ const id = page.routePath === "/" ? "home" : page.routePath.replace(/\//g, "-").replace(/^-/, "");
365
+ const makeSectionType = (name) => `${themeId}-${name.toLowerCase().replace(/\s+/g, "-")}`;
366
+ result[id] = {
367
+ id,
368
+ name: id.charAt(0).toUpperCase() + id.slice(1),
369
+ path: page.routePath,
370
+ config: {
371
+ id,
372
+ path: page.routePath,
373
+ sections: page.sections.map((s, i) => ({
374
+ id: `${id}-section-${i}`,
375
+ type: makeSectionType(s.name),
376
+ sectionType: "opaque-react",
377
+ settings: {}
378
+ }))
379
+ },
380
+ sections: page.sections.map((s, i) => ({
381
+ id: `${id}-section-${i}`,
382
+ type: makeSectionType(s.name),
383
+ sectionType: "opaque-react",
384
+ settings: {}
385
+ }))
386
+ };
387
+ }
388
+ return result;
389
+ }
390
+ var init_nextjs_page_scanner = __esm({
391
+ "src/utils/nextjs-page-scanner.ts"() {
392
+ }
393
+ });
92
394
 
93
395
  // src/utils/compile-theme.ts
94
396
  var compile_theme_exports = {};
@@ -104,8 +406,8 @@ async function generateThemeCSS(themePath, outDir) {
104
406
  const tailwindcss = (await import('tailwindcss')).default;
105
407
  const tailwindConfig = {
106
408
  content: [
107
- path9.join(themePath, "sections/**/*.{ts,tsx}"),
108
- path9.join(themePath, "components/**/*.{ts,tsx}")
409
+ path13.join(themePath, "sections/**/*.{ts,tsx}"),
410
+ path13.join(themePath, "components/**/*.{ts,tsx}")
109
411
  ],
110
412
  theme: { extend: {} },
111
413
  plugins: []
@@ -115,7 +417,7 @@ async function generateThemeCSS(themePath, outDir) {
115
417
  inputCSS,
116
418
  { from: void 0 }
117
419
  );
118
- await fs8.writeFile(path9.join(outDir, "bundle.css"), result.css);
420
+ await fs11.writeFile(path13.join(outDir, "bundle.css"), result.css);
119
421
  logger.info("Generated bundle.css");
120
422
  } catch (err) {
121
423
  logger.warning(
@@ -126,12 +428,12 @@ async function generateThemeCSS(themePath, outDir) {
126
428
  async function resolveNodeModulesFile(startDir, relativePath) {
127
429
  let dir = startDir;
128
430
  while (true) {
129
- const candidate = path9.join(dir, "node_modules", relativePath);
431
+ const candidate = path13.join(dir, "node_modules", relativePath);
130
432
  try {
131
- await fs8.access(candidate);
433
+ await fs11.access(candidate);
132
434
  return candidate;
133
435
  } catch {
134
- const parent = path9.dirname(dir);
436
+ const parent = path13.dirname(dir);
135
437
  if (parent === dir) break;
136
438
  dir = parent;
137
439
  }
@@ -155,7 +457,7 @@ async function scanImportsFromPackage(sourceDir, packageName) {
155
457
  });
156
458
  for (const file of sourceFiles) {
157
459
  try {
158
- const content = await fs8.readFile(path9.join(sourceDir, file), "utf-8");
460
+ const content = await fs11.readFile(path13.join(sourceDir, file), "utf-8");
159
461
  for (const match of content.matchAll(namespaceImportRegex)) {
160
462
  const subpath = match[1] ? match[1].slice(1) : "";
161
463
  if (!result[subpath]) result[subpath] = /* @__PURE__ */ new Set();
@@ -209,17 +511,17 @@ function createCoreGlobalPlugin(themePath) {
209
511
  const distFileName = subpath ? `${subpath}.mjs` : "index.mjs";
210
512
  let distPath = await resolveNodeModulesFile(
211
513
  themePath,
212
- path9.join("@onexapis", "core", "dist", distFileName)
514
+ path13.join("@onexapis", "core", "dist", distFileName)
213
515
  );
214
516
  if (!distPath) {
215
517
  distPath = await resolveNodeModulesFile(
216
518
  __dirname,
217
- path9.join("@onexapis", "core", "dist", distFileName)
519
+ path13.join("@onexapis", "core", "dist", distFileName)
218
520
  );
219
521
  }
220
522
  try {
221
523
  if (!distPath) throw new Error("not found");
222
- const distContent = await fs8.readFile(distPath, "utf-8");
524
+ const distContent = await fs11.readFile(distPath, "utf-8");
223
525
  const exportMatches = distContent.matchAll(/export\s*\{([^}]+)\}/g);
224
526
  for (const m of exportMatches) {
225
527
  const names = m[1].split(",").map((n) => {
@@ -266,180 +568,6 @@ ${namedExportLines}
266
568
  }
267
569
  };
268
570
  }
269
- function createThemeDepsStubPlugin(themePath) {
270
- return {
271
- name: "theme-deps-stub",
272
- setup(build2) {
273
- const tryResolveOrStub = (filter, namespace) => {
274
- build2.onResolve({ filter }, async (args) => {
275
- if (args.pluginData?.skipStub) return void 0;
276
- try {
277
- const result = await build2.resolve(args.path, {
278
- kind: args.kind,
279
- resolveDir: args.resolveDir || themePath,
280
- importer: args.importer,
281
- namespace: "file",
282
- pluginData: { skipStub: true }
283
- });
284
- if (!result.errors.length) return result;
285
- } catch {
286
- }
287
- try {
288
- const req = createRequire(import.meta.url || __filename);
289
- const resolved = req.resolve(args.path);
290
- if (resolved) return { path: resolved, namespace: "file" };
291
- } catch {
292
- }
293
- return { path: args.path, namespace };
294
- });
295
- };
296
- tryResolveOrStub(/^next\//, "next-stub");
297
- build2.onLoad({ filter: /.*/, namespace: "next-stub" }, (args) => {
298
- const stubs = {
299
- "next/image": `
300
- import React from 'react';
301
- const Image = React.forwardRef((props, ref) => {
302
- const { src, alt, width, height, fill, priority, sizes, quality, placeholder, blurDataURL, onLoad, onError, style, className, ...rest } = props;
303
- const imgSrc = typeof src === 'object' ? src.src : src;
304
- const fillStyle = fill ? { position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: style?.objectFit || 'cover', display: 'block' } : {};
305
- const mergedStyle = { ...fillStyle, ...style };
306
- return React.createElement('img', {
307
- ref, src: imgSrc, alt,
308
- width: fill ? undefined : width, height: fill ? undefined : height,
309
- loading: priority ? 'eager' : 'lazy',
310
- style: Object.keys(mergedStyle).length > 0 ? mergedStyle : undefined,
311
- className, onLoad, onError, ...rest,
312
- });
313
- });
314
- export default Image;
315
- `,
316
- "next/link": `
317
- import React from 'react';
318
- const Link = ({ href, children, ...rest }) => React.createElement('a', { href, ...rest }, children);
319
- export default Link;
320
- `,
321
- "next/navigation": `
322
- 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(){} }; }
323
- export function usePathname() { return window.location.pathname; }
324
- export function useSearchParams() { return new URLSearchParams(window.location.search); }
325
- export function useParams() { return {}; }
326
- export function redirect(url) { window.location.href = url; }
327
- export function notFound() { throw new Error('Not Found'); }
328
- `,
329
- "next/headers": `
330
- export function cookies() { return { get(){}, getAll(){ return []; }, set(){}, delete(){}, has(){ return false; } }; }
331
- export function headers() { return new Headers(); }
332
- `
333
- };
334
- return {
335
- contents: stubs[args.path] || "export default {};",
336
- loader: "jsx",
337
- resolveDir: themePath
338
- };
339
- });
340
- const lucideImports = /* @__PURE__ */ new Set();
341
- let lucideThemeScanned = false;
342
- tryResolveOrStub(/^lucide-react/, "lucide-stub");
343
- build2.onLoad({ filter: /.*/, namespace: "lucide-stub" }, async () => {
344
- if (!lucideThemeScanned) {
345
- lucideThemeScanned = true;
346
- try {
347
- const scanned = await scanImportsFromPackage(
348
- themePath,
349
- "lucide-react"
350
- );
351
- for (const names of Object.values(scanned)) {
352
- for (const name of names) lucideImports.add(name);
353
- }
354
- } catch {
355
- }
356
- }
357
- const iconNames = [...lucideImports];
358
- const exports$1 = iconNames.map((n) => `icon as ${n}`).join(", ");
359
- return {
360
- contents: `
361
- const icon = (props) => null;
362
- export { ${exports$1} };
363
- export default new Proxy({}, { get: (_, name) => name === '__esModule' ? true : icon });
364
- `.trim(),
365
- loader: "jsx"
366
- };
367
- });
368
- tryResolveOrStub(/^framer-motion/, "motion-stub");
369
- build2.onLoad({ filter: /.*/, namespace: "motion-stub" }, () => ({
370
- contents: `
371
- import React from 'react';
372
- const handler = { get: (_, name) => {
373
- if (name === '__esModule') return true;
374
- return React.forwardRef((props, ref) => React.createElement(name, { ...props, ref }));
375
- }};
376
- export const motion = new Proxy({}, handler);
377
- export const AnimatePresence = ({ children }) => children || null;
378
- export function useInView() { return true; }
379
- export default { motion, AnimatePresence, useInView };
380
- `.trim(),
381
- loader: "jsx",
382
- resolveDir: themePath
383
- }));
384
- tryResolveOrStub(/^sonner$/, "sonner-stub");
385
- build2.onLoad({ filter: /.*/, namespace: "sonner-stub" }, () => ({
386
- contents: `
387
- export const toast = new Proxy(() => {}, { get: () => () => {} });
388
- export const Toaster = () => null;
389
- export default { toast, Toaster };
390
- `.trim(),
391
- loader: "jsx"
392
- }));
393
- tryResolveOrStub(/^react-hook-form$/, "rhf-stub");
394
- build2.onLoad({ filter: /.*/, namespace: "rhf-stub" }, () => ({
395
- contents: `
396
- export function useForm() {
397
- return {
398
- register: () => ({}),
399
- handleSubmit: (fn) => (e) => { e?.preventDefault?.(); fn({}); },
400
- formState: { errors: {}, isSubmitting: false, isValid: true },
401
- watch: () => undefined,
402
- setValue: () => {},
403
- reset: () => {},
404
- control: {},
405
- };
406
- }
407
- export function useController() { return { field: {}, fieldState: {} }; }
408
- export function useFormContext() { return useForm(); }
409
- `.trim(),
410
- loader: "js"
411
- }));
412
- tryResolveOrStub(/^@hookform\/resolvers/, "hookform-resolvers-stub");
413
- build2.onLoad(
414
- { filter: /.*/, namespace: "hookform-resolvers-stub" },
415
- () => ({
416
- contents: `export function zodResolver() { return () => ({ values: {}, errors: {} }); }`,
417
- loader: "js"
418
- })
419
- );
420
- tryResolveOrStub(/^next-intl$/, "next-intl-stub");
421
- build2.onLoad({ filter: /.*/, namespace: "next-intl-stub" }, () => ({
422
- contents: `
423
- export function useTranslations(ns) {
424
- return (key) => ns ? ns + '.' + key : key;
425
- }
426
- export function useLocale() { return 'en'; }
427
- export function useMessages() { return {}; }
428
- `.trim(),
429
- loader: "js"
430
- }));
431
- tryResolveOrStub(/^zod$/, "zod-stub");
432
- build2.onLoad({ filter: /.*/, namespace: "zod-stub" }, () => ({
433
- contents: `
434
- 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 });
435
- export const z = { string: schema, number: schema, boolean: schema, object: (s) => ({ ...schema(), shape: s }), array: schema, enum: schema, union: schema, literal: schema, infer: undefined };
436
- export default z;
437
- `.trim(),
438
- loader: "js"
439
- }));
440
- }
441
- };
442
- }
443
571
  async function generateThemeData(themePath, outputDir, themeId) {
444
572
  const { createJiti } = await import('jiti');
445
573
  const jiti = createJiti(import.meta.url);
@@ -448,7 +576,7 @@ async function generateThemeData(themePath, outputDir, themeId) {
448
576
  const pages = {};
449
577
  for (const ext of [".ts", ".js"]) {
450
578
  try {
451
- const mod = await jiti.import(path9.join(themePath, `theme.config${ext}`));
579
+ const mod = await jiti.import(path13.join(themePath, `theme.config${ext}`));
452
580
  themeConfig = mod.default || mod;
453
581
  break;
454
582
  } catch {
@@ -456,20 +584,20 @@ async function generateThemeData(themePath, outputDir, themeId) {
456
584
  }
457
585
  for (const ext of [".ts", ".js"]) {
458
586
  try {
459
- const mod = await jiti.import(path9.join(themePath, `theme.layout${ext}`));
587
+ const mod = await jiti.import(path13.join(themePath, `theme.layout${ext}`));
460
588
  layoutConfig = mod.default || mod;
461
589
  break;
462
590
  } catch {
463
591
  }
464
592
  }
465
593
  const schemas = {};
466
- const sectionsDir = path9.join(themePath, "sections");
594
+ const sectionsDir = path13.join(themePath, "sections");
467
595
  try {
468
- const sectionDirs = await fs8.readdir(sectionsDir);
596
+ const sectionDirs = await fs11.readdir(sectionsDir);
469
597
  for (const dir of sectionDirs) {
470
- const schemaFile = path9.join(sectionsDir, dir, `${dir}.schema.ts`);
598
+ const schemaFile = path13.join(sectionsDir, dir, `${dir}.schema.ts`);
471
599
  try {
472
- await fs8.access(schemaFile);
600
+ await fs11.access(schemaFile);
473
601
  const mod = await jiti.import(schemaFile);
474
602
  for (const [key, value] of Object.entries(mod)) {
475
603
  if (key.endsWith("Schema") && value && typeof value === "object" && value.type) {
@@ -481,14 +609,14 @@ async function generateThemeData(themePath, outputDir, themeId) {
481
609
  }
482
610
  } catch {
483
611
  }
484
- const pagesDir = path9.join(themePath, "pages");
612
+ const pagesDir = path13.join(themePath, "pages");
485
613
  try {
486
- const files = await fs8.readdir(pagesDir);
614
+ const files = await fs11.readdir(pagesDir);
487
615
  for (const file of files) {
488
616
  if (!file.match(/\.(ts|js)$/)) continue;
489
617
  const name = file.replace(/\.(ts|js)$/, "");
490
618
  try {
491
- const mod = await jiti.import(path9.join(pagesDir, file));
619
+ const mod = await jiti.import(path13.join(pagesDir, file));
492
620
  const config = mod.default || mod;
493
621
  const sections = (config.sections || []).map((section) => {
494
622
  const schema = schemas[section.type];
@@ -516,8 +644,16 @@ async function generateThemeData(themePath, outputDir, themeId) {
516
644
  }
517
645
  } catch {
518
646
  }
519
- await fs8.writeFile(
520
- path9.join(outputDir, "theme-data.json"),
647
+ if (isNextjsProject(themePath)) {
648
+ const nextjsPages = await scanAppDirectory(themePath);
649
+ if (nextjsPages.length > 0) {
650
+ const nextjsPagesMap = buildNextjsPagesMap(nextjsPages, themeId);
651
+ Object.assign(pages, nextjsPagesMap);
652
+ logger.info(`Scanned ${nextjsPages.length} Next.js app/ pages`);
653
+ }
654
+ }
655
+ await fs11.writeFile(
656
+ path13.join(outputDir, "theme-data.json"),
521
657
  JSON.stringify(
522
658
  {
523
659
  themeId,
@@ -540,22 +676,22 @@ async function generateThemeData(themePath, outputDir, themeId) {
540
676
  logger.info(`Generated theme-data.json (${Object.keys(pages).length} pages)`);
541
677
  }
542
678
  async function contentHashEntry(outputDir) {
543
- const entryPath = path9.join(outputDir, "bundle-entry.js");
544
- const mapPath = path9.join(outputDir, "bundle-entry.js.map");
679
+ const entryPath = path13.join(outputDir, "bundle-entry.js");
680
+ const mapPath = path13.join(outputDir, "bundle-entry.js.map");
545
681
  let entryContent;
546
682
  try {
547
- entryContent = await fs8.readFile(entryPath, "utf-8");
683
+ entryContent = await fs11.readFile(entryPath, "utf-8");
548
684
  } catch {
549
- const indexPath = path9.join(outputDir, "index.js");
685
+ const indexPath = path13.join(outputDir, "index.js");
550
686
  try {
551
- entryContent = await fs8.readFile(indexPath, "utf-8");
687
+ entryContent = await fs11.readFile(indexPath, "utf-8");
552
688
  } catch {
553
689
  logger.warning("No entry file found in output, skipping content hash");
554
690
  return;
555
691
  }
556
692
  const hash2 = crypto.createHash("sha256").update(entryContent).digest("hex").slice(0, 8);
557
693
  const hashedName2 = `bundle-entry-${hash2}.js`;
558
- const indexMapPath = path9.join(outputDir, "index.js.map");
694
+ const indexMapPath = path13.join(outputDir, "index.js.map");
559
695
  const hashedMapName2 = `bundle-entry-${hash2}.js.map`;
560
696
  entryContent = entryContent.replace(
561
697
  /\/\/# sourceMappingURL=index\.js\.map/,
@@ -563,18 +699,18 @@ async function contentHashEntry(outputDir) {
563
699
  );
564
700
  const oldFiles2 = await glob("bundle-entry-*.js*", { cwd: outputDir });
565
701
  for (const f of oldFiles2) {
566
- await fs8.unlink(path9.join(outputDir, f));
702
+ await fs11.unlink(path13.join(outputDir, f));
567
703
  }
568
- await fs8.writeFile(path9.join(outputDir, hashedName2), entryContent);
569
- await fs8.unlink(indexPath);
704
+ await fs11.writeFile(path13.join(outputDir, hashedName2), entryContent);
705
+ await fs11.unlink(indexPath);
570
706
  try {
571
- await fs8.unlink(entryPath);
707
+ await fs11.unlink(entryPath);
572
708
  } catch {
573
709
  }
574
- await fs8.writeFile(entryPath, entryContent);
710
+ await fs11.writeFile(entryPath, entryContent);
575
711
  try {
576
- await fs8.access(indexMapPath);
577
- await fs8.rename(indexMapPath, path9.join(outputDir, hashedMapName2));
712
+ await fs11.access(indexMapPath);
713
+ await fs11.rename(indexMapPath, path13.join(outputDir, hashedMapName2));
578
714
  } catch {
579
715
  }
580
716
  logger.info(`Entry hashed: ${hashedName2}`);
@@ -589,17 +725,17 @@ async function contentHashEntry(outputDir) {
589
725
  );
590
726
  const oldFiles = await glob("bundle-entry-*.js*", { cwd: outputDir });
591
727
  for (const f of oldFiles) {
592
- await fs8.unlink(path9.join(outputDir, f));
728
+ await fs11.unlink(path13.join(outputDir, f));
593
729
  }
594
- await fs8.writeFile(path9.join(outputDir, hashedName), entryContent);
730
+ await fs11.writeFile(path13.join(outputDir, hashedName), entryContent);
595
731
  try {
596
- await fs8.unlink(entryPath);
732
+ await fs11.unlink(entryPath);
597
733
  } catch {
598
734
  }
599
- await fs8.writeFile(entryPath, entryContent);
735
+ await fs11.writeFile(entryPath, entryContent);
600
736
  try {
601
- await fs8.access(mapPath);
602
- await fs8.rename(mapPath, path9.join(outputDir, hashedMapName));
737
+ await fs11.access(mapPath);
738
+ await fs11.rename(mapPath, path13.join(outputDir, hashedMapName));
603
739
  } catch {
604
740
  }
605
741
  logger.info(`Entry hashed: ${hashedName}`);
@@ -611,7 +747,7 @@ async function extractDataRequirements(themePath) {
611
747
  const requirements = {};
612
748
  for (const file of schemaFiles) {
613
749
  try {
614
- const mod = await jiti.import(path9.join(themePath, file));
750
+ const mod = await jiti.import(path13.join(themePath, file));
615
751
  const exports$1 = mod;
616
752
  for (const value of Object.values(exports$1)) {
617
753
  if (value && typeof value === "object" && typeof value.type === "string" && value.dataRequirements && typeof value.dataRequirements === "object") {
@@ -626,12 +762,105 @@ async function extractDataRequirements(themePath) {
626
762
  }
627
763
  return requirements;
628
764
  }
765
+ async function writeGateManifests(themePath, outputDir) {
766
+ try {
767
+ const schemas = await extractSchemas(themePath);
768
+ await fs11.writeFile(
769
+ path13.join(outputDir, "schemas.json"),
770
+ serializeManifest(schemas)
771
+ );
772
+ logger.info(
773
+ `Generated schemas.json (${Object.keys(schemas.sections).length} sections)`
774
+ );
775
+ } catch (err) {
776
+ logger.warning(
777
+ `schemas.json not written: ${err instanceof Error ? err.message : String(err)}`
778
+ );
779
+ }
780
+ try {
781
+ const entries = await scanThemeAssets(outputDir);
782
+ const assets = entries.map((e) => ({
783
+ path: e.originalPath,
784
+ hash: e.hash,
785
+ size: e.size,
786
+ contentType: e.contentType
787
+ }));
788
+ await fs11.writeFile(
789
+ path13.join(outputDir, "asset-manifest.json"),
790
+ JSON.stringify({ manifestVersion: 1, assets }, null, 2)
791
+ );
792
+ logger.info(`Generated asset-manifest.json (${assets.length} assets)`);
793
+ } catch (err) {
794
+ logger.warning(
795
+ `asset-manifest.json not written: ${err instanceof Error ? err.message : String(err)}`
796
+ );
797
+ }
798
+ }
799
+ async function compileSections(themePath, outputDir) {
800
+ const sectionsDir = path13.join(themePath, "sections");
801
+ let sectionDirs;
802
+ try {
803
+ sectionDirs = await fs11.readdir(sectionsDir);
804
+ } catch {
805
+ return;
806
+ }
807
+ for (const dirName of sectionDirs) {
808
+ const sectionSrc = path13.join(sectionsDir, dirName);
809
+ const sectionOut = path13.join(outputDir, "sections", dirName);
810
+ let section = null;
811
+ try {
812
+ const raw = await fs11.readFile(
813
+ path13.join(sectionSrc, "section.manifest.json"),
814
+ "utf-8"
815
+ );
816
+ section = JSON.parse(raw);
817
+ } catch {
818
+ continue;
819
+ }
820
+ switch (section.type) {
821
+ case "editable":
822
+ case "opaque-react":
823
+ break;
824
+ case "html": {
825
+ await fs11.mkdir(sectionOut, { recursive: true });
826
+ const htmlSrc = path13.join(sectionSrc, section.html);
827
+ let htmlContent = await fs11.readFile(htmlSrc, "utf-8");
828
+ htmlContent = htmlContent.replace(
829
+ /<script[^>]+src=["']https?:\/\/[^"']*["'][^>]*><\/script>/gi,
830
+ ""
831
+ );
832
+ await fs11.writeFile(path13.join(sectionOut, path13.basename(section.html)), htmlContent);
833
+ if (section.css) {
834
+ await fs11.copyFile(
835
+ path13.join(sectionSrc, section.css),
836
+ path13.join(sectionOut, path13.basename(section.css))
837
+ );
838
+ }
839
+ break;
840
+ }
841
+ case "iframe":
842
+ break;
843
+ case "webcomponent": {
844
+ await fs11.mkdir(sectionOut, { recursive: true });
845
+ await fs11.copyFile(
846
+ path13.join(sectionSrc, section.bundle),
847
+ path13.join(sectionOut, path13.basename(section.bundle))
848
+ );
849
+ break;
850
+ }
851
+ default:
852
+ throw new Error(
853
+ `Unknown section type. Valid types: editable, opaque-react, html, iframe, webcomponent`
854
+ );
855
+ }
856
+ }
857
+ }
629
858
  async function generateManifest(themeName, themePath, outputDir) {
630
859
  let version2 = "1.0.0";
631
860
  let themeId = themeName;
632
861
  try {
633
- const pkgContent = await fs8.readFile(
634
- path9.join(themePath, "package.json"),
862
+ const pkgContent = await fs11.readFile(
863
+ path13.join(themePath, "package.json"),
635
864
  "utf-8"
636
865
  );
637
866
  const pkg = JSON.parse(pkgContent);
@@ -649,7 +878,7 @@ async function generateManifest(themeName, themePath, outputDir) {
649
878
  const dataRequirements = await extractDataRequirements(themePath);
650
879
  let hasThemeConfig = false;
651
880
  try {
652
- await fs8.access(path9.join(themePath, "theme.config.ts"));
881
+ await fs11.access(path13.join(themePath, "theme.config.ts"));
653
882
  hasThemeConfig = true;
654
883
  } catch {
655
884
  }
@@ -690,24 +919,34 @@ async function generateManifest(themeName, themePath, outputDir) {
690
919
  // Section data requirements for server-side prefetching (keyed by section type)
691
920
  dataRequirements
692
921
  };
693
- await fs8.writeFile(
694
- path9.join(outputDir, "manifest.json"),
922
+ await fs11.writeFile(
923
+ path13.join(outputDir, "manifest.json"),
695
924
  JSON.stringify(manifest, null, 2)
696
925
  );
697
926
  }
698
927
  async function compileStandaloneTheme(themePath, themeName) {
699
- const outputDir = path9.join(themePath, "dist");
700
- const bundleEntry = path9.join(themePath, "bundle-entry.ts");
701
- const indexEntry = path9.join(themePath, "index.ts");
928
+ const outputDir = path13.join(themePath, "dist");
929
+ const isNextjs = isNextjsProject(themePath);
930
+ if (isNextjs) {
931
+ logger.info("Detected Next.js project \u2014 using next/* shims");
932
+ }
933
+ const bundleEntry = path13.join(themePath, "bundle-entry.ts");
934
+ const indexEntry = path13.join(themePath, "index.ts");
702
935
  let entryPoint = indexEntry;
703
936
  try {
704
- await fs8.access(bundleEntry);
937
+ await fs11.access(bundleEntry);
705
938
  entryPoint = bundleEntry;
706
939
  } catch {
707
940
  }
708
- const shimPath = path9.join(outputDir, ".process-shim.js");
709
- await fs8.mkdir(outputDir, { recursive: true });
710
- await fs8.writeFile(shimPath, PROCESS_SHIM);
941
+ const shimPath = path13.join(outputDir, ".process-shim.js");
942
+ await fs11.mkdir(outputDir, { recursive: true });
943
+ await fs11.writeFile(shimPath, PROCESS_SHIM);
944
+ const plugins = [
945
+ reactGlobalPlugin,
946
+ reactQueryGlobalPlugin,
947
+ createCoreGlobalPlugin(themePath)
948
+ ];
949
+ if (isNextjs) plugins.unshift(nextShimPlugin);
711
950
  const buildOptions = {
712
951
  entryPoints: [entryPoint],
713
952
  bundle: true,
@@ -719,12 +958,7 @@ async function compileStandaloneTheme(themePath, themeName) {
719
958
  banner: {
720
959
  js: '"use client";'
721
960
  },
722
- plugins: [
723
- reactGlobalPlugin,
724
- reactQueryGlobalPlugin,
725
- createCoreGlobalPlugin(themePath),
726
- createThemeDepsStubPlugin(themePath)
727
- ],
961
+ plugins,
728
962
  external: [],
729
963
  alias: {
730
964
  events: "events/",
@@ -758,19 +992,21 @@ async function compileStandaloneTheme(themePath, themeName) {
758
992
  try {
759
993
  const result = await esbuild.build(buildOptions);
760
994
  try {
761
- await fs8.unlink(shimPath);
995
+ await fs11.unlink(shimPath);
762
996
  } catch {
763
997
  }
998
+ await compileSections(themePath, outputDir);
764
999
  await contentHashEntry(outputDir);
765
- const themeAssetsDir = path9.join(themePath, "assets");
766
- const distThemeAssets = path9.join(outputDir, "theme-assets");
1000
+ const themeAssetsDir = path13.join(themePath, "assets");
1001
+ const distThemeAssets = path13.join(outputDir, "theme-assets");
767
1002
  try {
768
- await fs8.access(themeAssetsDir);
769
- await fs8.cp(themeAssetsDir, distThemeAssets, { recursive: true });
1003
+ await fs11.access(themeAssetsDir);
1004
+ await fs11.cp(themeAssetsDir, distThemeAssets, { recursive: true });
770
1005
  logger.info("Copied static assets to dist/theme-assets/");
771
1006
  } catch {
772
1007
  }
773
1008
  await generateManifest(themeName, themePath, outputDir);
1009
+ await writeGateManifests(themePath, outputDir);
774
1010
  await generateThemeData(themePath, outputDir, themeName);
775
1011
  await generateThemeCSS(themePath, outputDir);
776
1012
  if (result.metafile) {
@@ -785,7 +1021,7 @@ async function compileStandaloneTheme(themePath, themeName) {
785
1021
  return true;
786
1022
  } catch (error) {
787
1023
  try {
788
- await fs8.unlink(shimPath);
1024
+ await fs11.unlink(shimPath);
789
1025
  } catch {
790
1026
  }
791
1027
  logger.error(`esbuild compilation failed: ${error}`);
@@ -793,18 +1029,25 @@ async function compileStandaloneTheme(themePath, themeName) {
793
1029
  }
794
1030
  }
795
1031
  async function compileStandaloneThemeDev(themePath, themeName) {
796
- const outputDir = path9.join(themePath, "dist");
797
- const bundleEntry = path9.join(themePath, "bundle-entry.ts");
798
- const indexEntry = path9.join(themePath, "index.ts");
1032
+ const outputDir = path13.join(themePath, "dist");
1033
+ const isNextjs = isNextjsProject(themePath);
1034
+ const bundleEntry = path13.join(themePath, "bundle-entry.ts");
1035
+ const indexEntry = path13.join(themePath, "index.ts");
799
1036
  let entryPoint = indexEntry;
800
1037
  try {
801
- await fs8.access(bundleEntry);
1038
+ await fs11.access(bundleEntry);
802
1039
  entryPoint = bundleEntry;
803
1040
  } catch {
804
1041
  }
805
- const shimPath = path9.join(outputDir, ".process-shim.js");
806
- await fs8.mkdir(outputDir, { recursive: true });
807
- await fs8.writeFile(shimPath, PROCESS_SHIM);
1042
+ const shimPath = path13.join(outputDir, ".process-shim.js");
1043
+ await fs11.mkdir(outputDir, { recursive: true });
1044
+ await fs11.writeFile(shimPath, PROCESS_SHIM);
1045
+ const devPlugins = [
1046
+ reactGlobalPlugin,
1047
+ reactQueryGlobalPlugin,
1048
+ createCoreGlobalPlugin(themePath)
1049
+ ];
1050
+ if (isNextjs) devPlugins.unshift(nextShimPlugin);
808
1051
  const buildOptions = {
809
1052
  entryPoints: [entryPoint],
810
1053
  bundle: true,
@@ -815,12 +1058,7 @@ async function compileStandaloneThemeDev(themePath, themeName) {
815
1058
  banner: {
816
1059
  js: '"use client";'
817
1060
  },
818
- plugins: [
819
- reactGlobalPlugin,
820
- reactQueryGlobalPlugin,
821
- createCoreGlobalPlugin(themePath),
822
- createThemeDepsStubPlugin(themePath)
823
- ],
1061
+ plugins: devPlugins,
824
1062
  external: [],
825
1063
  alias: {
826
1064
  events: "events/",
@@ -858,18 +1096,18 @@ async function compileStandaloneThemeDev(themePath, themeName) {
858
1096
  return { context: context2, outputDir };
859
1097
  }
860
1098
  async function compilePreviewRuntime(themePath) {
861
- const outputDir = path9.join(themePath, "dist");
862
- await fs8.mkdir(outputDir, { recursive: true });
863
- const outputPath = path9.join(outputDir, "preview-runtime.js");
1099
+ const outputDir = path13.join(themePath, "dist");
1100
+ await fs11.mkdir(outputDir, { recursive: true });
1101
+ const outputPath = path13.join(outputDir, "preview-runtime.js");
864
1102
  const locations = [
865
- path9.join(__dirname, "..", "preview", "preview-app.tsx"),
866
- path9.join(__dirname, "preview", "preview-app.tsx"),
867
- path9.join(__dirname, "..", "..", "src", "preview", "preview-app.tsx")
1103
+ path13.join(__dirname, "..", "preview", "preview-app.tsx"),
1104
+ path13.join(__dirname, "preview", "preview-app.tsx"),
1105
+ path13.join(__dirname, "..", "..", "src", "preview", "preview-app.tsx")
868
1106
  ];
869
1107
  let previewEntryPath = null;
870
1108
  for (const loc of locations) {
871
1109
  try {
872
- await fs8.access(loc);
1110
+ await fs11.access(loc);
873
1111
  previewEntryPath = loc;
874
1112
  break;
875
1113
  } catch {
@@ -952,10 +1190,10 @@ ${locations.join("\n")}`
952
1190
  if (!lucideScanned) {
953
1191
  lucideScanned = true;
954
1192
  const coreSrcCandidates = [
955
- path9.join(themePath, "node_modules", "@onexapis", "core", "src"),
956
- path9.join(themePath, "..", "..", "packages", "core", "src"),
1193
+ path13.join(themePath, "node_modules", "@onexapis", "core", "src"),
1194
+ path13.join(themePath, "..", "..", "packages", "core", "src"),
957
1195
  // monorepo sibling
958
- path9.join(
1196
+ path13.join(
959
1197
  __dirname,
960
1198
  "..",
961
1199
  "..",
@@ -970,7 +1208,7 @@ ${locations.join("\n")}`
970
1208
  let coreSourceDir = null;
971
1209
  for (const candidate of coreSrcCandidates) {
972
1210
  try {
973
- await fs8.access(candidate);
1211
+ await fs11.access(candidate);
974
1212
  coreSourceDir = candidate;
975
1213
  break;
976
1214
  } catch {
@@ -989,21 +1227,21 @@ ${locations.join("\n")}`
989
1227
  }
990
1228
  } else {
991
1229
  const coreDistCandidates = [
992
- path9.join(themePath, "node_modules", "@onexapis", "core", "dist")
1230
+ path13.join(themePath, "node_modules", "@onexapis", "core", "dist")
993
1231
  ];
994
1232
  const resolvedDist = await resolveNodeModulesFile(
995
1233
  __dirname,
996
- path9.join("@onexapis", "core", "dist")
1234
+ path13.join("@onexapis", "core", "dist")
997
1235
  );
998
1236
  if (resolvedDist) coreDistCandidates.push(resolvedDist);
999
1237
  for (const candidate of coreDistCandidates) {
1000
1238
  try {
1001
- await fs8.access(candidate);
1239
+ await fs11.access(candidate);
1002
1240
  const mjsFiles = await glob("*.mjs", { cwd: candidate });
1003
1241
  const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']lucide-react["']/g;
1004
1242
  for (const file of mjsFiles) {
1005
- const content = await fs8.readFile(
1006
- path9.join(candidate, file),
1243
+ const content = await fs11.readFile(
1244
+ path13.join(candidate, file),
1007
1245
  "utf-8"
1008
1246
  );
1009
1247
  for (const match of content.matchAll(importRegex)) {
@@ -1058,7 +1296,7 @@ export default new Proxy({}, { get: (_, name) => name === '__esModule' ? true :
1058
1296
  const req = createRequire(import.meta.url || __filename);
1059
1297
  const cjsPath = req.resolve("framer-motion");
1060
1298
  const pkgDir = cjsPath.replace(/[/\\]dist[/\\].*$/, "");
1061
- const esmEntry = path9.join(pkgDir, "dist", "es", "index.mjs");
1299
+ const esmEntry = path13.join(pkgDir, "dist", "es", "index.mjs");
1062
1300
  const { existsSync } = await import('fs');
1063
1301
  if (existsSync(esmEntry)) {
1064
1302
  return { path: esmEntry, namespace: "file" };
@@ -1157,8 +1395,8 @@ export function headers() { return new Headers(); }
1157
1395
  });
1158
1396
  }
1159
1397
  };
1160
- const shimPath = path9.join(outputDir, ".process-shim-preview.js");
1161
- await fs8.writeFile(shimPath, PROCESS_SHIM);
1398
+ const shimPath = path13.join(outputDir, ".process-shim-preview.js");
1399
+ await fs11.writeFile(shimPath, PROCESS_SHIM);
1162
1400
  await esbuild.build({
1163
1401
  entryPoints: [previewEntryPath],
1164
1402
  bundle: true,
@@ -1193,15 +1431,19 @@ export function headers() { return new Headers(); }
1193
1431
  }
1194
1432
  });
1195
1433
  try {
1196
- await fs8.unlink(shimPath);
1434
+ await fs11.unlink(shimPath);
1197
1435
  } catch {
1198
1436
  }
1199
1437
  return outputPath;
1200
1438
  }
1201
- var PROCESS_SHIM, reactGlobalPlugin, reactQueryGlobalPlugin;
1439
+ var PROCESS_SHIM, reactGlobalPlugin, reactQueryGlobalPlugin, nextShimPlugin;
1202
1440
  var init_compile_theme = __esm({
1203
1441
  "src/utils/compile-theme.ts"() {
1204
1442
  init_logger();
1443
+ init_extract_schemas();
1444
+ init_scan_theme_assets();
1445
+ init_detect_nextjs();
1446
+ init_nextjs_page_scanner();
1205
1447
  PROCESS_SHIM = `
1206
1448
  if (typeof process === "undefined") {
1207
1449
  globalThis.process = {
@@ -1347,6 +1589,145 @@ export const {
1347
1589
  }));
1348
1590
  }
1349
1591
  };
1592
+ nextShimPlugin = {
1593
+ name: "next-shim",
1594
+ setup(build2) {
1595
+ for (const serverModule of ["next/headers", "next/server", "next/cache"]) {
1596
+ build2.onResolve({ filter: new RegExp(`^${serverModule.replace("/", "\\/")}`) }, (args) => ({
1597
+ path: args.path,
1598
+ namespace: "next-server-error"
1599
+ }));
1600
+ }
1601
+ build2.onLoad({ filter: /.*/, namespace: "next-server-error" }, (args) => ({
1602
+ errors: [
1603
+ {
1604
+ text: `"${args.path}" is server-only and cannot be used in a OneX theme bundle. Use client-side equivalents or remove the import.`
1605
+ }
1606
+ ]
1607
+ }));
1608
+ build2.onResolve({ filter: /^next\/navigation$/ }, () => ({
1609
+ path: "next-navigation-shim",
1610
+ namespace: "next-shim"
1611
+ }));
1612
+ build2.onLoad({ filter: /^next-navigation-shim$/, namespace: "next-shim" }, () => ({
1613
+ contents: `
1614
+ export function usePathname() {
1615
+ if (typeof window === 'undefined') return '/';
1616
+ return window.location.pathname;
1617
+ }
1618
+ export function useSearchParams() {
1619
+ if (typeof window === 'undefined') return new URLSearchParams();
1620
+ return new URLSearchParams(window.location.search);
1621
+ }
1622
+ export function useParams() {
1623
+ if (typeof window === 'undefined') return {};
1624
+ return (globalThis.__ONEX_ROUTE_PARAMS__) ?? {};
1625
+ }
1626
+ export function useRouter() {
1627
+ return {
1628
+ push(url) { if (typeof window !== 'undefined') window.location.href = url; },
1629
+ replace(url) { if (typeof window !== 'undefined') window.location.replace(url); },
1630
+ back() { if (typeof window !== 'undefined') window.history.back(); },
1631
+ forward() { if (typeof window !== 'undefined') window.history.forward(); },
1632
+ refresh() { if (typeof window !== 'undefined') window.location.reload(); },
1633
+ prefetch() {},
1634
+ };
1635
+ }
1636
+ export function redirect(url) {
1637
+ if (typeof window !== 'undefined') window.location.href = url;
1638
+ throw new Error('redirect');
1639
+ }
1640
+ export function notFound() { throw new Error('not-found'); }
1641
+ `.trim(),
1642
+ loader: "js"
1643
+ }));
1644
+ build2.onResolve({ filter: /^next\/font\// }, () => ({
1645
+ path: "next-font-shim",
1646
+ namespace: "next-shim"
1647
+ }));
1648
+ build2.onLoad({ filter: /^next-font-shim$/, namespace: "next-shim" }, () => ({
1649
+ contents: `
1650
+ function makeFont(family) {
1651
+ return function(_opts) {
1652
+ return {
1653
+ className: '',
1654
+ style: { fontFamily: family + ', system-ui, sans-serif' },
1655
+ variable: '--font-' + family.toLowerCase().replace(/\\s+/g, '-'),
1656
+ };
1657
+ };
1658
+ }
1659
+ export const Inter = makeFont('Inter');
1660
+ export const Roboto = makeFont('Roboto');
1661
+ export const Open_Sans = makeFont('Open Sans');
1662
+ export const Lato = makeFont('Lato');
1663
+ export const Montserrat = makeFont('Montserrat');
1664
+ export const Poppins = makeFont('Poppins');
1665
+ export const Raleway = makeFont('Raleway');
1666
+ export const Nunito = makeFont('Nunito');
1667
+ export const Geist = makeFont('Geist');
1668
+ export const Geist_Mono = makeFont('Geist Mono');
1669
+ export const DM_Sans = makeFont('DM Sans');
1670
+ export const Plus_Jakarta_Sans = makeFont('Plus Jakarta Sans');
1671
+ export function localFont(_opts) {
1672
+ return { className: '', style: { fontFamily: 'system-ui, sans-serif' }, variable: '--font-local' };
1673
+ }
1674
+ `.trim(),
1675
+ loader: "js"
1676
+ }));
1677
+ build2.onResolve({ filter: /^next\/dynamic$/ }, () => ({
1678
+ path: "next-dynamic-shim",
1679
+ namespace: "next-shim"
1680
+ }));
1681
+ build2.onLoad({ filter: /^next-dynamic-shim$/, namespace: "next-shim" }, () => ({
1682
+ contents: `
1683
+ import { lazy, Suspense, createElement } from 'react';
1684
+ export default function dynamic(loader, opts) {
1685
+ const Lazy = lazy(loader);
1686
+ return function DynamicComponent(props) {
1687
+ return createElement(Suspense, { fallback: opts?.loading ? createElement(opts.loading) : null },
1688
+ createElement(Lazy, props));
1689
+ };
1690
+ }
1691
+ `.trim(),
1692
+ loader: "js"
1693
+ }));
1694
+ build2.onResolve({ filter: /^next\/image$/ }, () => ({
1695
+ path: "next-image-shim",
1696
+ namespace: "next-shim"
1697
+ }));
1698
+ build2.onLoad({ filter: /^next-image-shim$/, namespace: "next-shim" }, () => ({
1699
+ contents: `
1700
+ import { createElement } from 'react';
1701
+ export default function Image({ src, alt, width, height, style, className, ...rest }) {
1702
+ return createElement('img', { src, alt, width, height, style, className, ...rest });
1703
+ }
1704
+ `.trim(),
1705
+ loader: "js"
1706
+ }));
1707
+ build2.onResolve({ filter: /^next\/link$/ }, () => ({
1708
+ path: "next-link-shim",
1709
+ namespace: "next-shim"
1710
+ }));
1711
+ build2.onLoad({ filter: /^next-link-shim$/, namespace: "next-shim" }, () => ({
1712
+ contents: `
1713
+ import { createElement } from 'react';
1714
+ export default function Link({ href, children, className, style, ...rest }) {
1715
+ return createElement('a', { href, className, style, ...rest }, children);
1716
+ }
1717
+ `.trim(),
1718
+ loader: "js"
1719
+ }));
1720
+ build2.onResolve({ filter: /^next\// }, () => ({
1721
+ path: "next-noop-shim",
1722
+ namespace: "next-shim"
1723
+ }));
1724
+ build2.onLoad({ filter: /^next-noop-shim$/, namespace: "next-shim" }, () => ({
1725
+ contents: `export default {};
1726
+ `,
1727
+ loader: "js"
1728
+ }));
1729
+ }
1730
+ };
1350
1731
  }
1351
1732
  });
1352
1733
 
@@ -1357,7 +1738,7 @@ __export(dev_server_exports, {
1357
1738
  });
1358
1739
  function createDevServer(options) {
1359
1740
  const clients = /* @__PURE__ */ new Set();
1360
- const themeDataPath = path9.join(options.distDir, "theme-data.json");
1741
+ const themeDataPath = path13.join(options.distDir, "theme-data.json");
1361
1742
  const server = http.createServer((req, res) => {
1362
1743
  res.setHeader("Access-Control-Allow-Origin", "*");
1363
1744
  res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
@@ -1383,8 +1764,8 @@ function createDevServer(options) {
1383
1764
  if (pathname.startsWith("/_assets/")) {
1384
1765
  const parts = pathname.replace(/^\/_assets\//, "").split("/");
1385
1766
  const assetSubpath = parts.slice(1).join("/");
1386
- const assetPath = path9.join(options.themePath, "assets", assetSubpath);
1387
- if (!assetPath.startsWith(path9.join(options.themePath, "assets"))) {
1767
+ const assetPath = path13.join(options.themePath, "assets", assetSubpath);
1768
+ if (!assetPath.startsWith(path13.join(options.themePath, "assets"))) {
1388
1769
  res.writeHead(403);
1389
1770
  res.end("Forbidden");
1390
1771
  return;
@@ -1395,8 +1776,8 @@ function createDevServer(options) {
1395
1776
  if (pathname.startsWith("/themes/")) {
1396
1777
  const match = pathname.match(/^\/themes\/[^/]+\/assets\/(.+)/);
1397
1778
  if (match) {
1398
- const assetPath = path9.join(options.themePath, "assets", match[1]);
1399
- if (!assetPath.startsWith(path9.join(options.themePath, "assets"))) {
1779
+ const assetPath = path13.join(options.themePath, "assets", match[1]);
1780
+ if (!assetPath.startsWith(path13.join(options.themePath, "assets"))) {
1400
1781
  res.writeHead(403);
1401
1782
  res.end("Forbidden");
1402
1783
  return;
@@ -1408,26 +1789,26 @@ function createDevServer(options) {
1408
1789
  if (pathname.startsWith("/assets/")) {
1409
1790
  const subpath = pathname.replace(/^\/assets\//, "");
1410
1791
  const segments = subpath.split("/");
1411
- const assetsBase = path9.join(options.themePath, "assets");
1792
+ const assetsBase = path13.join(options.themePath, "assets");
1412
1793
  let assetPath;
1413
1794
  if (segments[0] === options.themeName || segments[0] === options.themeName.replace(/^my-/, "")) {
1414
- assetPath = path9.join(assetsBase, segments.slice(1).join("/"));
1795
+ assetPath = path13.join(assetsBase, segments.slice(1).join("/"));
1415
1796
  } else {
1416
- assetPath = path9.join(assetsBase, subpath);
1797
+ assetPath = path13.join(assetsBase, subpath);
1417
1798
  }
1418
1799
  if (assetPath.startsWith(assetsBase) && fs3.existsSync(assetPath)) {
1419
1800
  serveFile(res, assetPath);
1420
1801
  return;
1421
1802
  }
1422
1803
  if (segments.length > 1) {
1423
- const fallbackPath = path9.join(assetsBase, segments.slice(1).join("/"));
1804
+ const fallbackPath = path13.join(assetsBase, segments.slice(1).join("/"));
1424
1805
  if (fallbackPath.startsWith(assetsBase) && fs3.existsSync(fallbackPath)) {
1425
1806
  serveFile(res, fallbackPath);
1426
1807
  return;
1427
1808
  }
1428
1809
  }
1429
1810
  }
1430
- const filePath = path9.join(options.distDir, pathname);
1811
+ const filePath = path13.join(options.distDir, pathname);
1431
1812
  if (!filePath.startsWith(options.distDir)) {
1432
1813
  res.writeHead(403);
1433
1814
  res.end("Forbidden");
@@ -1470,7 +1851,7 @@ function serveFile(res, filePath) {
1470
1851
  res.end("Not Found");
1471
1852
  return;
1472
1853
  }
1473
- const ext = path9.extname(filePath);
1854
+ const ext = path13.extname(filePath);
1474
1855
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
1475
1856
  const content = fs3.readFileSync(filePath);
1476
1857
  res.writeHead(200, { "Content-Type": contentType });
@@ -1587,26 +1968,26 @@ var init_dev_server = __esm({
1587
1968
  // src/utils/file-helpers.ts
1588
1969
  init_logger();
1589
1970
  async function renderTemplate(templatePath, data) {
1590
- const template = await fs.readFile(templatePath, "utf-8");
1971
+ const template = await fs8.readFile(templatePath, "utf-8");
1591
1972
  return ejs.render(template, data);
1592
1973
  }
1593
1974
  async function writeFile(filePath, content) {
1594
- await fs.ensureDir(path9.dirname(filePath));
1595
- await fs.writeFile(filePath, content, "utf-8");
1975
+ await fs8.ensureDir(path13.dirname(filePath));
1976
+ await fs8.writeFile(filePath, content, "utf-8");
1596
1977
  }
1597
1978
  function getTemplatesDir() {
1598
1979
  const locations = [
1599
- path9.join(__dirname, "../../templates"),
1980
+ path13.join(__dirname, "../../templates"),
1600
1981
  // Development
1601
- path9.join(__dirname, "../templates"),
1982
+ path13.join(__dirname, "../templates"),
1602
1983
  // Production (dist/)
1603
- path9.join(process.cwd(), "templates"),
1984
+ path13.join(process.cwd(), "templates"),
1604
1985
  // Fallback
1605
- path9.join(process.cwd(), "packages/cli/templates")
1986
+ path13.join(process.cwd(), "packages/cli/templates")
1606
1987
  // Monorepo
1607
1988
  ];
1608
1989
  for (const location of locations) {
1609
- if (fs.existsSync(location)) {
1990
+ if (fs8.existsSync(location)) {
1610
1991
  return location;
1611
1992
  }
1612
1993
  }
@@ -1614,18 +1995,18 @@ function getTemplatesDir() {
1614
1995
  }
1615
1996
  async function copyTemplate(templateName, targetDir, data) {
1616
1997
  const templatesDir = getTemplatesDir();
1617
- const templateDir = path9.join(templatesDir, templateName);
1618
- if (!fs.existsSync(templateDir)) {
1998
+ const templateDir = path13.join(templatesDir, templateName);
1999
+ if (!fs8.existsSync(templateDir)) {
1619
2000
  throw new Error(
1620
- `Template "${templateName}" not found at ${templateDir}. Available templates: ${fs.readdirSync(templatesDir).join(", ")}`
2001
+ `Template "${templateName}" not found at ${templateDir}. Available templates: ${fs8.readdirSync(templatesDir).join(", ")}`
1621
2002
  );
1622
2003
  }
1623
- await fs.ensureDir(targetDir);
1624
- const files = await fs.readdir(templateDir);
2004
+ await fs8.ensureDir(targetDir);
2005
+ const files = await fs8.readdir(templateDir);
1625
2006
  for (const file of files) {
1626
- const templatePath = path9.join(templateDir, file);
1627
- const targetPath = path9.join(targetDir, file);
1628
- const stat = await fs.stat(templatePath);
2007
+ const templatePath = path13.join(templateDir, file);
2008
+ const targetPath = path13.join(targetDir, file);
2009
+ const stat = await fs8.stat(templatePath);
1629
2010
  if (stat.isDirectory()) {
1630
2011
  await copyTemplateDir(templatePath, targetPath, data);
1631
2012
  } else if (file.endsWith(".ejs")) {
@@ -1633,17 +2014,17 @@ async function copyTemplate(templateName, targetDir, data) {
1633
2014
  const outputPath = targetPath.replace(/\.ejs$/, "");
1634
2015
  await writeFile(outputPath, content);
1635
2016
  } else {
1636
- await fs.copy(templatePath, targetPath);
2017
+ await fs8.copy(templatePath, targetPath);
1637
2018
  }
1638
2019
  }
1639
2020
  }
1640
2021
  async function copyTemplateDir(templateDir, targetDir, data) {
1641
- await fs.ensureDir(targetDir);
1642
- const files = await fs.readdir(templateDir);
2022
+ await fs8.ensureDir(targetDir);
2023
+ const files = await fs8.readdir(templateDir);
1643
2024
  for (const file of files) {
1644
- const templatePath = path9.join(templateDir, file);
1645
- const targetPath = path9.join(targetDir, file);
1646
- const stat = await fs.stat(templatePath);
2025
+ const templatePath = path13.join(templateDir, file);
2026
+ const targetPath = path13.join(targetDir, file);
2027
+ const stat = await fs8.stat(templatePath);
1647
2028
  if (stat.isDirectory()) {
1648
2029
  await copyTemplateDir(templatePath, targetPath, data);
1649
2030
  } else if (file.endsWith(".ejs")) {
@@ -1651,38 +2032,38 @@ async function copyTemplateDir(templateDir, targetDir, data) {
1651
2032
  const outputPath = targetPath.replace(/\.ejs$/, "");
1652
2033
  await writeFile(outputPath, content);
1653
2034
  } else {
1654
- await fs.copy(templatePath, targetPath);
2035
+ await fs8.copy(templatePath, targetPath);
1655
2036
  }
1656
2037
  }
1657
2038
  }
1658
2039
  function getProjectRoot() {
1659
2040
  let currentDir = process.cwd();
1660
- while (currentDir !== path9.parse(currentDir).root) {
1661
- const packageJsonPath = path9.join(currentDir, "package.json");
1662
- if (fs.existsSync(packageJsonPath)) {
1663
- const packageJson = fs.readJsonSync(packageJsonPath);
1664
- if (packageJson.workspaces || fs.existsSync(path9.join(currentDir, "src/themes")) || fs.existsSync(path9.join(currentDir, "themes"))) {
2041
+ while (currentDir !== path13.parse(currentDir).root) {
2042
+ const packageJsonPath = path13.join(currentDir, "package.json");
2043
+ if (fs8.existsSync(packageJsonPath)) {
2044
+ const packageJson = fs8.readJsonSync(packageJsonPath);
2045
+ if (packageJson.workspaces || fs8.existsSync(path13.join(currentDir, "src/themes")) || fs8.existsSync(path13.join(currentDir, "themes"))) {
1665
2046
  return currentDir;
1666
2047
  }
1667
2048
  }
1668
- currentDir = path9.dirname(currentDir);
2049
+ currentDir = path13.dirname(currentDir);
1669
2050
  }
1670
2051
  return process.cwd();
1671
2052
  }
1672
2053
  function getThemesDir() {
1673
2054
  const root = getProjectRoot();
1674
- if (fs.existsSync(path9.join(root, "themes")))
1675
- return path9.join(root, "themes");
1676
- if (fs.existsSync(path9.join(root, "src/themes")))
1677
- return path9.join(root, "src/themes");
1678
- return path9.dirname(root);
2055
+ if (fs8.existsSync(path13.join(root, "themes")))
2056
+ return path13.join(root, "themes");
2057
+ if (fs8.existsSync(path13.join(root, "src/themes")))
2058
+ return path13.join(root, "src/themes");
2059
+ return path13.dirname(root);
1679
2060
  }
1680
2061
  function getFeaturesDir() {
1681
- return path9.join(getProjectRoot(), "src/features");
2062
+ return path13.join(getProjectRoot(), "src/features");
1682
2063
  }
1683
2064
  function isOneXProject() {
1684
2065
  const root = getProjectRoot();
1685
- return fs.existsSync(path9.join(root, "themes")) || fs.existsSync(path9.join(root, "src/themes")) || fs.existsSync(path9.join(root, "theme.config.ts")) || fs.existsSync(path9.join(root, "bundle-entry.ts"));
2066
+ return fs8.existsSync(path13.join(root, "themes")) || fs8.existsSync(path13.join(root, "src/themes")) || fs8.existsSync(path13.join(root, "theme.config.ts")) || fs8.existsSync(path13.join(root, "bundle-entry.ts"));
1686
2067
  }
1687
2068
  function ensureOneXProject() {
1688
2069
  if (!isOneXProject()) {
@@ -1694,17 +2075,17 @@ function ensureOneXProject() {
1694
2075
  }
1695
2076
  function listThemes() {
1696
2077
  const themesDir = getThemesDir();
1697
- if (!fs.existsSync(themesDir)) {
2078
+ if (!fs8.existsSync(themesDir)) {
1698
2079
  return [];
1699
2080
  }
1700
- return fs.readdirSync(themesDir).filter((name) => {
1701
- const themePath = path9.join(themesDir, name);
1702
- return fs.statSync(themePath).isDirectory() && (fs.existsSync(path9.join(themePath, "theme.config.ts")) || fs.existsSync(path9.join(themePath, "bundle-entry.ts")) || fs.existsSync(path9.join(themePath, "manifest.ts")));
2081
+ return fs8.readdirSync(themesDir).filter((name) => {
2082
+ const themePath = path13.join(themesDir, name);
2083
+ return fs8.statSync(themePath).isDirectory() && (fs8.existsSync(path13.join(themePath, "theme.config.ts")) || fs8.existsSync(path13.join(themePath, "bundle-entry.ts")) || fs8.existsSync(path13.join(themePath, "manifest.ts")));
1703
2084
  });
1704
2085
  }
1705
2086
  function themeExists(themeName) {
1706
- const themePath = path9.join(getThemesDir(), themeName);
1707
- return fs.existsSync(themePath) && (fs.existsSync(path9.join(themePath, "theme.config.ts")) || fs.existsSync(path9.join(themePath, "bundle-entry.ts")) || fs.existsSync(path9.join(themePath, "manifest.ts")));
2087
+ const themePath = path13.join(getThemesDir(), themeName);
2088
+ return fs8.existsSync(themePath) && (fs8.existsSync(path13.join(themePath, "theme.config.ts")) || fs8.existsSync(path13.join(themePath, "bundle-entry.ts")) || fs8.existsSync(path13.join(themePath, "manifest.ts")));
1708
2089
  }
1709
2090
  function detectPackageManager() {
1710
2091
  const userAgent = process.env.npm_config_user_agent || "";
@@ -1712,9 +2093,9 @@ function detectPackageManager() {
1712
2093
  if (userAgent.includes("yarn")) return "yarn";
1713
2094
  if (userAgent.includes("bun")) return "bun";
1714
2095
  const cwd = process.cwd();
1715
- if (fs.existsSync(path9.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
1716
- if (fs.existsSync(path9.join(cwd, "yarn.lock"))) return "yarn";
1717
- if (fs.existsSync(path9.join(cwd, "bun.lockb"))) return "bun";
2096
+ if (fs8.existsSync(path13.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
2097
+ if (fs8.existsSync(path13.join(cwd, "yarn.lock"))) return "yarn";
2098
+ if (fs8.existsSync(path13.join(cwd, "bun.lockb"))) return "bun";
1718
2099
  return "npm";
1719
2100
  }
1720
2101
  async function installDependencies(projectPath, packageManager = "npm") {
@@ -1763,22 +2144,23 @@ function getValidCategories() {
1763
2144
  "contact"
1764
2145
  ];
1765
2146
  }
1766
- var AUTH_DIR = path9.join(os.homedir(), ".onexthm");
2147
+ var AUTH_DIR = path13.join(os.homedir(), ".onexthm");
1767
2148
  var ENV_URLS = {
1768
2149
  dev: "https://platform-dev.onexeos.com",
1769
- prod: "https://platform-staging.onexeos.com"
2150
+ staging: "https://platform-staging.onexeos.com",
2151
+ prod: "https://platform-apis.onexeos.com"
1770
2152
  };
1771
2153
  function getAuthFile(env = "dev") {
1772
- const newFile = path9.join(AUTH_DIR, `auth-${env}.json`);
2154
+ const newFile = path13.join(AUTH_DIR, `auth-${env}.json`);
1773
2155
  if (env === "dev") {
1774
- const legacyFile = path9.join(AUTH_DIR, "auth.json");
1775
- if (fs.existsSync(legacyFile) && !fs.existsSync(newFile)) {
2156
+ const legacyFile = path13.join(AUTH_DIR, "auth.json");
2157
+ if (fs8.existsSync(legacyFile) && !fs8.existsSync(newFile)) {
1776
2158
  try {
1777
- fs.moveSync(legacyFile, newFile);
2159
+ fs8.moveSync(legacyFile, newFile);
1778
2160
  } catch {
1779
2161
  try {
1780
- fs.copySync(legacyFile, newFile);
1781
- fs.removeSync(legacyFile);
2162
+ fs8.copySync(legacyFile, newFile);
2163
+ fs8.removeSync(legacyFile);
1782
2164
  } catch {
1783
2165
  }
1784
2166
  }
@@ -1790,17 +2172,17 @@ function getApiUrl(env = "dev") {
1790
2172
  return process.env.ONEXTHM_API_URL || ENV_URLS[env];
1791
2173
  }
1792
2174
  async function saveAuthTokens(tokens, env = "dev") {
1793
- await fs.ensureDir(AUTH_DIR);
2175
+ await fs8.ensureDir(AUTH_DIR);
1794
2176
  const key = getMachineKey();
1795
2177
  const data = JSON.stringify(tokens);
1796
2178
  const encrypted = encrypt(data, key);
1797
- await fs.writeFile(getAuthFile(env), encrypted, "utf-8");
2179
+ await fs8.writeFile(getAuthFile(env), encrypted, "utf-8");
1798
2180
  }
1799
2181
  function loadAuthTokens(env = "dev") {
1800
2182
  try {
1801
2183
  const file = getAuthFile(env);
1802
- if (!fs.existsSync(file)) return null;
1803
- const encrypted = fs.readFileSync(file, "utf-8");
2184
+ if (!fs8.existsSync(file)) return null;
2185
+ const encrypted = fs8.readFileSync(file, "utf-8");
1804
2186
  const key = getMachineKey();
1805
2187
  const data = decrypt(encrypted, key);
1806
2188
  return JSON.parse(data);
@@ -1810,7 +2192,7 @@ function loadAuthTokens(env = "dev") {
1810
2192
  }
1811
2193
  async function clearAuthTokens(env = "dev") {
1812
2194
  try {
1813
- await fs.remove(getAuthFile(env));
2195
+ await fs8.remove(getAuthFile(env));
1814
2196
  } catch {
1815
2197
  }
1816
2198
  }
@@ -1869,7 +2251,7 @@ function getMachineKey() {
1869
2251
  seed = `onexthm:${os.hostname()}:${os.userInfo().username}`;
1870
2252
  } else if (process.platform === "linux") {
1871
2253
  try {
1872
- seed = `onexthm:${fs.readFileSync("/etc/machine-id", "utf-8").trim()}`;
2254
+ seed = `onexthm:${fs8.readFileSync("/etc/machine-id", "utf-8").trim()}`;
1873
2255
  } catch {
1874
2256
  seed = `onexthm:${os.hostname()}:${os.userInfo().username}`;
1875
2257
  }
@@ -1907,7 +2289,7 @@ function parseJwtClaims(idToken) {
1907
2289
  }
1908
2290
 
1909
2291
  // src/commands/init.ts
1910
- async function initCommand(projectName, options = {}) {
2292
+ async function initCommand(projectName, options) {
1911
2293
  logger.header("Create New OneX Theme Project");
1912
2294
  let name;
1913
2295
  if (!projectName) {
@@ -1922,7 +2304,7 @@ async function initCommand(projectName, options = {}) {
1922
2304
  if (!validateThemeName(kebabName)) {
1923
2305
  return "Invalid project name. Use lowercase letters, numbers, and hyphens only.";
1924
2306
  }
1925
- if (fs3.existsSync(path9.join(process.cwd(), kebabName))) {
2307
+ if (fs3.existsSync(path13.join(process.cwd(), kebabName))) {
1926
2308
  return `Directory "${kebabName}" already exists`;
1927
2309
  }
1928
2310
  return true;
@@ -1933,14 +2315,14 @@ async function initCommand(projectName, options = {}) {
1933
2315
  } else {
1934
2316
  name = toKebabCase(projectName);
1935
2317
  }
1936
- const projectPath = path9.join(process.cwd(), name);
2318
+ const projectPath = path13.join(process.cwd(), name);
1937
2319
  if (fs3.existsSync(projectPath)) {
1938
2320
  logger.error(`Directory "${name}" already exists.`);
1939
2321
  process.exit(1);
1940
2322
  }
1941
2323
  if (!options.yes) {
1942
2324
  try {
1943
- const apiUrl = getApiUrl(options.env ?? "dev");
2325
+ const apiUrl = getApiUrl(options.env);
1944
2326
  const controller = new AbortController();
1945
2327
  const timeout = setTimeout(() => controller.abort(), 3e3);
1946
2328
  const response = await fetch(
@@ -2049,7 +2431,7 @@ async function initCommand(projectName, options = {}) {
2049
2431
  description,
2050
2432
  author
2051
2433
  );
2052
- const mcpJsonPath = path9.join(projectPath, ".mcp.json");
2434
+ const mcpJsonPath = path13.join(projectPath, ".mcp.json");
2053
2435
  if (fs3.existsSync(mcpJsonPath)) {
2054
2436
  let mcpContent = fs3.readFileSync(mcpJsonPath, "utf-8");
2055
2437
  if (figmaApiKey) {
@@ -2129,7 +2511,7 @@ async function initCommand(projectName, options = {}) {
2129
2511
  }
2130
2512
  }
2131
2513
  async function renameThemeInFiles(projectPath, themeName, displayName, description, author) {
2132
- const configPath = path9.join(projectPath, "theme.config.ts");
2514
+ const configPath = path13.join(projectPath, "theme.config.ts");
2133
2515
  if (fs3.existsSync(configPath)) {
2134
2516
  let content = fs3.readFileSync(configPath, "utf-8");
2135
2517
  content = content.replace(
@@ -2142,7 +2524,7 @@ async function renameThemeInFiles(projectPath, themeName, displayName, descripti
2142
2524
  );
2143
2525
  fs3.writeFileSync(configPath, content, "utf-8");
2144
2526
  }
2145
- const pkgPath = path9.join(projectPath, "package.json");
2527
+ const pkgPath = path13.join(projectPath, "package.json");
2146
2528
  if (fs3.existsSync(pkgPath)) {
2147
2529
  let content = fs3.readFileSync(pkgPath, "utf-8");
2148
2530
  content = content.replace(
@@ -2164,10 +2546,10 @@ async function createSectionCommand(name, options) {
2164
2546
  ensureOneXProject();
2165
2547
  if (!options.theme) {
2166
2548
  const isStandaloneTheme = ["theme.config.ts", "bundle-entry.ts"].some(
2167
- (f) => fs.existsSync(path9.join(process.cwd(), f))
2549
+ (f) => fs8.existsSync(path13.join(process.cwd(), f))
2168
2550
  );
2169
2551
  if (isStandaloneTheme) {
2170
- options.theme = path9.basename(process.cwd());
2552
+ options.theme = path13.basename(process.cwd());
2171
2553
  }
2172
2554
  }
2173
2555
  const sectionName = toKebabCase(name);
@@ -2230,35 +2612,35 @@ async function createSectionCommand(name, options) {
2230
2612
  };
2231
2613
  logger.startSpinner("Creating section files...");
2232
2614
  try {
2233
- const themePath = path9.join(getThemesDir(), themeName);
2234
- const sectionPath = path9.join(themePath, "sections", sectionName);
2615
+ const themePath = path13.join(getThemesDir(), themeName);
2616
+ const sectionPath = path13.join(themePath, "sections", sectionName);
2235
2617
  const schemaContent = generateSectionSchema(data);
2236
2618
  await writeFile(
2237
- path9.join(sectionPath, `${sectionName}.schema.ts`),
2619
+ path13.join(sectionPath, `${sectionName}.schema.ts`),
2238
2620
  schemaContent
2239
2621
  );
2240
2622
  if (createTemplate) {
2241
2623
  const templateContent = generateSectionTemplate(data);
2242
2624
  await writeFile(
2243
- path9.join(sectionPath, `${sectionName}-default.tsx`),
2625
+ path13.join(sectionPath, `${sectionName}-default.tsx`),
2244
2626
  templateContent
2245
2627
  );
2246
2628
  }
2247
2629
  const indexContent = generateSectionIndex(data, createTemplate);
2248
- await writeFile(path9.join(sectionPath, "index.ts"), indexContent);
2630
+ await writeFile(path13.join(sectionPath, "index.ts"), indexContent);
2249
2631
  logger.stopSpinner(true, "Section files created successfully!");
2250
2632
  logger.newLine();
2251
2633
  logger.section("Next steps:");
2252
2634
  logger.log(
2253
- ` 1. Edit schema: ${path9.relative(process.cwd(), path9.join(sectionPath, `${sectionName}.schema.ts`))}`
2635
+ ` 1. Edit schema: ${path13.relative(process.cwd(), path13.join(sectionPath, `${sectionName}.schema.ts`))}`
2254
2636
  );
2255
2637
  if (createTemplate) {
2256
2638
  logger.log(
2257
- ` 2. Edit template: ${path9.relative(process.cwd(), path9.join(sectionPath, `${sectionName}-default.tsx`))}`
2639
+ ` 2. Edit template: ${path13.relative(process.cwd(), path13.join(sectionPath, `${sectionName}-default.tsx`))}`
2258
2640
  );
2259
2641
  }
2260
2642
  logger.log(
2261
- ` 3. Add to theme manifest: ${path9.relative(process.cwd(), path9.join(themePath, "manifest.ts"))}`
2643
+ ` 3. Add to theme manifest: ${path13.relative(process.cwd(), path13.join(themePath, "manifest.ts"))}`
2262
2644
  );
2263
2645
  logger.newLine();
2264
2646
  logger.success("Section created successfully!");
@@ -2406,10 +2788,10 @@ async function createBlockCommand(name, options) {
2406
2788
  ensureOneXProject();
2407
2789
  if (!options.theme) {
2408
2790
  const isStandaloneTheme = ["theme.config.ts", "bundle-entry.ts"].some(
2409
- (f) => fs.existsSync(path9.join(process.cwd(), f))
2791
+ (f) => fs8.existsSync(path13.join(process.cwd(), f))
2410
2792
  );
2411
2793
  if (isStandaloneTheme) {
2412
- options.theme = path9.basename(process.cwd());
2794
+ options.theme = path13.basename(process.cwd());
2413
2795
  }
2414
2796
  }
2415
2797
  const blockName = toKebabCase(name);
@@ -2484,24 +2866,24 @@ async function createBlockCommand(name, options) {
2484
2866
  };
2485
2867
  logger.startSpinner("Creating block files...");
2486
2868
  try {
2487
- const blockPath = scope === "shared" ? path9.join(getFeaturesDir(), "blocks", blockName) : path9.join(getThemesDir(), themeName, "blocks", blockName);
2869
+ const blockPath = scope === "shared" ? path13.join(getFeaturesDir(), "blocks", blockName) : path13.join(getThemesDir(), themeName, "blocks", blockName);
2488
2870
  const schemaContent = generateBlockSchema(data);
2489
2871
  await writeFile(
2490
- path9.join(blockPath, `${blockName}.schema.ts`),
2872
+ path13.join(blockPath, `${blockName}.schema.ts`),
2491
2873
  schemaContent
2492
2874
  );
2493
2875
  const componentContent = generateBlockComponent(data);
2494
- await writeFile(path9.join(blockPath, `${blockName}.tsx`), componentContent);
2876
+ await writeFile(path13.join(blockPath, `${blockName}.tsx`), componentContent);
2495
2877
  const indexContent = generateBlockIndex(data);
2496
- await writeFile(path9.join(blockPath, "index.ts"), indexContent);
2878
+ await writeFile(path13.join(blockPath, "index.ts"), indexContent);
2497
2879
  logger.stopSpinner(true, "Block files created successfully!");
2498
2880
  logger.newLine();
2499
2881
  logger.section("Next steps:");
2500
2882
  logger.log(
2501
- ` 1. Edit schema: ${path9.relative(process.cwd(), path9.join(blockPath, `${blockName}.schema.ts`))}`
2883
+ ` 1. Edit schema: ${path13.relative(process.cwd(), path13.join(blockPath, `${blockName}.schema.ts`))}`
2502
2884
  );
2503
2885
  logger.log(
2504
- ` 2. Edit component: ${path9.relative(process.cwd(), path9.join(blockPath, `${blockName}.tsx`))}`
2886
+ ` 2. Edit component: ${path13.relative(process.cwd(), path13.join(blockPath, `${blockName}.tsx`))}`
2505
2887
  );
2506
2888
  logger.log(
2507
2889
  ` 3. Register in block registry: src/lib/registry/block-registry.ts`
@@ -2679,31 +3061,31 @@ async function createComponentCommand(name, options) {
2679
3061
  };
2680
3062
  logger.startSpinner("Creating component files...");
2681
3063
  try {
2682
- const componentPath = path9.join(
3064
+ const componentPath = path13.join(
2683
3065
  getFeaturesDir(),
2684
3066
  "components",
2685
3067
  componentName
2686
3068
  );
2687
3069
  const schemaContent = generateComponentSchema(data);
2688
3070
  await writeFile(
2689
- path9.join(componentPath, `${componentName}.schema.ts`),
3071
+ path13.join(componentPath, `${componentName}.schema.ts`),
2690
3072
  schemaContent
2691
3073
  );
2692
3074
  const componentContent = generateComponent(data);
2693
3075
  await writeFile(
2694
- path9.join(componentPath, `${componentName}.tsx`),
3076
+ path13.join(componentPath, `${componentName}.tsx`),
2695
3077
  componentContent
2696
3078
  );
2697
3079
  const indexContent = generateComponentIndex(data);
2698
- await writeFile(path9.join(componentPath, "index.ts"), indexContent);
3080
+ await writeFile(path13.join(componentPath, "index.ts"), indexContent);
2699
3081
  logger.stopSpinner(true, "Component files created successfully!");
2700
3082
  logger.newLine();
2701
3083
  logger.section("Next steps:");
2702
3084
  logger.log(
2703
- ` 1. Edit schema: ${path9.relative(process.cwd(), path9.join(componentPath, `${componentName}.schema.ts`))}`
3085
+ ` 1. Edit schema: ${path13.relative(process.cwd(), path13.join(componentPath, `${componentName}.schema.ts`))}`
2704
3086
  );
2705
3087
  logger.log(
2706
- ` 2. Edit component: ${path9.relative(process.cwd(), path9.join(componentPath, `${componentName}.tsx`))}`
3088
+ ` 2. Edit component: ${path13.relative(process.cwd(), path13.join(componentPath, `${componentName}.tsx`))}`
2707
3089
  );
2708
3090
  logger.log(
2709
3091
  ` 3. Register in component registry: src/lib/registry/component-registry.ts`
@@ -2860,13 +3242,13 @@ async function listSections(themeFilter) {
2860
3242
  return;
2861
3243
  }
2862
3244
  for (const theme of themes) {
2863
- const sectionsDir = path9.join(getThemesDir(), theme, "sections");
2864
- if (!fs.existsSync(sectionsDir)) {
3245
+ const sectionsDir = path13.join(getThemesDir(), theme, "sections");
3246
+ if (!fs8.existsSync(sectionsDir)) {
2865
3247
  continue;
2866
3248
  }
2867
- const sections = fs.readdirSync(sectionsDir).filter((name) => {
2868
- const sectionPath = path9.join(sectionsDir, name);
2869
- return fs.statSync(sectionPath).isDirectory() && fs.existsSync(path9.join(sectionPath, "index.ts"));
3249
+ const sections = fs8.readdirSync(sectionsDir).filter((name) => {
3250
+ const sectionPath = path13.join(sectionsDir, name);
3251
+ return fs8.statSync(sectionPath).isDirectory() && fs8.existsSync(path13.join(sectionPath, "index.ts"));
2870
3252
  });
2871
3253
  if (sections.length > 0) {
2872
3254
  logger.log(chalk4.cyan(`
@@ -2880,11 +3262,11 @@ async function listSections(themeFilter) {
2880
3262
  }
2881
3263
  async function listBlocks(themeFilter) {
2882
3264
  logger.section("\u{1F9F1} Blocks");
2883
- const sharedBlocksDir = path9.join(getFeaturesDir(), "blocks");
2884
- if (fs.existsSync(sharedBlocksDir)) {
2885
- const sharedBlocks = fs.readdirSync(sharedBlocksDir).filter((name) => {
2886
- const blockPath = path9.join(sharedBlocksDir, name);
2887
- return fs.statSync(blockPath).isDirectory() && fs.existsSync(path9.join(blockPath, "index.ts"));
3265
+ const sharedBlocksDir = path13.join(getFeaturesDir(), "blocks");
3266
+ if (fs8.existsSync(sharedBlocksDir)) {
3267
+ const sharedBlocks = fs8.readdirSync(sharedBlocksDir).filter((name) => {
3268
+ const blockPath = path13.join(sharedBlocksDir, name);
3269
+ return fs8.statSync(blockPath).isDirectory() && fs8.existsSync(path13.join(blockPath, "index.ts"));
2888
3270
  });
2889
3271
  if (sharedBlocks.length > 0) {
2890
3272
  logger.log(chalk4.cyan("\n Shared:"));
@@ -2895,13 +3277,13 @@ async function listBlocks(themeFilter) {
2895
3277
  }
2896
3278
  const themes = themeFilter ? [themeFilter] : listThemes();
2897
3279
  for (const theme of themes) {
2898
- const blocksDir = path9.join(getThemesDir(), theme, "blocks");
2899
- if (!fs.existsSync(blocksDir)) {
3280
+ const blocksDir = path13.join(getThemesDir(), theme, "blocks");
3281
+ if (!fs8.existsSync(blocksDir)) {
2900
3282
  continue;
2901
3283
  }
2902
- const blocks = fs.readdirSync(blocksDir).filter((name) => {
2903
- const blockPath = path9.join(blocksDir, name);
2904
- return fs.statSync(blockPath).isDirectory() && fs.existsSync(path9.join(blockPath, "index.ts"));
3284
+ const blocks = fs8.readdirSync(blocksDir).filter((name) => {
3285
+ const blockPath = path13.join(blocksDir, name);
3286
+ return fs8.statSync(blockPath).isDirectory() && fs8.existsSync(path13.join(blockPath, "index.ts"));
2905
3287
  });
2906
3288
  if (blocks.length > 0) {
2907
3289
  logger.log(chalk4.cyan(`
@@ -2915,14 +3297,14 @@ async function listBlocks(themeFilter) {
2915
3297
  }
2916
3298
  async function listComponents() {
2917
3299
  logger.section("\u2699\uFE0F Components");
2918
- const componentsDir = path9.join(getFeaturesDir(), "components");
2919
- if (!fs.existsSync(componentsDir)) {
3300
+ const componentsDir = path13.join(getFeaturesDir(), "components");
3301
+ if (!fs8.existsSync(componentsDir)) {
2920
3302
  logger.warning("No components directory found");
2921
3303
  return;
2922
3304
  }
2923
- const components = fs.readdirSync(componentsDir).filter((name) => {
2924
- const componentPath = path9.join(componentsDir, name);
2925
- return fs.statSync(componentPath).isDirectory() && fs.existsSync(path9.join(componentPath, "index.ts"));
3305
+ const components = fs8.readdirSync(componentsDir).filter((name) => {
3306
+ const componentPath = path13.join(componentsDir, name);
3307
+ return fs8.statSync(componentPath).isDirectory() && fs8.existsSync(path13.join(componentPath, "index.ts"));
2926
3308
  });
2927
3309
  if (components.length === 0) {
2928
3310
  logger.warning("No components found");
@@ -2943,13 +3325,13 @@ async function listThemesInfo() {
2943
3325
  }
2944
3326
  logger.log("");
2945
3327
  for (const theme of themes) {
2946
- const themeDir = path9.join(getThemesDir(), theme);
3328
+ const themeDir = path13.join(getThemesDir(), theme);
2947
3329
  const candidates = ["theme.config.ts", "bundle-entry.ts", "manifest.ts"];
2948
3330
  let manifestContent = "";
2949
3331
  for (const candidate of candidates) {
2950
- const candidatePath = path9.join(themeDir, candidate);
2951
- if (fs.existsSync(candidatePath)) {
2952
- manifestContent = fs.readFileSync(candidatePath, "utf-8");
3332
+ const candidatePath = path13.join(themeDir, candidate);
3333
+ if (fs8.existsSync(candidatePath)) {
3334
+ manifestContent = fs8.readFileSync(candidatePath, "utf-8");
2953
3335
  break;
2954
3336
  }
2955
3337
  }
@@ -2969,6 +3351,7 @@ async function listThemesInfo() {
2969
3351
 
2970
3352
  // src/commands/validate.ts
2971
3353
  init_logger();
3354
+ init_detect_nextjs();
2972
3355
  async function validateCommand(options) {
2973
3356
  logger.header("Validate Theme");
2974
3357
  ensureOneXProject();
@@ -2984,10 +3367,13 @@ async function validateCommand(options) {
2984
3367
  const isThemeDir2 = [
2985
3368
  "theme.config.ts",
2986
3369
  "bundle-entry.ts",
2987
- "manifest.ts"
2988
- ].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
3370
+ "manifest.ts",
3371
+ "next.config.ts",
3372
+ "next.config.js",
3373
+ "next.config.mjs"
3374
+ ].some((f) => fs8.existsSync(path13.join(process.cwd(), f)));
2989
3375
  if (isThemeDir2) {
2990
- themeToValidate = path9.basename(process.cwd());
3376
+ themeToValidate = path13.basename(process.cwd());
2991
3377
  logger.info(`Validating current theme: ${themeToValidate}`);
2992
3378
  } else {
2993
3379
  logger.error(
@@ -2996,11 +3382,11 @@ async function validateCommand(options) {
2996
3382
  process.exit(1);
2997
3383
  }
2998
3384
  }
2999
- const themePath = path9.join(getThemesDir(), themeToValidate);
3385
+ const themePath = path13.join(getThemesDir(), themeToValidate);
3000
3386
  logger.startSpinner("Running validation checks...");
3001
3387
  const entryFiles = ["manifest.ts", "theme.config.ts", "bundle-entry.ts"];
3002
3388
  const foundEntry = entryFiles.find(
3003
- (f) => fs.existsSync(path9.join(themePath, f))
3389
+ (f) => fs8.existsSync(path13.join(themePath, f))
3004
3390
  );
3005
3391
  if (!foundEntry) {
3006
3392
  issues.push({
@@ -3009,8 +3395,8 @@ async function validateCommand(options) {
3009
3395
  message: "No theme entry file found (need at least one of: manifest.ts, theme.config.ts, bundle-entry.ts)"
3010
3396
  });
3011
3397
  } else if (foundEntry === "manifest.ts") {
3012
- const manifestContent = fs.readFileSync(
3013
- path9.join(themePath, foundEntry),
3398
+ const manifestContent = fs8.readFileSync(
3399
+ path13.join(themePath, foundEntry),
3014
3400
  "utf-8"
3015
3401
  );
3016
3402
  if (!manifestContent.includes("export const") && !manifestContent.includes("export default") && !manifestContent.includes("export interface")) {
@@ -3021,56 +3407,56 @@ async function validateCommand(options) {
3021
3407
  });
3022
3408
  }
3023
3409
  }
3024
- const configPath = path9.join(themePath, "theme.config.ts");
3025
- if (!fs.existsSync(configPath)) {
3410
+ const configPath = path13.join(themePath, "theme.config.ts");
3411
+ if (!fs8.existsSync(configPath)) {
3026
3412
  issues.push({
3027
3413
  type: "warning",
3028
3414
  file: "theme.config.ts",
3029
3415
  message: "Theme config file not found (recommended)"
3030
3416
  });
3031
3417
  }
3032
- const indexPath = path9.join(themePath, "index.ts");
3033
- if (!fs.existsSync(indexPath)) {
3418
+ const indexPath = path13.join(themePath, "index.ts");
3419
+ if (!fs8.existsSync(indexPath)) {
3034
3420
  issues.push({
3035
3421
  type: "warning",
3036
3422
  file: "index.ts",
3037
3423
  message: "Index file not found (recommended)"
3038
3424
  });
3039
3425
  }
3040
- const sectionsDir = path9.join(themePath, "sections");
3041
- if (!fs.existsSync(sectionsDir)) {
3426
+ const sectionsDir = path13.join(themePath, "sections");
3427
+ if (!fs8.existsSync(sectionsDir)) {
3042
3428
  issues.push({
3043
3429
  type: "warning",
3044
3430
  file: "sections/",
3045
3431
  message: "Sections directory not found"
3046
3432
  });
3047
3433
  } else {
3048
- const sections = fs.readdirSync(sectionsDir).filter(
3049
- (name) => fs.statSync(path9.join(sectionsDir, name)).isDirectory()
3434
+ const sections = fs8.readdirSync(sectionsDir).filter(
3435
+ (name) => fs8.statSync(path13.join(sectionsDir, name)).isDirectory()
3050
3436
  );
3051
3437
  for (const sectionName of sections) {
3052
- const sectionPath = path9.join(sectionsDir, sectionName);
3053
- const schemaFile = path9.join(sectionPath, `${sectionName}.schema.ts`);
3054
- const defaultTemplate = path9.join(
3438
+ const sectionPath = path13.join(sectionsDir, sectionName);
3439
+ const schemaFile = path13.join(sectionPath, `${sectionName}.schema.ts`);
3440
+ const defaultTemplate = path13.join(
3055
3441
  sectionPath,
3056
3442
  `${sectionName}-default.tsx`
3057
3443
  );
3058
- const indexFile = path9.join(sectionPath, "index.ts");
3059
- if (!fs.existsSync(schemaFile)) {
3444
+ const indexFile = path13.join(sectionPath, "index.ts");
3445
+ if (!fs8.existsSync(schemaFile)) {
3060
3446
  issues.push({
3061
3447
  type: "error",
3062
3448
  file: `sections/${sectionName}/${sectionName}.schema.ts`,
3063
3449
  message: "Section schema file missing"
3064
3450
  });
3065
3451
  }
3066
- if (!fs.existsSync(indexFile)) {
3452
+ if (!fs8.existsSync(indexFile)) {
3067
3453
  issues.push({
3068
3454
  type: "error",
3069
3455
  file: `sections/${sectionName}/index.ts`,
3070
3456
  message: "Section index file missing"
3071
3457
  });
3072
3458
  }
3073
- if (!fs.existsSync(defaultTemplate)) {
3459
+ if (!fs8.existsSync(defaultTemplate)) {
3074
3460
  issues.push({
3075
3461
  type: "warning",
3076
3462
  file: `sections/${sectionName}/${sectionName}-default.tsx`,
@@ -3079,29 +3465,29 @@ async function validateCommand(options) {
3079
3465
  }
3080
3466
  }
3081
3467
  }
3082
- const blocksDir = path9.join(themePath, "blocks");
3083
- if (fs.existsSync(blocksDir)) {
3084
- const blocks = fs.readdirSync(blocksDir).filter((name) => fs.statSync(path9.join(blocksDir, name)).isDirectory());
3468
+ const blocksDir = path13.join(themePath, "blocks");
3469
+ if (fs8.existsSync(blocksDir)) {
3470
+ const blocks = fs8.readdirSync(blocksDir).filter((name) => fs8.statSync(path13.join(blocksDir, name)).isDirectory());
3085
3471
  for (const blockName of blocks) {
3086
- const blockPath = path9.join(blocksDir, blockName);
3087
- const schemaFile = path9.join(blockPath, `${blockName}.schema.ts`);
3088
- const componentFile = path9.join(blockPath, `${blockName}.tsx`);
3089
- const indexFile = path9.join(blockPath, "index.ts");
3090
- if (!fs.existsSync(schemaFile)) {
3472
+ const blockPath = path13.join(blocksDir, blockName);
3473
+ const schemaFile = path13.join(blockPath, `${blockName}.schema.ts`);
3474
+ const componentFile = path13.join(blockPath, `${blockName}.tsx`);
3475
+ const indexFile = path13.join(blockPath, "index.ts");
3476
+ if (!fs8.existsSync(schemaFile)) {
3091
3477
  issues.push({
3092
3478
  type: "error",
3093
3479
  file: `blocks/${blockName}/${blockName}.schema.ts`,
3094
3480
  message: "Block schema file missing"
3095
3481
  });
3096
3482
  }
3097
- if (!fs.existsSync(componentFile)) {
3483
+ if (!fs8.existsSync(componentFile)) {
3098
3484
  issues.push({
3099
3485
  type: "error",
3100
3486
  file: `blocks/${blockName}/${blockName}.tsx`,
3101
3487
  message: "Block component file missing"
3102
3488
  });
3103
3489
  }
3104
- if (!fs.existsSync(indexFile)) {
3490
+ if (!fs8.existsSync(indexFile)) {
3105
3491
  issues.push({
3106
3492
  type: "error",
3107
3493
  file: `blocks/${blockName}/index.ts`,
@@ -3110,16 +3496,16 @@ async function validateCommand(options) {
3110
3496
  }
3111
3497
  }
3112
3498
  }
3113
- if (fs.existsSync(sectionsDir)) {
3114
- const sections = fs.readdirSync(sectionsDir).filter(
3115
- (name) => fs.statSync(path9.join(sectionsDir, name)).isDirectory()
3499
+ if (fs8.existsSync(sectionsDir)) {
3500
+ const sections = fs8.readdirSync(sectionsDir).filter(
3501
+ (name) => fs8.statSync(path13.join(sectionsDir, name)).isDirectory()
3116
3502
  );
3117
3503
  for (const sectionName of sections) {
3118
- const sectionPath = path9.join(sectionsDir, sectionName);
3119
- const tsxFiles = fs.readdirSync(sectionPath).filter((f) => f.endsWith(".tsx") && !f.endsWith(".schema.ts"));
3504
+ const sectionPath = path13.join(sectionsDir, sectionName);
3505
+ const tsxFiles = fs8.readdirSync(sectionPath).filter((f) => f.endsWith(".tsx") && !f.endsWith(".schema.ts"));
3120
3506
  for (const tsxFile of tsxFiles) {
3121
- const filePath = path9.join(sectionPath, tsxFile);
3122
- const content = fs.readFileSync(filePath, "utf-8");
3507
+ const filePath = path13.join(sectionPath, tsxFile);
3508
+ const content = fs8.readFileSync(filePath, "utf-8");
3123
3509
  const relPath = `sections/${sectionName}/${tsxFile}`;
3124
3510
  if (!content.includes('"use client"') && !content.includes("'use client'")) {
3125
3511
  issues.push({
@@ -3166,12 +3552,12 @@ async function validateCommand(options) {
3166
3552
  }
3167
3553
  }
3168
3554
  }
3169
- const registryPath = path9.join(themePath, "sections-registry.ts");
3170
- const bundleEntryPath = path9.join(themePath, "bundle-entry.ts");
3171
- const registryContent = fs.existsSync(registryPath) ? fs.readFileSync(registryPath, "utf-8") : fs.existsSync(bundleEntryPath) ? fs.readFileSync(bundleEntryPath, "utf-8") : "";
3172
- if (fs.existsSync(sectionsDir) && registryContent) {
3173
- const sections = fs.readdirSync(sectionsDir).filter(
3174
- (name) => fs.statSync(path9.join(sectionsDir, name)).isDirectory()
3555
+ const registryPath = path13.join(themePath, "sections-registry.ts");
3556
+ const bundleEntryPath = path13.join(themePath, "bundle-entry.ts");
3557
+ const registryContent = fs8.existsSync(registryPath) ? fs8.readFileSync(registryPath, "utf-8") : fs8.existsSync(bundleEntryPath) ? fs8.readFileSync(bundleEntryPath, "utf-8") : "";
3558
+ if (fs8.existsSync(sectionsDir) && registryContent) {
3559
+ const sections = fs8.readdirSync(sectionsDir).filter(
3560
+ (name) => fs8.statSync(path13.join(sectionsDir, name)).isDirectory()
3175
3561
  );
3176
3562
  for (const sectionName of sections) {
3177
3563
  if (!registryContent.includes(`sections/${sectionName}`) && !registryContent.includes(`"${sectionName}"`)) {
@@ -3183,7 +3569,7 @@ async function validateCommand(options) {
3183
3569
  }
3184
3570
  }
3185
3571
  }
3186
- if (fs.existsSync(sectionsDir)) {
3572
+ if (fs8.existsSync(sectionsDir)) {
3187
3573
  const schemaTypes = await loadSchemaTypes(themePath, sectionsDir);
3188
3574
  for (const { folderName, schemaType } of schemaTypes) {
3189
3575
  if (schemaType && schemaType !== folderName) {
@@ -3194,8 +3580,8 @@ async function validateCommand(options) {
3194
3580
  });
3195
3581
  }
3196
3582
  }
3197
- const pagesDir = path9.join(themePath, "pages");
3198
- if (fs.existsSync(pagesDir)) {
3583
+ const pagesDir = path13.join(themePath, "pages");
3584
+ if (fs8.existsSync(pagesDir)) {
3199
3585
  const allSchemaTypeSet = new Set(
3200
3586
  schemaTypes.map((s) => s.schemaType || s.folderName)
3201
3587
  );
@@ -3206,6 +3592,10 @@ async function validateCommand(options) {
3206
3592
  issues.push(...pageIssues);
3207
3593
  }
3208
3594
  }
3595
+ if (isNextjsProject(themePath)) {
3596
+ const nextjsIssues = await validateNextjsComponents(themePath);
3597
+ issues.push(...nextjsIssues);
3598
+ }
3209
3599
  logger.stopSpinner(true, "Validation complete");
3210
3600
  const errors = issues.filter((i) => i.type === "error");
3211
3601
  const warnings = issues.filter((i) => i.type === "warning");
@@ -3246,18 +3636,18 @@ async function validateCommand(options) {
3246
3636
  }
3247
3637
  async function loadSchemaTypes(themePath, sectionsDir) {
3248
3638
  const results = [];
3249
- const sections = fs.readdirSync(sectionsDir).filter((name) => fs.statSync(path9.join(sectionsDir, name)).isDirectory());
3639
+ const sections = fs8.readdirSync(sectionsDir).filter((name) => fs8.statSync(path13.join(sectionsDir, name)).isDirectory());
3250
3640
  for (const sectionName of sections) {
3251
- const schemaFile = path9.join(
3641
+ const schemaFile = path13.join(
3252
3642
  sectionsDir,
3253
3643
  sectionName,
3254
3644
  `${sectionName}.schema.ts`
3255
3645
  );
3256
- if (!fs.existsSync(schemaFile)) {
3646
+ if (!fs8.existsSync(schemaFile)) {
3257
3647
  results.push({ folderName: sectionName, schemaType: null });
3258
3648
  continue;
3259
3649
  }
3260
- const content = fs.readFileSync(schemaFile, "utf-8");
3650
+ const content = fs8.readFileSync(schemaFile, "utf-8");
3261
3651
  let schemaType = null;
3262
3652
  const schemaExportMatch = content.match(
3263
3653
  /:\s*SectionSchema\s*=\s*\{[\s\S]*?\btype:\s*["']([^"']+)["']/
@@ -3284,9 +3674,9 @@ async function loadSchemaTypes(themePath, sectionsDir) {
3284
3674
  }
3285
3675
  async function validatePageSectionTypes(pagesDir, validTypes) {
3286
3676
  const issues = [];
3287
- const files = fs.readdirSync(pagesDir).filter((f) => f.match(/\.(ts|js)$/));
3677
+ const files = fs8.readdirSync(pagesDir).filter((f) => f.match(/\.(ts|js)$/));
3288
3678
  for (const file of files) {
3289
- const content = fs.readFileSync(path9.join(pagesDir, file), "utf-8");
3679
+ const content = fs8.readFileSync(path13.join(pagesDir, file), "utf-8");
3290
3680
  const pageName = file.replace(/\.(ts|js)$/, "");
3291
3681
  const sectionsMatch = content.match(/\bsections:\s*\[/);
3292
3682
  if (!sectionsMatch || sectionsMatch.index === void 0) continue;
@@ -3299,9 +3689,13 @@ async function validatePageSectionTypes(pagesDir, validTypes) {
3299
3689
  endIdx = i;
3300
3690
  }
3301
3691
  const sectionsBlock = content.slice(startIdx, endIdx);
3302
- const typeMatches = sectionsBlock.matchAll(/\btype:\s*["']([^"']+)["']/g);
3303
- for (const match of typeMatches) {
3692
+ const sectionTypeMatches = sectionsBlock.matchAll(
3693
+ /\bid:\s*["'][^"']*["'],\s*\n?\s*type:\s*["']([^"']+)["']/g
3694
+ );
3695
+ for (const match of sectionTypeMatches) {
3304
3696
  const sectionType = match[1];
3697
+ if (COMPONENT_TYPES.has(sectionType)) continue;
3698
+ if (BLOCK_TYPES.has(sectionType)) continue;
3305
3699
  if (!validTypes.has(sectionType)) {
3306
3700
  issues.push({
3307
3701
  type: "error",
@@ -3354,9 +3748,128 @@ var FIELD_TYPES = /* @__PURE__ */ new Set([
3354
3748
  "inline_richtext",
3355
3749
  "repeater"
3356
3750
  ]);
3751
+ var COMPONENT_TYPES = /* @__PURE__ */ new Set([
3752
+ "heading",
3753
+ "paragraph",
3754
+ "button",
3755
+ "image",
3756
+ "link",
3757
+ "icon",
3758
+ "badge",
3759
+ "divider",
3760
+ "spacer",
3761
+ "container",
3762
+ "grid",
3763
+ "columns",
3764
+ "card",
3765
+ "quote",
3766
+ "input",
3767
+ "textarea",
3768
+ "checkbox",
3769
+ "select",
3770
+ "video",
3771
+ "gallery",
3772
+ "alert",
3773
+ "progress",
3774
+ "rating",
3775
+ "timer",
3776
+ "list",
3777
+ "table",
3778
+ "accordion",
3779
+ "tabs",
3780
+ "code",
3781
+ "map",
3782
+ "product-card",
3783
+ "blog-card",
3784
+ "social-links",
3785
+ "hotline-contacts",
3786
+ "company-info",
3787
+ "torn-separator"
3788
+ ]);
3789
+ var BLOCK_TYPES = /* @__PURE__ */ new Set([
3790
+ "brand-feature",
3791
+ "collection-item",
3792
+ "crafting-step",
3793
+ "testimonial-item",
3794
+ "stat-item",
3795
+ "footer-link",
3796
+ "navigation-links-block",
3797
+ "policy-section",
3798
+ "core-value-card",
3799
+ "faq-item",
3800
+ "feature-item",
3801
+ "gallery-item",
3802
+ "logo-item",
3803
+ "pricing-tier",
3804
+ "service-item",
3805
+ "stat-card",
3806
+ "team-member",
3807
+ "hero-content"
3808
+ ]);
3809
+ var SERVER_ONLY_APIS = [
3810
+ "next/headers",
3811
+ "next/server",
3812
+ "next/cache",
3813
+ "cookies()",
3814
+ "headers()",
3815
+ "draftMode()"
3816
+ ];
3817
+ async function validateNextjsComponents(themePath) {
3818
+ const issues = [];
3819
+ const { glob: glob6 } = await import('glob');
3820
+ const componentFiles = await glob6("components/**/*.{tsx,ts}", {
3821
+ cwd: themePath,
3822
+ ignore: ["**/node_modules/**"]
3823
+ });
3824
+ for (const relFile of componentFiles) {
3825
+ const absFile = path13.join(themePath, relFile);
3826
+ const content = fs8.readFileSync(absFile, "utf-8");
3827
+ const dir = path13.dirname(absFile);
3828
+ const hasSectionJson = fs8.existsSync(path13.join(dir, "section.json"));
3829
+ if (!hasSectionJson) continue;
3830
+ for (const api of SERVER_ONLY_APIS) {
3831
+ if (content.includes(`"${api}"`) || content.includes(`'${api}'`)) {
3832
+ issues.push({
3833
+ type: "error",
3834
+ file: relFile,
3835
+ 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.`
3836
+ });
3837
+ }
3838
+ if (content.includes(api.replace("()", "") + "(")) {
3839
+ issues.push({
3840
+ type: "error",
3841
+ file: relFile,
3842
+ message: `${api} is server-only and cannot be called in a browser bundle. Use useQuery or fetch() inside useEffect instead.`
3843
+ });
3844
+ }
3845
+ }
3846
+ const isAsyncComponent = /export\s+default\s+async\s+function/.test(content) || /export\s+default\s+async\s+\(/.test(content);
3847
+ if (isAsyncComponent) {
3848
+ const hasUseClient2 = content.includes('"use client"') || content.includes("'use client'");
3849
+ if (!hasUseClient2) {
3850
+ issues.push({
3851
+ type: "error",
3852
+ file: relFile,
3853
+ 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.'
3854
+ });
3855
+ }
3856
+ }
3857
+ const usesHooks = /\buse[A-Z]\w+\s*\(/.test(content);
3858
+ const hasUseClient = content.includes('"use client"') || content.includes("'use client'");
3859
+ if (usesHooks && !hasUseClient) {
3860
+ issues.push({
3861
+ type: "warning",
3862
+ file: relFile,
3863
+ message: 'Component uses React hooks but is missing "use client" directive. Add "use client" at the top of the file.'
3864
+ });
3865
+ }
3866
+ }
3867
+ return issues;
3868
+ }
3357
3869
 
3358
3870
  // src/commands/build.ts
3359
3871
  init_logger();
3872
+ init_detect_nextjs();
3360
3873
  async function buildCommand(options) {
3361
3874
  logger.header("Build Theme");
3362
3875
  let themePath;
@@ -3364,16 +3877,16 @@ async function buildCommand(options) {
3364
3877
  if (options.theme) {
3365
3878
  themeName = options.theme;
3366
3879
  try {
3367
- const workspaceThemePath = path9.join(getThemesDir(), themeName);
3368
- if (fs.existsSync(workspaceThemePath)) {
3880
+ const workspaceThemePath = path13.join(getThemesDir(), themeName);
3881
+ if (fs8.existsSync(workspaceThemePath)) {
3369
3882
  themePath = workspaceThemePath;
3370
3883
  } else {
3371
- themePath = path9.join(process.cwd(), themeName);
3884
+ themePath = path13.join(process.cwd(), themeName);
3372
3885
  }
3373
3886
  } catch {
3374
- themePath = path9.join(process.cwd(), themeName);
3887
+ themePath = path13.join(process.cwd(), themeName);
3375
3888
  }
3376
- if (!fs.existsSync(themePath)) {
3889
+ if (!fs8.existsSync(themePath)) {
3377
3890
  logger.error(`Theme "${themeName}" not found.`);
3378
3891
  process.exit(1);
3379
3892
  }
@@ -3381,11 +3894,14 @@ async function buildCommand(options) {
3381
3894
  const isThemeDir2 = [
3382
3895
  "theme.config.ts",
3383
3896
  "bundle-entry.ts",
3384
- "manifest.ts"
3385
- ].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
3897
+ "manifest.ts",
3898
+ "next.config.ts",
3899
+ "next.config.js",
3900
+ "next.config.mjs"
3901
+ ].some((f) => fs8.existsSync(path13.join(process.cwd(), f)));
3386
3902
  if (isThemeDir2) {
3387
3903
  themePath = process.cwd();
3388
- themeName = path9.basename(themePath);
3904
+ themeName = path13.basename(themePath);
3389
3905
  logger.info(`Building current theme: ${themeName}`);
3390
3906
  } else {
3391
3907
  logger.error(
@@ -3394,8 +3910,8 @@ async function buildCommand(options) {
3394
3910
  process.exit(1);
3395
3911
  }
3396
3912
  }
3397
- const packageJsonPath = path9.join(themePath, "package.json");
3398
- const hasPkgJson = fs.existsSync(packageJsonPath);
3913
+ const packageJsonPath = path13.join(themePath, "package.json");
3914
+ const hasPkgJson = fs8.existsSync(packageJsonPath);
3399
3915
  if (!hasPkgJson) {
3400
3916
  logger.warning(
3401
3917
  "No package.json found in theme. Skipping build (themes in monorepo are built via turbo)."
@@ -3410,30 +3926,42 @@ async function buildCommand(options) {
3410
3926
  }
3411
3927
  logger.newLine();
3412
3928
  logger.section("Build Steps");
3413
- logger.startSpinner("Running type check...");
3414
- const typeCheckSuccess = await runCommand("pnpm", ["type-check"], themePath);
3415
- if (!typeCheckSuccess) {
3416
- logger.stopSpinner(false, "Type check failed");
3417
- logger.error("Fix type errors before building.");
3418
- process.exit(1);
3419
- }
3420
- logger.stopSpinner(true, "Type check passed");
3421
- logger.startSpinner("Running linter...");
3422
- const lintSuccess = await runCommand("pnpm", ["lint"], themePath);
3423
- if (!lintSuccess) {
3424
- logger.stopSpinner(false, "Lint failed");
3425
- logger.error("Fix lint errors before building.");
3426
- process.exit(1);
3929
+ const pkgJson = fs8.readJsonSync(packageJsonPath);
3930
+ if (pkgJson.scripts?.["type-check"]) {
3931
+ logger.startSpinner("Running type check...");
3932
+ const typeCheckSuccess = await runCommand("pnpm", ["type-check"], themePath);
3933
+ if (!typeCheckSuccess) {
3934
+ logger.stopSpinner(false, "Type check failed");
3935
+ logger.error("Fix type errors before building.");
3936
+ process.exit(1);
3937
+ }
3938
+ logger.stopSpinner(true, "Type check passed");
3939
+ } else {
3940
+ logger.info("Skipping type check (no type-check script in package.json)");
3941
+ }
3942
+ const isNextjsForLint = isNextjsProject(themePath);
3943
+ if (!isNextjsForLint && pkgJson.scripts?.lint) {
3944
+ logger.startSpinner("Running linter...");
3945
+ const lintSuccess = await runCommand("pnpm", ["lint"], themePath);
3946
+ if (!lintSuccess) {
3947
+ logger.stopSpinner(false, "Lint failed");
3948
+ logger.error("Fix lint errors before building.");
3949
+ process.exit(1);
3950
+ }
3951
+ logger.stopSpinner(true, "Lint passed");
3952
+ } else if (isNextjsForLint) {
3953
+ logger.info("Skipping lint (Next.js project compiled via esbuild)");
3954
+ } else {
3955
+ logger.info("Skipping lint (no lint script in package.json)");
3427
3956
  }
3428
- logger.stopSpinner(true, "Lint passed");
3429
- const pkgJson = fs.readJsonSync(packageJsonPath);
3430
3957
  const buildScript = pkgJson.scripts?.build || "";
3431
3958
  const isRecursive = buildScript.includes("onexthm build") || buildScript.includes("onex build") || buildScript.includes("onex-cli build");
3959
+ const isNextjs = isNextjsProject(themePath);
3432
3960
  logger.startSpinner(
3433
3961
  options.watch ? "Building (watch mode)..." : "Building..."
3434
3962
  );
3435
3963
  let buildSuccess;
3436
- if (isRecursive) {
3964
+ if (isRecursive || isNextjs) {
3437
3965
  const { compileStandaloneTheme: compileStandaloneTheme2 } = await Promise.resolve().then(() => (init_compile_theme(), compile_theme_exports));
3438
3966
  buildSuccess = await compileStandaloneTheme2(themePath, themeName);
3439
3967
  } else {
@@ -3450,10 +3978,10 @@ async function buildCommand(options) {
3450
3978
  logger.success("\u2713 Theme built successfully!");
3451
3979
  logger.newLine();
3452
3980
  logger.info(`Theme: ${themeName}`);
3453
- const distPath = path9.join(themePath, "dist");
3454
- if (fs.existsSync(distPath)) {
3455
- logger.log(`Output: ${path9.relative(process.cwd(), distPath)}`);
3456
- const files = fs.readdirSync(distPath);
3981
+ const distPath = path13.join(themePath, "dist");
3982
+ if (fs8.existsSync(distPath)) {
3983
+ logger.log(`Output: ${path13.relative(process.cwd(), distPath)}`);
3984
+ const files = fs8.readdirSync(distPath);
3457
3985
  logger.log(`Files: ${files.length}`);
3458
3986
  }
3459
3987
  }
@@ -3508,8 +4036,8 @@ async function packageCommand(options) {
3508
4036
  let themeName;
3509
4037
  if (options.theme) {
3510
4038
  themeName = options.theme;
3511
- themePath = path9.join(getThemesDir(), themeName);
3512
- if (!fs.existsSync(themePath)) {
4039
+ themePath = path13.join(getThemesDir(), themeName);
4040
+ if (!fs8.existsSync(themePath)) {
3513
4041
  logger.error(`Theme "${themeName}" not found.`);
3514
4042
  process.exit(1);
3515
4043
  }
@@ -3517,11 +4045,14 @@ async function packageCommand(options) {
3517
4045
  const isThemeDir2 = [
3518
4046
  "theme.config.ts",
3519
4047
  "bundle-entry.ts",
3520
- "manifest.ts"
3521
- ].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
4048
+ "manifest.ts",
4049
+ "next.config.ts",
4050
+ "next.config.js",
4051
+ "next.config.mjs"
4052
+ ].some((f) => fs8.existsSync(path13.join(process.cwd(), f)));
3522
4053
  if (isThemeDir2) {
3523
4054
  themePath = process.cwd();
3524
- themeName = path9.basename(themePath);
4055
+ themeName = path13.basename(themePath);
3525
4056
  logger.info(`Packaging current theme: ${themeName}`);
3526
4057
  } else {
3527
4058
  logger.error(
@@ -3530,22 +4061,19 @@ async function packageCommand(options) {
3530
4061
  process.exit(1);
3531
4062
  }
3532
4063
  }
3533
- const packageJsonPath = path9.join(themePath, "package.json");
4064
+ const packageJsonPath = path13.join(themePath, "package.json");
3534
4065
  let version2 = "1.0.0";
3535
- if (fs.existsSync(packageJsonPath)) {
3536
- const packageJson = await fs.readJson(packageJsonPath);
4066
+ if (fs8.existsSync(packageJsonPath)) {
4067
+ const packageJson = await fs8.readJson(packageJsonPath);
3537
4068
  version2 = packageJson.version || "1.0.0";
3538
4069
  }
3539
4070
  logger.newLine();
3540
4071
  logger.info(`Theme: ${themeName}`);
3541
4072
  logger.info(`Version: ${version2}`);
3542
4073
  logger.newLine();
3543
- const compiledThemePath = path9.join(
3544
- process.cwd(),
3545
- "themes",
3546
- themeName,
3547
- "dist"
3548
- );
4074
+ const standaloneDistPath = path13.join(themePath, "dist");
4075
+ const monorepoDistPath = path13.join(process.cwd(), "themes", themeName, "dist");
4076
+ const compiledThemePath = fs8.existsSync(standaloneDistPath) ? standaloneDistPath : monorepoDistPath;
3549
4077
  if (!options.skipBuild) {
3550
4078
  logger.section("Step 1: Compile Theme");
3551
4079
  logger.startSpinner("Compiling theme with esbuild...");
@@ -3566,7 +4094,7 @@ async function packageCommand(options) {
3566
4094
  } else {
3567
4095
  logger.info("Skipping build (--skip-build flag)");
3568
4096
  }
3569
- if (!fs.existsSync(compiledThemePath)) {
4097
+ if (!fs8.existsSync(compiledThemePath)) {
3570
4098
  logger.error(`Compiled theme not found at: ${compiledThemePath}`);
3571
4099
  logger.info("Run without --skip-build to compile first.");
3572
4100
  process.exit(1);
@@ -3574,25 +4102,25 @@ async function packageCommand(options) {
3574
4102
  logger.newLine();
3575
4103
  logger.section("Step 2: Create Package");
3576
4104
  const packageName = options.name || `${themeName}-${version2}`;
3577
- const outputDir = options.output || path9.join(process.cwd(), "dist");
3578
- const outputPath = path9.join(outputDir, `${packageName}.zip`);
3579
- await fs.ensureDir(outputDir);
4105
+ const outputDir = options.output || path13.join(process.cwd(), "dist");
4106
+ const outputPath = path13.join(outputDir, `${packageName}.zip`);
4107
+ await fs8.ensureDir(outputDir);
3580
4108
  logger.startSpinner("Creating zip archive...");
3581
4109
  try {
3582
4110
  await createZipArchive(compiledThemePath, outputPath);
3583
4111
  logger.stopSpinner(true, "Package created");
3584
- const stats = await fs.stat(outputPath);
4112
+ const stats = await fs8.stat(outputPath);
3585
4113
  const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
3586
4114
  logger.newLine();
3587
4115
  logger.success("\u2713 Theme packaged successfully!");
3588
4116
  logger.newLine();
3589
4117
  logger.info(`Package: ${packageName}.zip`);
3590
4118
  logger.log(`Size: ${sizeMB} MB`);
3591
- logger.log(`Location: ${path9.relative(process.cwd(), outputPath)}`);
4119
+ logger.log(`Location: ${path13.relative(process.cwd(), outputPath)}`);
3592
4120
  logger.newLine();
3593
4121
  logger.section("Next steps:");
3594
4122
  logger.log(
3595
- ` onexthm deploy --package ${path9.relative(process.cwd(), outputPath)}`
4123
+ ` onexthm deploy --package ${path13.relative(process.cwd(), outputPath)}`
3596
4124
  );
3597
4125
  } catch (error) {
3598
4126
  logger.stopSpinner(false, "Failed to create package");
@@ -3626,7 +4154,7 @@ function runCommand2(command, args) {
3626
4154
  }
3627
4155
  async function createZipArchive(compiledThemePath, outputPath) {
3628
4156
  return new Promise((resolve, reject) => {
3629
- const output = fs.createWriteStream(outputPath);
4157
+ const output = fs8.createWriteStream(outputPath);
3630
4158
  const archive = archiver("zip", {
3631
4159
  zlib: { level: 9 }
3632
4160
  // Maximum compression
@@ -3650,14 +4178,14 @@ async function deployCommand(options) {
3650
4178
  ensureOneXProject();
3651
4179
  let packagePath;
3652
4180
  if (options.package) {
3653
- packagePath = path9.resolve(options.package);
4181
+ packagePath = path13.resolve(options.package);
3654
4182
  } else if (options.theme) {
3655
- const distDir = path9.join(process.cwd(), "dist");
3656
- if (!fs.existsSync(distDir)) {
4183
+ const distDir = path13.join(process.cwd(), "dist");
4184
+ if (!fs8.existsSync(distDir)) {
3657
4185
  logger.error("No dist/ directory found. Run 'onexthm package' first.");
3658
4186
  process.exit(1);
3659
4187
  }
3660
- const files = fs.readdirSync(distDir);
4188
+ const files = fs8.readdirSync(distDir);
3661
4189
  const packageFiles = files.filter(
3662
4190
  (f) => f.startsWith(options.theme) && f.endsWith(".zip")
3663
4191
  );
@@ -3667,7 +4195,7 @@ async function deployCommand(options) {
3667
4195
  process.exit(1);
3668
4196
  }
3669
4197
  packageFiles.sort().reverse();
3670
- packagePath = path9.join(distDir, packageFiles[0]);
4198
+ packagePath = path13.join(distDir, packageFiles[0]);
3671
4199
  } else {
3672
4200
  logger.error("Either --package or --theme must be specified.");
3673
4201
  logger.info("Examples:");
@@ -3675,17 +4203,17 @@ async function deployCommand(options) {
3675
4203
  logger.log(" onexthm deploy --theme tinan");
3676
4204
  process.exit(1);
3677
4205
  }
3678
- if (!fs.existsSync(packagePath)) {
4206
+ if (!fs8.existsSync(packagePath)) {
3679
4207
  logger.error(`Package not found: ${packagePath}`);
3680
4208
  process.exit(1);
3681
4209
  }
3682
- const stats = await fs.stat(packagePath);
4210
+ const stats = await fs8.stat(packagePath);
3683
4211
  const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
3684
- const fileName = path9.basename(packagePath);
4212
+ const fileName = path13.basename(packagePath);
3685
4213
  logger.newLine();
3686
4214
  logger.info(`Package: ${fileName}`);
3687
4215
  logger.log(`Size: ${sizeMB} MB`);
3688
- logger.log(`Path: ${path9.relative(process.cwd(), packagePath)}`);
4216
+ logger.log(`Path: ${path13.relative(process.cwd(), packagePath)}`);
3689
4217
  logger.newLine();
3690
4218
  const apiUrl = options.apiUrl || process.env.ONEX_API_URL || "http://localhost:3001";
3691
4219
  const uploadEndpoint = `${apiUrl}/website-api/themes/upload`;
@@ -3695,7 +4223,7 @@ async function deployCommand(options) {
3695
4223
  logger.startSpinner("Uploading theme package...");
3696
4224
  try {
3697
4225
  const formData = new FormData();
3698
- formData.append("theme", fs.createReadStream(packagePath), {
4226
+ formData.append("theme", fs8.createReadStream(packagePath), {
3699
4227
  filename: fileName,
3700
4228
  contentType: "application/zip"
3701
4229
  });
@@ -3842,24 +4370,24 @@ async function downloadBundleZip(apiUrl, themeId, version2) {
3842
4370
  async function createCompatibilityFiles(outputDir, manifest) {
3843
4371
  const entryFile = manifest.output?.entry || "bundle-entry.js";
3844
4372
  if (entryFile !== "bundle-entry.js" && entryFile.startsWith("bundle-entry-")) {
3845
- const hashedPath = path9.join(outputDir, entryFile);
3846
- const stablePath = path9.join(outputDir, "bundle-entry.js");
3847
- if (await fs.pathExists(hashedPath)) {
3848
- await fs.copy(hashedPath, stablePath);
4373
+ const hashedPath = path13.join(outputDir, entryFile);
4374
+ const stablePath = path13.join(outputDir, "bundle-entry.js");
4375
+ if (await fs8.pathExists(hashedPath)) {
4376
+ await fs8.copy(hashedPath, stablePath);
3849
4377
  const mapPath = hashedPath + ".map";
3850
- if (await fs.pathExists(mapPath)) {
3851
- await fs.copy(mapPath, stablePath + ".map");
4378
+ if (await fs8.pathExists(mapPath)) {
4379
+ await fs8.copy(mapPath, stablePath + ".map");
3852
4380
  }
3853
4381
  }
3854
4382
  }
3855
- const sectionsRegistryPath = path9.join(outputDir, "sections-registry.js");
4383
+ const sectionsRegistryPath = path13.join(outputDir, "sections-registry.js");
3856
4384
  const content = `// Re-export all sections from bundle-entry
3857
4385
  // This file exists to maintain compatibility with the import path
3858
4386
  export * from './bundle-entry.js';
3859
4387
  `;
3860
- await fs.writeFile(sectionsRegistryPath, content, "utf-8");
3861
- const pkgJsonPath = path9.join(outputDir, "package.json");
3862
- await fs.writeFile(pkgJsonPath, '{\n "type": "module"\n}\n', "utf-8");
4388
+ await fs8.writeFile(sectionsRegistryPath, content, "utf-8");
4389
+ const pkgJsonPath = path13.join(outputDir, "package.json");
4390
+ await fs8.writeFile(pkgJsonPath, '{\n "type": "module"\n}\n', "utf-8");
3863
4391
  }
3864
4392
  function showDownloadFailureHelp(themeId, apiUrl) {
3865
4393
  console.log();
@@ -3899,7 +4427,7 @@ function showDownloadFailureHelp(themeId, apiUrl) {
3899
4427
  }
3900
4428
  async function downloadCommand(options) {
3901
4429
  logger.header("Download Theme");
3902
- const env = options.env ?? "dev";
4430
+ const env = options.env;
3903
4431
  const apiUrl = getApiUrl(env);
3904
4432
  logger.info(`Environment: ${env} (${apiUrl})`);
3905
4433
  const spinner = ora("Initializing download...").start();
@@ -3951,14 +4479,14 @@ async function downloadCommand(options) {
3951
4479
  const sizeMB = (zipBuffer.length / 1024 / 1024).toFixed(2);
3952
4480
  spinner.succeed(`Downloaded bundle.zip (${sizeMB} MB)`);
3953
4481
  spinner.start("Extracting bundle...");
3954
- await fs.remove(outputDir);
3955
- await fs.ensureDir(outputDir);
4482
+ await fs8.remove(outputDir);
4483
+ await fs8.ensureDir(outputDir);
3956
4484
  const zip = new AdmZip(zipBuffer);
3957
4485
  zip.extractAllTo(outputDir, true);
3958
4486
  const entries = zip.getEntries().filter((e) => !e.isDirectory);
3959
4487
  spinner.succeed(`Extracted ${entries.length} files to ${outputDir}`);
3960
- const manifestPath = path9.join(outputDir, "manifest.json");
3961
- const manifest = await fs.readJson(manifestPath);
4488
+ const manifestPath = path13.join(outputDir, "manifest.json");
4489
+ const manifest = await fs8.readJson(manifestPath);
3962
4490
  await createCompatibilityFiles(outputDir, manifest);
3963
4491
  console.log();
3964
4492
  logger.success(chalk4.green.bold("Theme downloaded successfully!"));
@@ -4078,9 +4606,9 @@ async function renameTheme(themeDir, oldName, newName) {
4078
4606
  const oldPrefix = `${oldName}-`;
4079
4607
  const newPrefix = `${newName}-`;
4080
4608
  const newDisplayName = newName.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
4081
- const pkgPath = path9.join(themeDir, "package.json");
4082
- if (await fs.pathExists(pkgPath)) {
4083
- const pkg = await fs.readJson(pkgPath);
4609
+ const pkgPath = path13.join(themeDir, "package.json");
4610
+ if (await fs8.pathExists(pkgPath)) {
4611
+ const pkg = await fs8.readJson(pkgPath);
4084
4612
  pkg.name = `@onex-themes/${newName}`;
4085
4613
  if (pkg.description) {
4086
4614
  pkg.description = pkg.description.replace(
@@ -4092,33 +4620,33 @@ async function renameTheme(themeDir, oldName, newName) {
4092
4620
  if (pkg.devDependencies?.["@onexapis/cli"]) {
4093
4621
  delete pkg.devDependencies["@onexapis/cli"];
4094
4622
  }
4095
- await fs.writeJson(pkgPath, pkg, { spaces: 2 });
4623
+ await fs8.writeJson(pkgPath, pkg, { spaces: 2 });
4096
4624
  }
4097
- const configPath = path9.join(themeDir, "theme.config.ts");
4098
- if (await fs.pathExists(configPath)) {
4099
- let content = await fs.readFile(configPath, "utf-8");
4625
+ const configPath = path13.join(themeDir, "theme.config.ts");
4626
+ if (await fs8.pathExists(configPath)) {
4627
+ let content = await fs8.readFile(configPath, "utf-8");
4100
4628
  content = content.replace(/id:\s*"[^"]*"/, `id: "${newName}"`);
4101
4629
  content = content.replace(
4102
4630
  /name:\s*"[^"]*Theme"/,
4103
4631
  `name: "${newDisplayName} Theme"`
4104
4632
  );
4105
- await fs.writeFile(configPath, content);
4633
+ await fs8.writeFile(configPath, content);
4106
4634
  }
4107
- const layoutPath = path9.join(themeDir, "theme.layout.ts");
4108
- if (await fs.pathExists(layoutPath)) {
4109
- let content = await fs.readFile(layoutPath, "utf-8");
4635
+ const layoutPath = path13.join(themeDir, "theme.layout.ts");
4636
+ if (await fs8.pathExists(layoutPath)) {
4637
+ let content = await fs8.readFile(layoutPath, "utf-8");
4110
4638
  content = content.replace(/id:\s*"[^"]*"/, `id: "${newName}"`);
4111
4639
  content = content.replace(
4112
4640
  /name:\s*"[^"]*Theme"/,
4113
4641
  `name: "${newDisplayName} Theme"`
4114
4642
  );
4115
- await fs.writeFile(layoutPath, content);
4643
+ await fs8.writeFile(layoutPath, content);
4116
4644
  }
4117
4645
  const oldDisplayName = oldName.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
4118
4646
  const tsFiles = await glob("**/*.ts", { cwd: themeDir, nodir: true });
4119
4647
  for (const file of tsFiles) {
4120
- const filePath = path9.join(themeDir, file);
4121
- let content = await fs.readFile(filePath, "utf-8");
4648
+ const filePath = path13.join(themeDir, file);
4649
+ let content = await fs8.readFile(filePath, "utf-8");
4122
4650
  const original = content;
4123
4651
  content = content.replace(
4124
4652
  new RegExp(`"${oldPrefix}`, "g"),
@@ -4133,13 +4661,13 @@ async function renameTheme(themeDir, oldName, newName) {
4133
4661
  `${newDisplayName} Theme`
4134
4662
  );
4135
4663
  if (content !== original) {
4136
- await fs.writeFile(filePath, content);
4664
+ await fs8.writeFile(filePath, content);
4137
4665
  }
4138
4666
  }
4139
4667
  }
4140
4668
  async function cloneCommand(themeName, options) {
4141
4669
  logger.header("Clone Theme Source");
4142
- const env = options.env ?? "dev";
4670
+ const env = options.env;
4143
4671
  const apiUrl = getApiUrl(env);
4144
4672
  logger.info(`Environment: ${env} (${apiUrl})`);
4145
4673
  if (options.bucket) {
@@ -4160,8 +4688,8 @@ async function cloneCommand(themeName, options) {
4160
4688
  }
4161
4689
  const spinner = ora("Initializing clone...").start();
4162
4690
  try {
4163
- const outputDir = options.output || path9.resolve(process.cwd(), newName);
4164
- if (await fs.pathExists(outputDir)) {
4691
+ const outputDir = options.output || path13.resolve(process.cwd(), newName);
4692
+ if (await fs8.pathExists(outputDir)) {
4165
4693
  spinner.fail(chalk4.red(`Directory already exists: ${outputDir}`));
4166
4694
  logger.info(
4167
4695
  chalk4.gray(
@@ -4194,7 +4722,7 @@ async function cloneCommand(themeName, options) {
4194
4722
  const sizeMB = (zipBuffer.length / 1024 / 1024).toFixed(2);
4195
4723
  spinner.succeed(`Downloaded source.zip (${sizeMB} MB)`);
4196
4724
  spinner.start(`Extracting to ${outputDir}...`);
4197
- await fs.ensureDir(outputDir);
4725
+ await fs8.ensureDir(outputDir);
4198
4726
  const zip = new AdmZip(zipBuffer);
4199
4727
  zip.extractAllTo(outputDir, true);
4200
4728
  const entries = zip.getEntries().filter((e) => !e.isDirectory);
@@ -4206,9 +4734,9 @@ async function cloneCommand(themeName, options) {
4206
4734
  spinner.succeed(
4207
4735
  `Renamed theme: ${chalk4.gray(themeName)} \u2192 ${chalk4.cyan(newName)}`
4208
4736
  );
4209
- const envExamplePath = path9.join(outputDir, ".env.example");
4210
- if (!await fs.pathExists(envExamplePath)) {
4211
- await fs.writeFile(
4737
+ const envExamplePath = path13.join(outputDir, ".env.example");
4738
+ if (!await fs8.pathExists(envExamplePath)) {
4739
+ await fs8.writeFile(
4212
4740
  envExamplePath,
4213
4741
  [
4214
4742
  "# API Configuration (enables real data in preview)",
@@ -4219,8 +4747,8 @@ async function cloneCommand(themeName, options) {
4219
4747
  ].join("\n")
4220
4748
  );
4221
4749
  }
4222
- const mcpJsonPath = path9.join(outputDir, ".mcp.json");
4223
- if (await fs.pathExists(mcpJsonPath)) {
4750
+ const mcpJsonPath = path13.join(outputDir, ".mcp.json");
4751
+ if (await fs8.pathExists(mcpJsonPath)) {
4224
4752
  const { default: inquirerMod } = await import('inquirer');
4225
4753
  const { figmaApiKey } = await inquirerMod.prompt([
4226
4754
  {
@@ -4229,7 +4757,7 @@ async function cloneCommand(themeName, options) {
4229
4757
  message: "Figma API Key (optional, for Figma-to-code MCP \u2014 press Enter to skip):"
4230
4758
  }
4231
4759
  ]);
4232
- let mcpContent = await fs.readFile(mcpJsonPath, "utf-8");
4760
+ let mcpContent = await fs8.readFile(mcpJsonPath, "utf-8");
4233
4761
  if (figmaApiKey) {
4234
4762
  mcpContent = mcpContent.replace("__FIGMA_API_KEY__", figmaApiKey);
4235
4763
  } else {
@@ -4240,11 +4768,11 @@ async function cloneCommand(themeName, options) {
4240
4768
  } catch {
4241
4769
  }
4242
4770
  }
4243
- await fs.writeFile(mcpJsonPath, mcpContent, "utf-8");
4771
+ await fs8.writeFile(mcpJsonPath, mcpContent, "utf-8");
4244
4772
  }
4245
4773
  if (options.install !== false) {
4246
- const hasPkgJson = await fs.pathExists(
4247
- path9.join(outputDir, "package.json")
4774
+ const hasPkgJson = await fs8.pathExists(
4775
+ path13.join(outputDir, "package.json")
4248
4776
  );
4249
4777
  if (hasPkgJson) {
4250
4778
  spinner.start("Installing dependencies...");
@@ -4272,7 +4800,7 @@ async function cloneCommand(themeName, options) {
4272
4800
  console.log(chalk4.cyan(" Files: ") + chalk4.white(entries.length));
4273
4801
  console.log();
4274
4802
  console.log(chalk4.cyan("Next steps:"));
4275
- console.log(chalk4.gray(` cd ${path9.relative(process.cwd(), outputDir)}`));
4803
+ console.log(chalk4.gray(` cd ${path13.relative(process.cwd(), outputDir)}`));
4276
4804
  console.log(
4277
4805
  chalk4.gray(" cp .env.example .env # then add your Company ID")
4278
4806
  );
@@ -4299,16 +4827,16 @@ async function devCommand(options) {
4299
4827
  if (options.theme) {
4300
4828
  themeName = options.theme;
4301
4829
  try {
4302
- const workspaceThemePath = path9.join(getThemesDir(), themeName);
4303
- if (fs.existsSync(workspaceThemePath)) {
4830
+ const workspaceThemePath = path13.join(getThemesDir(), themeName);
4831
+ if (fs8.existsSync(workspaceThemePath)) {
4304
4832
  themePath = workspaceThemePath;
4305
4833
  } else {
4306
- themePath = path9.join(process.cwd(), themeName);
4834
+ themePath = path13.join(process.cwd(), themeName);
4307
4835
  }
4308
4836
  } catch {
4309
- themePath = path9.join(process.cwd(), themeName);
4837
+ themePath = path13.join(process.cwd(), themeName);
4310
4838
  }
4311
- if (!fs.existsSync(themePath)) {
4839
+ if (!fs8.existsSync(themePath)) {
4312
4840
  logger.error(`Theme "${themeName}" not found.`);
4313
4841
  process.exit(1);
4314
4842
  }
@@ -4316,11 +4844,14 @@ async function devCommand(options) {
4316
4844
  const isThemeDir2 = [
4317
4845
  "theme.config.ts",
4318
4846
  "bundle-entry.ts",
4319
- "manifest.ts"
4320
- ].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
4847
+ "manifest.ts",
4848
+ "next.config.ts",
4849
+ "next.config.js",
4850
+ "next.config.mjs"
4851
+ ].some((f) => fs8.existsSync(path13.join(process.cwd(), f)));
4321
4852
  if (isThemeDir2) {
4322
4853
  themePath = process.cwd();
4323
- themeName = path9.basename(themePath);
4854
+ themeName = path13.basename(themePath);
4324
4855
  } else {
4325
4856
  logger.error(
4326
4857
  "Not in a theme directory and no --theme specified. Run from theme root or use --theme flag."
@@ -4389,9 +4920,9 @@ async function devCommand(options) {
4389
4920
  watcher.close();
4390
4921
  await context2.dispose();
4391
4922
  server.close();
4392
- const shimPath = path9.join(outputDir, ".process-shim.js");
4923
+ const shimPath = path13.join(outputDir, ".process-shim.js");
4393
4924
  try {
4394
- await fs8.unlink(shimPath);
4925
+ await fs11.unlink(shimPath);
4395
4926
  } catch {
4396
4927
  }
4397
4928
  process.exit(0);
@@ -4400,8 +4931,8 @@ async function devCommand(options) {
4400
4931
 
4401
4932
  // src/commands/config.ts
4402
4933
  init_logger();
4403
- var CONFIG_DIR = path9.join(os.homedir(), ".onexthm");
4404
- var CONFIG_FILE = path9.join(CONFIG_DIR, ".env");
4934
+ var CONFIG_DIR = path13.join(os.homedir(), ".onexthm");
4935
+ var CONFIG_FILE = path13.join(CONFIG_DIR, ".env");
4405
4936
  var CONFIG_ENTRIES = [
4406
4937
  {
4407
4938
  key: "AWS_ACCESS_KEY_ID",
@@ -4487,7 +5018,7 @@ async function configCommand() {
4487
5018
  logger.header("OneX CLI Configuration");
4488
5019
  let existing = {};
4489
5020
  try {
4490
- const content = await fs.readFile(CONFIG_FILE, "utf-8");
5021
+ const content = await fs8.readFile(CONFIG_FILE, "utf-8");
4491
5022
  existing = parseEnvFile(content);
4492
5023
  logger.info(`Existing config found at: ${CONFIG_FILE}`);
4493
5024
  logger.newLine();
@@ -4523,8 +5054,8 @@ async function configCommand() {
4523
5054
  for (const key of Object.keys(merged)) {
4524
5055
  if (!merged[key]) delete merged[key];
4525
5056
  }
4526
- await fs.ensureDir(CONFIG_DIR);
4527
- await fs.writeFile(CONFIG_FILE, serializeEnv(merged));
5057
+ await fs8.ensureDir(CONFIG_DIR);
5058
+ await fs8.writeFile(CONFIG_FILE, serializeEnv(merged));
4528
5059
  logger.newLine();
4529
5060
  logger.success(`Config saved to: ${CONFIG_FILE}`);
4530
5061
  logger.newLine();
@@ -4543,8 +5074,8 @@ async function configCommand() {
4543
5074
 
4544
5075
  // src/commands/login.ts
4545
5076
  init_logger();
4546
- async function loginCommand(options = {}) {
4547
- const env = options.env ?? "dev";
5077
+ async function loginCommand(options) {
5078
+ const env = options.env;
4548
5079
  const apiUrl = getApiUrl(env);
4549
5080
  logger.header("OneX Theme Developer Login");
4550
5081
  logger.info(`Environment: ${env} (${apiUrl})`);
@@ -4633,8 +5164,8 @@ async function loginCommand(options = {}) {
4633
5164
 
4634
5165
  // src/commands/logout.ts
4635
5166
  init_logger();
4636
- async function logoutCommand(options = {}) {
4637
- const env = options.env ?? "dev";
5167
+ async function logoutCommand(options) {
5168
+ const env = options.env;
4638
5169
  const tokens = loadAuthTokens(env);
4639
5170
  if (!tokens) {
4640
5171
  logger.info(`Not logged in to ${env} environment.`);
@@ -4646,8 +5177,8 @@ async function logoutCommand(options = {}) {
4646
5177
 
4647
5178
  // src/commands/whoami.ts
4648
5179
  init_logger();
4649
- async function whoamiCommand(options = {}) {
4650
- const env = options.env ?? "dev";
5180
+ async function whoamiCommand(options) {
5181
+ const env = options.env;
4651
5182
  const tokens = loadAuthTokens(env);
4652
5183
  if (!tokens) {
4653
5184
  logger.error(
@@ -4669,96 +5200,362 @@ async function whoamiCommand(options = {}) {
4669
5200
 
4670
5201
  // src/commands/publish.ts
4671
5202
  init_logger();
4672
- var MIME_MAP = {
4673
- ".png": "image/png",
4674
- ".jpg": "image/jpeg",
4675
- ".jpeg": "image/jpeg",
4676
- ".gif": "image/gif",
4677
- ".webp": "image/webp",
4678
- ".avif": "image/avif",
4679
- ".svg": "image/svg+xml",
4680
- ".ico": "image/x-icon",
4681
- ".bmp": "image/bmp",
4682
- ".woff": "font/woff",
4683
- ".woff2": "font/woff2",
4684
- ".ttf": "font/ttf",
4685
- ".otf": "font/otf",
4686
- ".eot": "application/vnd.ms-fontobject",
4687
- ".mp4": "video/mp4",
4688
- ".webm": "video/webm",
4689
- ".mov": "video/quicktime",
4690
- ".ogg": "video/ogg",
4691
- ".json": "application/json"
5203
+ init_scan_theme_assets();
5204
+
5205
+ // src/utils/fetch-prior-schemas.ts
5206
+ async function fetchPriorGateManifests(themeId, env) {
5207
+ const apiUrl = getApiUrl(env);
5208
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/gate-manifests/latest`;
5209
+ let res;
5210
+ try {
5211
+ res = await authenticatedFetch(url, { method: "GET" }, env);
5212
+ } catch (err) {
5213
+ return {
5214
+ result: null,
5215
+ reason: `network error: ${err instanceof Error ? err.message : "unknown"}`
5216
+ };
5217
+ }
5218
+ if (res.status === 404) {
5219
+ return { result: null, reason: "no-prior" };
5220
+ }
5221
+ if (!res.ok) {
5222
+ return {
5223
+ result: null,
5224
+ reason: `server returned ${res.status} ${res.statusText}`
5225
+ };
5226
+ }
5227
+ let data;
5228
+ try {
5229
+ data = await res.json();
5230
+ } catch {
5231
+ return { result: null, reason: "non-JSON response from server" };
5232
+ }
5233
+ const body = data.statusCode ? data.body : data;
5234
+ if (!body || typeof body.version !== "string" || !body.schemas || !body.assets) {
5235
+ return { result: null, reason: "malformed response (missing fields)" };
5236
+ }
5237
+ return {
5238
+ result: {
5239
+ version: body.version,
5240
+ schemas: body.schemas,
5241
+ assets: body.assets
5242
+ },
5243
+ reason: null
5244
+ };
5245
+ }
5246
+
5247
+ // src/utils/schema-diff.ts
5248
+ var SEVERITY = {
5249
+ safe: 0,
5250
+ "safe-rename": 1,
5251
+ "defaults-only": 2,
5252
+ additive: 3,
5253
+ breaking: 4,
5254
+ "breaking-asset": 5,
5255
+ "breaking-severe": 6
4692
5256
  };
4693
- var HASH_LEN = 8;
4694
- var VIDEO_EXTENSIONS = [
4695
- ".mp4",
4696
- ".webm",
4697
- ".ogg",
4698
- ".mov",
4699
- ".avi",
4700
- ".mkv"
4701
- ];
4702
- function isVideoAsset(filePath) {
4703
- const lower = filePath.toLowerCase();
4704
- return VIDEO_EXTENSIONS.some((ext) => lower.endsWith(ext));
5257
+ var BUMP_FOR = {
5258
+ safe: "patch",
5259
+ "safe-rename": "patch",
5260
+ "defaults-only": "patch",
5261
+ additive: "minor",
5262
+ breaking: "major",
5263
+ "breaking-asset": "major",
5264
+ "breaking-severe": "major"
5265
+ };
5266
+ function bumpFor(kind) {
5267
+ return BUMP_FOR[kind];
4705
5268
  }
4706
- function mimeFor(filename) {
4707
- const ext = path9.extname(filename).toLowerCase();
4708
- return MIME_MAP[ext] || "application/octet-stream";
5269
+ function maxSeverity(a, b) {
5270
+ return SEVERITY[a] >= SEVERITY[b] ? a : b;
4709
5271
  }
4710
- async function sha256Prefix(absPath, len) {
4711
- const buf = await fs.readFile(absPath);
4712
- return crypto.createHash("sha256").update(buf).digest("hex").slice(0, len);
5272
+ function classify(changes) {
5273
+ let highest = "safe";
5274
+ for (const c of changes) {
5275
+ highest = maxSeverity(highest, c.kind);
5276
+ }
5277
+ return { bump: bumpFor(highest), highest, changes };
4713
5278
  }
4714
- function insertHashIntoName(relPath, hash) {
4715
- const dir = path9.posix.dirname(relPath);
4716
- const base = path9.posix.basename(relPath);
4717
- const ext = path9.posix.extname(base);
4718
- const stem = ext ? base.slice(0, -ext.length) : base;
4719
- const hashed = `${stem}-${hash}${ext}`;
4720
- return dir === "." ? hashed : `${dir}/${hashed}`;
5279
+ function diffManifests(prior, current) {
5280
+ const changes = [];
5281
+ const priorSections = prior.schemas.sections;
5282
+ const currentSections = current.schemas.sections;
5283
+ const sectionTypes = /* @__PURE__ */ new Set([
5284
+ ...Object.keys(priorSections),
5285
+ ...Object.keys(currentSections)
5286
+ ]);
5287
+ for (const type of [...sectionTypes].sort()) {
5288
+ const p = priorSections[type];
5289
+ const c = currentSections[type];
5290
+ if (p && !c) {
5291
+ changes.push({
5292
+ kind: "breaking-severe",
5293
+ path: `sections.${type}`,
5294
+ detail: `Section type "${type}" removed. Pages using this section will render empty.`
5295
+ });
5296
+ continue;
5297
+ }
5298
+ if (!p && c) {
5299
+ changes.push({
5300
+ kind: "additive",
5301
+ path: `sections.${type}`,
5302
+ detail: `Section type "${type}" added.`
5303
+ });
5304
+ continue;
5305
+ }
5306
+ if (p && c) diffSection(p, c, changes);
5307
+ }
5308
+ diffAssets(prior.assets, current.assets, changes);
5309
+ return changes;
4721
5310
  }
4722
- async function scanThemeAssets(distDir) {
4723
- const assetsDir = path9.join(distDir, "theme-assets");
4724
- if (!await fs.pathExists(assetsDir)) return [];
4725
- const files = await glob("**/*", {
4726
- cwd: assetsDir,
4727
- nodir: true,
4728
- dot: false
4729
- });
4730
- const results = [];
4731
- for (const rel of files) {
4732
- const absPath = path9.join(assetsDir, rel);
4733
- const stat = await fs.stat(absPath);
4734
- if (!stat.isFile()) continue;
4735
- const originalPath = rel.split(path9.sep).join("/");
4736
- const hash = await sha256Prefix(absPath, HASH_LEN);
4737
- const hashedPath = insertHashIntoName(originalPath, hash);
4738
- const contentType = mimeFor(rel);
4739
- results.push({
4740
- originalPath,
4741
- hashedPath,
4742
- hash,
4743
- size: stat.size,
4744
- contentType,
4745
- absPath
5311
+ function diffSection(prior, current, out) {
5312
+ const type = current.type;
5313
+ if (JSON.stringify(prior.dataRequirements) !== JSON.stringify(current.dataRequirements)) {
5314
+ out.push({
5315
+ kind: "breaking",
5316
+ path: `sections.${type}.dataRequirements`,
5317
+ detail: "dataRequirements changed."
4746
5318
  });
4747
5319
  }
4748
- results.sort((a, b) => a.originalPath.localeCompare(b.originalPath));
4749
- return results;
5320
+ diffFieldList(
5321
+ prior.settings,
5322
+ current.settings,
5323
+ `sections.${type}.settings`,
5324
+ out
5325
+ );
5326
+ diffDefaults(
5327
+ prior.defaults,
5328
+ current.defaults,
5329
+ `sections.${type}.defaults`,
5330
+ out
5331
+ );
5332
+ diffBlocks(prior.blocks, current.blocks, `sections.${type}.blocks`, out);
4750
5333
  }
4751
- function buildAssetMap(entries) {
4752
- const map = {};
4753
- for (const e of entries) {
4754
- map[e.originalPath] = e.hashedPath;
5334
+ function diffBlocks(prior, current, pathPrefix, out) {
5335
+ const priorByType = new Map(prior.map((b) => [b.type, b]));
5336
+ const currentByType = new Map(current.map((b) => [b.type, b]));
5337
+ for (const type of /* @__PURE__ */ new Set([
5338
+ ...priorByType.keys(),
5339
+ ...currentByType.keys()
5340
+ ])) {
5341
+ const p = priorByType.get(type);
5342
+ const c = currentByType.get(type);
5343
+ if (p && !c) {
5344
+ out.push({
5345
+ kind: "breaking",
5346
+ path: `${pathPrefix}.${type}`,
5347
+ detail: `Block type "${type}" removed.`
5348
+ });
5349
+ continue;
5350
+ }
5351
+ if (!p && c) {
5352
+ out.push({
5353
+ kind: "additive",
5354
+ path: `${pathPrefix}.${type}`,
5355
+ detail: `Block type "${type}" added.`
5356
+ });
5357
+ continue;
5358
+ }
5359
+ if (p && c) {
5360
+ diffFieldList(
5361
+ p.settings,
5362
+ c.settings,
5363
+ `${pathPrefix}.${type}.settings`,
5364
+ out
5365
+ );
5366
+ diffDefaults(
5367
+ p.defaults,
5368
+ c.defaults,
5369
+ `${pathPrefix}.${type}.defaults`,
5370
+ out
5371
+ );
5372
+ }
4755
5373
  }
4756
- return map;
5374
+ }
5375
+ function diffFieldList(prior, current, pathPrefix, out) {
5376
+ const priorById = new Map(prior.map((f) => [f.id, f]));
5377
+ const currentById = new Map(current.map((f) => [f.id, f]));
5378
+ const aliasToCurrent = /* @__PURE__ */ new Map();
5379
+ for (const f of current) {
5380
+ if (f.aliases) {
5381
+ for (const alias of f.aliases) {
5382
+ aliasToCurrent.set(alias, f);
5383
+ }
5384
+ }
5385
+ }
5386
+ for (const [id, p] of priorById) {
5387
+ const c = currentById.get(id);
5388
+ if (c) {
5389
+ diffFieldPair(p, c, `${pathPrefix}.${id}`, out);
5390
+ continue;
5391
+ }
5392
+ const renamed = aliasToCurrent.get(id);
5393
+ if (renamed) {
5394
+ if (renamed.type === p.type) {
5395
+ out.push({
5396
+ kind: "safe-rename",
5397
+ path: `${pathPrefix}.${id}`,
5398
+ detail: `Field "${id}" renamed to "${renamed.id}" (alias preserved).`
5399
+ });
5400
+ } else {
5401
+ out.push({
5402
+ kind: "breaking",
5403
+ path: `${pathPrefix}.${id}`,
5404
+ detail: `Field "${id}" renamed to "${renamed.id}" but type changed (${p.type} \u2192 ${renamed.type}).`
5405
+ });
5406
+ }
5407
+ } else {
5408
+ out.push({
5409
+ kind: "breaking",
5410
+ path: `${pathPrefix}.${id}`,
5411
+ detail: `Field "${id}" removed. Consider adding aliases: ["${id}"] to the replacement field if this was a rename.`
5412
+ });
5413
+ }
5414
+ }
5415
+ for (const [id, c] of currentById) {
5416
+ if (priorById.has(id)) continue;
5417
+ const coveredByAlias = c.aliases?.some((a) => priorById.has(a)) ?? false;
5418
+ if (coveredByAlias) continue;
5419
+ if (c.required && c.default === void 0) {
5420
+ out.push({
5421
+ kind: "breaking",
5422
+ path: `${pathPrefix}.${id}`,
5423
+ detail: `Required field "${id}" added with no default. Existing instances cannot satisfy it.`
5424
+ });
5425
+ } else {
5426
+ out.push({
5427
+ kind: "additive",
5428
+ path: `${pathPrefix}.${id}`,
5429
+ detail: `Field "${id}" added.`
5430
+ });
5431
+ }
5432
+ }
5433
+ }
5434
+ function diffFieldPair(p, c, path25, out) {
5435
+ if (p.type !== c.type) {
5436
+ out.push({
5437
+ kind: "breaking",
5438
+ path: path25,
5439
+ detail: `Type changed (${p.type} \u2192 ${c.type}). Saved values may misrender.`
5440
+ });
5441
+ return;
5442
+ }
5443
+ if (p.required !== true && c.required === true) {
5444
+ out.push({
5445
+ kind: "breaking",
5446
+ path: path25,
5447
+ detail: "Field became required. Existing empty instances now invalid."
5448
+ });
5449
+ }
5450
+ if (typeof p.maxLength === "number" || typeof c.maxLength === "number") {
5451
+ if ((c.maxLength ?? Infinity) < (p.maxLength ?? Infinity)) {
5452
+ out.push({
5453
+ kind: "breaking",
5454
+ path: path25,
5455
+ detail: `maxLength tightened (${p.maxLength ?? "\u221E"} \u2192 ${c.maxLength}).`
5456
+ });
5457
+ }
5458
+ }
5459
+ if (typeof p.min === "number" || typeof c.min === "number") {
5460
+ if ((c.min ?? -Infinity) > (p.min ?? -Infinity)) {
5461
+ out.push({
5462
+ kind: "breaking",
5463
+ path: path25,
5464
+ detail: `min raised (${p.min ?? "-\u221E"} \u2192 ${c.min}).`
5465
+ });
5466
+ }
5467
+ }
5468
+ if (typeof p.max === "number" || typeof c.max === "number") {
5469
+ if ((c.max ?? Infinity) < (p.max ?? Infinity)) {
5470
+ out.push({
5471
+ kind: "breaking",
5472
+ path: path25,
5473
+ detail: `max lowered (${p.max ?? "\u221E"} \u2192 ${c.max}).`
5474
+ });
5475
+ }
5476
+ }
5477
+ if (p.options || c.options) {
5478
+ const priorOpts = new Set(p.options ?? []);
5479
+ const currentOpts = new Set(c.options ?? []);
5480
+ const removed = [...priorOpts].filter((o) => !currentOpts.has(o));
5481
+ const added = [...currentOpts].filter((o) => !priorOpts.has(o));
5482
+ if (removed.length > 0) {
5483
+ out.push({
5484
+ kind: "breaking",
5485
+ path: path25,
5486
+ detail: `Option(s) removed: ${removed.join(", ")}. Existing saved values may be orphaned.`
5487
+ });
5488
+ }
5489
+ if (added.length > 0) {
5490
+ out.push({
5491
+ kind: "additive",
5492
+ path: path25,
5493
+ detail: `Option(s) added: ${added.join(", ")}.`
5494
+ });
5495
+ }
5496
+ }
5497
+ if (!deepEqual(p.default, c.default)) {
5498
+ out.push({
5499
+ kind: "defaults-only",
5500
+ path: path25,
5501
+ detail: `Default changed: ${JSON.stringify(p.default)} \u2192 ${JSON.stringify(c.default)}.`
5502
+ });
5503
+ }
5504
+ }
5505
+ function diffDefaults(prior, current, pathPrefix, out) {
5506
+ const keys = /* @__PURE__ */ new Set([...Object.keys(prior), ...Object.keys(current)]);
5507
+ for (const key of [...keys].sort()) {
5508
+ if (!(key in prior) || !(key in current)) continue;
5509
+ if (!deepEqual(prior[key], current[key])) {
5510
+ out.push({
5511
+ kind: "defaults-only",
5512
+ path: `${pathPrefix}.${key}`,
5513
+ detail: `Default value changed.`
5514
+ });
5515
+ }
5516
+ }
5517
+ }
5518
+ function diffAssets(prior, current, out) {
5519
+ const currentPaths = new Set(current.assets.map((a) => a.path));
5520
+ for (const a of prior.assets) {
5521
+ if (!currentPaths.has(a.path)) {
5522
+ out.push({
5523
+ kind: "breaking-asset",
5524
+ path: `theme-assets/${a.path}`,
5525
+ detail: `Asset "${a.path}" was present in the prior version and is now missing. Code that references it hardcoded will break.`
5526
+ });
5527
+ }
5528
+ }
5529
+ }
5530
+ function deepEqual(a, b) {
5531
+ if (a === b) return true;
5532
+ if (a === null || b === null) return false;
5533
+ if (typeof a !== typeof b) return false;
5534
+ if (typeof a !== "object") return false;
5535
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
5536
+ if (Array.isArray(a) && Array.isArray(b)) {
5537
+ if (a.length !== b.length) return false;
5538
+ for (let i = 0; i < a.length; i++) {
5539
+ if (!deepEqual(a[i], b[i])) return false;
5540
+ }
5541
+ return true;
5542
+ }
5543
+ const ak = Object.keys(a);
5544
+ const bk = Object.keys(b);
5545
+ if (ak.length !== bk.length) return false;
5546
+ for (const k of ak) {
5547
+ if (!deepEqual(
5548
+ a[k],
5549
+ b[k]
5550
+ ))
5551
+ return false;
5552
+ }
5553
+ return true;
4757
5554
  }
4758
5555
 
4759
5556
  // src/commands/publish.ts
4760
5557
  async function publishCommand(options) {
4761
- const env = options.env ?? "dev";
5558
+ const env = options.env;
4762
5559
  logger.header("OneX Theme Publish");
4763
5560
  logger.info(`Environment: ${env} (${getApiUrl(env)})`);
4764
5561
  logger.newLine();
@@ -4772,13 +5569,13 @@ async function publishCommand(options) {
4772
5569
  logger.info(`Logged in as: ${tokens.user.email}`);
4773
5570
  let themePath;
4774
5571
  if (options.theme) {
4775
- themePath = path9.resolve(options.theme);
5572
+ themePath = path13.resolve(options.theme);
4776
5573
  } else {
4777
5574
  const isThemeDir2 = [
4778
5575
  "theme.config.ts",
4779
5576
  "bundle-entry.ts",
4780
5577
  "manifest.ts"
4781
- ].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
5578
+ ].some((f) => fs8.existsSync(path13.join(process.cwd(), f)));
4782
5579
  if (isThemeDir2) {
4783
5580
  themePath = process.cwd();
4784
5581
  } else {
@@ -4788,13 +5585,13 @@ async function publishCommand(options) {
4788
5585
  process.exit(1);
4789
5586
  }
4790
5587
  }
4791
- const pkgPath = path9.join(themePath, "package.json");
4792
- if (!fs.existsSync(pkgPath)) {
5588
+ const pkgPath = path13.join(themePath, "package.json");
5589
+ if (!fs8.existsSync(pkgPath)) {
4793
5590
  logger.error("No package.json found in theme directory");
4794
5591
  process.exit(1);
4795
5592
  }
4796
- const pkg = fs.readJsonSync(pkgPath);
4797
- const themeId = pkg.name?.replace("@onex-themes/", "") || path9.basename(themePath);
5593
+ const pkg = fs8.readJsonSync(pkgPath);
5594
+ const themeId = pkg.name?.replace("@onex-themes/", "") || path13.basename(themePath);
4798
5595
  if (options.bump) {
4799
5596
  const currentVersion = pkg.version || "1.0.0";
4800
5597
  const newVersion = semver.inc(currentVersion, options.bump);
@@ -4803,7 +5600,7 @@ async function publishCommand(options) {
4803
5600
  process.exit(1);
4804
5601
  }
4805
5602
  pkg.version = newVersion;
4806
- fs.writeJsonSync(pkgPath, pkg, { spaces: 2 });
5603
+ fs8.writeJsonSync(pkgPath, pkg, { spaces: 2 });
4807
5604
  logger.info(`Bumped version: ${currentVersion} -> ${newVersion}`);
4808
5605
  }
4809
5606
  const version2 = pkg.version || "1.0.0";
@@ -4818,57 +5615,62 @@ async function publishCommand(options) {
4818
5615
  logger.info(`Version: ${version2}`);
4819
5616
  logger.newLine();
4820
5617
  const apiUrl = getApiUrl(env);
4821
- logger.startSpinner("Registering theme...");
4822
- try {
4823
- const regResponse = await authenticatedFetch(
4824
- `${apiUrl}/website-api/themes/register`,
4825
- {
4826
- method: "POST",
4827
- body: JSON.stringify({
4828
- themeId,
4829
- name: pkg.displayName || themeId,
4830
- description: pkg.description || "",
4831
- email: tokens.user.email,
4832
- author: typeof pkg.author === "string" ? pkg.author : pkg.author?.name || tokens.user.name || "",
4833
- category: pkg.onex?.category || "MINIMAL",
4834
- tags: pkg.keywords || [],
4835
- thumbnail_url: pkg.onex?.thumbnail || ""
4836
- })
4837
- },
4838
- env
4839
- );
4840
- const regData = await regResponse.json();
4841
- const regBody = regData.statusCode ? regData.body : regData;
4842
- if (!regResponse.ok) {
4843
- const errMsg = regBody.error || regBody.message || "Registration failed";
4844
- if (!errMsg.includes("already registered")) {
4845
- logger.stopSpinner(false, "Registration failed");
4846
- logger.error(errMsg);
4847
- process.exit(1);
5618
+ if (!options.dryRun) {
5619
+ logger.startSpinner("Registering theme...");
5620
+ try {
5621
+ const regResponse = await authenticatedFetch(
5622
+ `${apiUrl}/website-api/themes/register`,
5623
+ {
5624
+ method: "POST",
5625
+ body: JSON.stringify({
5626
+ themeId,
5627
+ name: pkg.displayName || themeId,
5628
+ description: pkg.description || "",
5629
+ email: tokens.user.email,
5630
+ author: typeof pkg.author === "string" ? pkg.author : pkg.author?.name || tokens.user.name || "",
5631
+ category: pkg.onex?.category || "MINIMAL",
5632
+ tags: pkg.keywords || [],
5633
+ thumbnail_url: pkg.onex?.thumbnail || ""
5634
+ })
5635
+ },
5636
+ env
5637
+ );
5638
+ const regData = await regResponse.json();
5639
+ const regBody = regData.statusCode ? regData.body : regData;
5640
+ if (!regResponse.ok) {
5641
+ const errMsg = regBody.error || regBody.message || "Registration failed";
5642
+ if (!errMsg.includes("already registered")) {
5643
+ logger.stopSpinner(false, "Registration failed");
5644
+ logger.error(errMsg);
5645
+ process.exit(1);
5646
+ }
4848
5647
  }
5648
+ logger.stopSpinner(true, regBody.message || "Theme registered");
5649
+ } catch (error) {
5650
+ logger.stopSpinner(false, "Registration failed");
5651
+ logger.error(
5652
+ error instanceof Error ? error.message : "Connection failed"
5653
+ );
5654
+ process.exit(1);
4849
5655
  }
4850
- logger.stopSpinner(true, regBody.message || "Theme registered");
4851
- } catch (error) {
4852
- logger.stopSpinner(false, "Registration failed");
4853
- logger.error(error instanceof Error ? error.message : "Connection failed");
4854
- process.exit(1);
4855
5656
  }
4856
- logger.startSpinner("Checking version availability...");
4857
- try {
4858
- const checkResponse = await authenticatedFetch(
4859
- `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(version2)}/exists`,
4860
- { method: "GET" },
4861
- env
4862
- );
4863
- const checkData = await checkResponse.json();
4864
- const checkBody = checkData.statusCode ? checkData.body : checkData;
4865
- if (checkBody.exists) {
4866
- logger.stopSpinner(false, "Version already published");
4867
- const patchVer = semver.inc(version2, "patch") || "?";
4868
- const minorVer = semver.inc(version2, "minor") || "?";
4869
- const majorVer = semver.inc(version2, "major") || "?";
4870
- logger.error(
4871
- `
5657
+ if (!options.dryRun) {
5658
+ logger.startSpinner("Checking version availability...");
5659
+ try {
5660
+ const checkResponse = await authenticatedFetch(
5661
+ `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(version2)}/exists`,
5662
+ { method: "GET" },
5663
+ env
5664
+ );
5665
+ const checkData = await checkResponse.json();
5666
+ const checkBody = checkData.statusCode ? checkData.body : checkData;
5667
+ if (checkBody.exists) {
5668
+ logger.stopSpinner(false, "Version already published");
5669
+ const patchVer = semver.inc(version2, "patch") || "?";
5670
+ const minorVer = semver.inc(version2, "minor") || "?";
5671
+ const majorVer = semver.inc(version2, "major") || "?";
5672
+ logger.error(
5673
+ `
4872
5674
  Version ${version2} of "${themeId}" is already published and cannot be overwritten.
4873
5675
 
4874
5676
  To publish a new version:
@@ -4879,12 +5681,16 @@ Or use the --bump flag:
4879
5681
  onexthm publish --bump patch (${version2} -> ${patchVer})
4880
5682
  onexthm publish --bump minor (${version2} -> ${minorVer})
4881
5683
  onexthm publish --bump major (${version2} -> ${majorVer})`
5684
+ );
5685
+ process.exit(1);
5686
+ }
5687
+ logger.stopSpinner(true, `Version ${version2} is available`);
5688
+ } catch (error) {
5689
+ logger.stopSpinner(
5690
+ true,
5691
+ "Version check skipped (endpoint not available)"
4882
5692
  );
4883
- process.exit(1);
4884
5693
  }
4885
- logger.stopSpinner(true, `Version ${version2} is available`);
4886
- } catch (error) {
4887
- logger.stopSpinner(true, "Version check skipped (endpoint not available)");
4888
5694
  }
4889
5695
  logger.startSpinner("Building theme...");
4890
5696
  try {
@@ -4901,7 +5707,19 @@ Or use the --bump flag:
4901
5707
  logger.error(error instanceof Error ? error.message : "Build error");
4902
5708
  process.exit(1);
4903
5709
  }
4904
- const distDir = path9.join(themePath, "dist");
5710
+ const distDir = path13.join(themePath, "dist");
5711
+ const classification = await runSchemaDiffGate(
5712
+ themeId,
5713
+ distDir,
5714
+ env,
5715
+ options
5716
+ );
5717
+ if (options.dryRun) {
5718
+ const exitCode = classification?.highest === "breaking" || classification?.highest === "breaking-severe" || classification?.highest === "breaking-asset" ? 2 : 0;
5719
+ logger.newLine();
5720
+ logger.info(`Dry run complete (exit ${exitCode}). No files uploaded.`);
5721
+ process.exit(exitCode);
5722
+ }
4905
5723
  let assetEntries = [];
4906
5724
  try {
4907
5725
  assetEntries = await scanThemeAssets(distDir);
@@ -4938,8 +5756,8 @@ Or use the --bump flag:
4938
5756
  for (const [originalPath, url] of Object.entries(videoUrls)) {
4939
5757
  assetMap[originalPath] = url;
4940
5758
  }
4941
- const assetMapPath = path9.join(distDir, "asset-map.json");
4942
- await fs.writeFile(assetMapPath, JSON.stringify(assetMap, null, 2));
5759
+ const assetMapPath = path13.join(distDir, "asset-map.json");
5760
+ await fs8.writeFile(assetMapPath, JSON.stringify(assetMap, null, 2));
4943
5761
  } catch (error) {
4944
5762
  logger.error(
4945
5763
  `Failed to write asset-map.json: ${error instanceof Error ? error.message : "unknown"}`
@@ -5014,7 +5832,7 @@ Or use the --bump flag:
5014
5832
  continue;
5015
5833
  }
5016
5834
  try {
5017
- const buf = await fs.promises.readFile(entry.absPath);
5835
+ const buf = await fs8.promises.readFile(entry.absPath);
5018
5836
  const res = await fetch(item.upload_url, {
5019
5837
  method: "PUT",
5020
5838
  headers: {
@@ -5056,12 +5874,12 @@ Or use the --bump flag:
5056
5874
  }
5057
5875
  logger.startSpinner("Uploading bundle...");
5058
5876
  try {
5059
- if (!fs.existsSync(distDir)) {
5877
+ if (!fs8.existsSync(distDir)) {
5060
5878
  logger.stopSpinner(false, "No dist/ directory");
5061
5879
  logger.error("Build the theme first: onexthm build");
5062
5880
  process.exit(1);
5063
5881
  }
5064
- const bundleZipPath = path9.join(themePath, "dist", "bundle.zip");
5882
+ const bundleZipPath = path13.join(themePath, "dist", "bundle.zip");
5065
5883
  await createZip(distDir, bundleZipPath, [
5066
5884
  "bundle.zip",
5067
5885
  "source.zip",
@@ -5070,7 +5888,7 @@ Or use the --bump flag:
5070
5888
  "theme-assets",
5071
5889
  "theme-assets/**"
5072
5890
  ]);
5073
- const bundleBuffer = fs.readFileSync(bundleZipPath);
5891
+ const bundleBuffer = fs8.readFileSync(bundleZipPath);
5074
5892
  const bundleRes = await fetch(bundleUploadUrl, {
5075
5893
  method: "PUT",
5076
5894
  headers: { "Content-Type": "application/zip" },
@@ -5088,7 +5906,7 @@ Or use the --bump flag:
5088
5906
  }
5089
5907
  logger.startSpinner("Uploading source...");
5090
5908
  try {
5091
- const sourceZipPath = path9.join(themePath, "dist", "source.zip");
5909
+ const sourceZipPath = path13.join(themePath, "dist", "source.zip");
5092
5910
  await createZip(themePath, sourceZipPath, [
5093
5911
  "node_modules",
5094
5912
  "dist",
@@ -5096,7 +5914,7 @@ Or use the --bump flag:
5096
5914
  ".env",
5097
5915
  ".env.local"
5098
5916
  ]);
5099
- const sourceBuffer = fs.readFileSync(sourceZipPath);
5917
+ const sourceBuffer = fs8.readFileSync(sourceZipPath);
5100
5918
  const sourceRes = await fetch(sourceUploadUrl, {
5101
5919
  method: "PUT",
5102
5920
  headers: { "Content-Type": "application/zip" },
@@ -5174,9 +5992,9 @@ async function uploadThumbnail(apiUrl, themeId, themePath, distDir, env = "dev")
5174
5992
  let imageBase64 = null;
5175
5993
  let mimeType = "image/png";
5176
5994
  for (const { file, mime } of THUMBNAIL_CANDIDATES) {
5177
- const candidate = path9.join(themePath, file);
5178
- if (fs.existsSync(candidate)) {
5179
- const buf = fs.readFileSync(candidate);
5995
+ const candidate = path13.join(themePath, file);
5996
+ if (fs8.existsSync(candidate)) {
5997
+ const buf = fs8.readFileSync(candidate);
5180
5998
  imageBase64 = `data:${mime};base64,${buf.toString("base64")}`;
5181
5999
  mimeType = mime;
5182
6000
  logger.info(`Using local thumbnail: ${file}`);
@@ -5276,7 +6094,7 @@ async function screenshotHomePage(themePath, distDir) {
5276
6094
  const { compilePreviewRuntime: compilePreviewRuntime2 } = await Promise.resolve().then(() => (init_compile_theme(), compile_theme_exports));
5277
6095
  const { createDevServer: createDevServer2 } = await Promise.resolve().then(() => (init_dev_server(), dev_server_exports));
5278
6096
  const previewRuntimePath = await compilePreviewRuntime2(themePath);
5279
- const themeName = path9.basename(themePath);
6097
+ const themeName = path13.basename(themePath);
5280
6098
  const port = await findFreePort(4500);
5281
6099
  const server = createDevServer2({
5282
6100
  port,
@@ -5333,7 +6151,7 @@ async function findFreePort(start) {
5333
6151
  });
5334
6152
  }
5335
6153
  async function uploadVideoMultipart(apiUrl, themeId, video, env = "dev") {
5336
- const fileName = path9.basename(video.originalPath);
6154
+ const fileName = path13.basename(video.originalPath);
5337
6155
  const videoInitUrl = `${apiUrl}/media/videos/multipart/init`;
5338
6156
  const videoInitBody = {
5339
6157
  file_name: fileName,
@@ -5369,7 +6187,7 @@ async function uploadVideoMultipart(apiUrl, themeId, video, env = "dev") {
5369
6187
  );
5370
6188
  }
5371
6189
  const { upload_id, file_key, chunk_size, chunk_urls } = initBody;
5372
- const fileBuffer = await fs.promises.readFile(video.absPath);
6190
+ const fileBuffer = await fs8.promises.readFile(video.absPath);
5373
6191
  const CHUNK_CONCURRENCY = 4;
5374
6192
  const queue = [...chunk_urls];
5375
6193
  const parts = [];
@@ -5462,6 +6280,91 @@ async function createZip(sourceDir, outputPath, exclude) {
5462
6280
  archive.finalize();
5463
6281
  });
5464
6282
  }
6283
+ async function runSchemaDiffGate(themeId, distDir, env, options) {
6284
+ logger.startSpinner("Fetching prior version for diff...");
6285
+ const { result: prior, reason } = await fetchPriorGateManifests(themeId, env);
6286
+ if (!prior) {
6287
+ if (reason === "no-prior") {
6288
+ logger.stopSpinner(true, "First publish \u2014 no prior version to diff");
6289
+ } else {
6290
+ logger.stopSpinner(true, `Gate skipped (${reason})`);
6291
+ }
6292
+ return null;
6293
+ }
6294
+ logger.stopSpinner(true, `Fetched prior version ${prior.version}`);
6295
+ let currentSchemas;
6296
+ let currentAssets;
6297
+ try {
6298
+ currentSchemas = JSON.parse(
6299
+ await fs8.readFile(path13.join(distDir, "schemas.json"), "utf-8")
6300
+ );
6301
+ } catch (err) {
6302
+ logger.warning(
6303
+ `Gate skipped: dist/schemas.json missing or unreadable (${err instanceof Error ? err.message : "unknown"})`
6304
+ );
6305
+ return null;
6306
+ }
6307
+ try {
6308
+ currentAssets = JSON.parse(
6309
+ await fs8.readFile(path13.join(distDir, "asset-manifest.json"), "utf-8")
6310
+ );
6311
+ } catch {
6312
+ currentAssets = { manifestVersion: 1, assets: [] };
6313
+ }
6314
+ const changes = diffManifests(
6315
+ { schemas: prior.schemas, assets: prior.assets },
6316
+ { schemas: currentSchemas, assets: currentAssets }
6317
+ );
6318
+ const classification = classify(changes);
6319
+ printGateReport(prior.version, classification);
6320
+ if (options.dryRun) return classification;
6321
+ const isBreaking = classification.highest === "breaking" || classification.highest === "breaking-severe" || classification.highest === "breaking-asset";
6322
+ if (isBreaking && !options.force) {
6323
+ logger.error(
6324
+ "\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)."
6325
+ );
6326
+ process.exit(1);
6327
+ }
6328
+ if (classification.highest === "defaults-only" && !options.confirmDefaults) {
6329
+ logger.error(
6330
+ "\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."
6331
+ );
6332
+ process.exit(1);
6333
+ }
6334
+ return classification;
6335
+ }
6336
+ function printGateReport(priorVersion, classification) {
6337
+ logger.newLine();
6338
+ logger.info(`Schema diff vs. v${priorVersion}:`);
6339
+ if (classification.changes.length === 0) {
6340
+ logger.log(" \u2713 Safe \u2014 no schema changes detected");
6341
+ return;
6342
+ }
6343
+ for (const change of classification.changes) {
6344
+ const icon = iconFor(change.kind);
6345
+ logger.log(` ${icon} [${change.kind}] ${change.path} \u2014 ${change.detail}`);
6346
+ }
6347
+ logger.log(
6348
+ `
6349
+ \u2192 Classification: ${classification.highest}. Suggested bump: ${classification.bump}.`
6350
+ );
6351
+ }
6352
+ function iconFor(kind) {
6353
+ switch (kind) {
6354
+ case "safe":
6355
+ case "safe-rename":
6356
+ return "\u2713";
6357
+ case "additive":
6358
+ return "+";
6359
+ case "defaults-only":
6360
+ return "\u26A0";
6361
+ case "breaking":
6362
+ case "breaking-asset":
6363
+ return "\u2717";
6364
+ case "breaking-severe":
6365
+ return "\u2717\u2717";
6366
+ }
6367
+ }
5465
6368
 
5466
6369
  // src/commands/mcp.ts
5467
6370
  init_logger();
@@ -5473,24 +6376,24 @@ var AI_CONTEXT_FILES = [
5473
6376
  ".mcp.json"
5474
6377
  ];
5475
6378
  function resolveTargetDir(opts) {
5476
- return path9.resolve(opts.cwd ?? process.cwd());
6379
+ return path13.resolve(opts.cwd ?? process.cwd());
5477
6380
  }
5478
6381
  function resolveDefaultTemplateDir() {
5479
- return path9.join(getTemplatesDir(), "default");
6382
+ return path13.join(getTemplatesDir(), "default");
5480
6383
  }
5481
6384
  function isThemeDir(dir) {
5482
- return fs.existsSync(path9.join(dir, "theme.config.ts")) || fs.existsSync(path9.join(dir, "theme.config.js"));
6385
+ return fs8.existsSync(path13.join(dir, "theme.config.ts")) || fs8.existsSync(path13.join(dir, "theme.config.js"));
5483
6386
  }
5484
6387
  function inspectFiles(templateDir, targetDir) {
5485
6388
  return AI_CONTEXT_FILES.map((name) => {
5486
- const templatePath = path9.join(templateDir, name);
5487
- const targetPath = path9.join(targetDir, name);
5488
- const exists = fs.existsSync(targetPath);
6389
+ const templatePath = path13.join(templateDir, name);
6390
+ const targetPath = path13.join(targetDir, name);
6391
+ const exists = fs8.existsSync(targetPath);
5489
6392
  let identical = false;
5490
- if (exists && fs.existsSync(templatePath)) {
6393
+ if (exists && fs8.existsSync(templatePath)) {
5491
6394
  try {
5492
- const a = fs.readFileSync(templatePath, "utf-8");
5493
- const b = fs.readFileSync(targetPath, "utf-8");
6395
+ const a = fs8.readFileSync(templatePath, "utf-8");
6396
+ const b = fs8.readFileSync(targetPath, "utf-8");
5494
6397
  identical = a.replace(/\r\n/g, "\n") === b.replace(/\r\n/g, "\n");
5495
6398
  } catch {
5496
6399
  identical = false;
@@ -5540,11 +6443,11 @@ async function mcpSetupCommand(options = {}) {
5540
6443
  }
5541
6444
  }
5542
6445
  for (const s of missing) {
5543
- if (!fs.existsSync(s.templatePath)) {
6446
+ if (!fs8.existsSync(s.templatePath)) {
5544
6447
  logger.warning(` ! ${s.name} not in template \u2014 skipped`);
5545
6448
  continue;
5546
6449
  }
5547
- await fs.copy(s.templatePath, s.targetPath);
6450
+ await fs8.copy(s.templatePath, s.targetPath);
5548
6451
  logger.success(` \u2713 ${s.name}`);
5549
6452
  }
5550
6453
  logger.log("");
@@ -5605,11 +6508,11 @@ async function mcpUpgradeCommand(options = {}) {
5605
6508
  }
5606
6509
  }
5607
6510
  for (const s of toUpgrade) {
5608
- if (!fs.existsSync(s.templatePath)) {
6511
+ if (!fs8.existsSync(s.templatePath)) {
5609
6512
  logger.warning(` ! ${s.name} not in template \u2014 skipped`);
5610
6513
  continue;
5611
6514
  }
5612
- await fs.copy(s.templatePath, s.targetPath, { overwrite: true });
6515
+ await fs8.copy(s.templatePath, s.targetPath, { overwrite: true });
5613
6516
  logger.success(` \u2713 ${s.name}`);
5614
6517
  }
5615
6518
  logger.log("");
@@ -5627,12 +6530,12 @@ async function mcpDoctorCommand(options = {}) {
5627
6530
  return;
5628
6531
  }
5629
6532
  logger.success("theme.config.ts present");
5630
- const mcpJsonPath = path9.join(targetDir, ".mcp.json");
5631
- if (!fs.existsSync(mcpJsonPath)) {
6533
+ const mcpJsonPath = path13.join(targetDir, ".mcp.json");
6534
+ if (!fs8.existsSync(mcpJsonPath)) {
5632
6535
  logger.error(".mcp.json missing \u2014 run `onexthm mcp setup`");
5633
6536
  } else {
5634
6537
  try {
5635
- const mcpJson = JSON.parse(fs.readFileSync(mcpJsonPath, "utf-8"));
6538
+ const mcpJson = JSON.parse(fs8.readFileSync(mcpJsonPath, "utf-8"));
5636
6539
  const servers = mcpJson?.mcpServers ?? {};
5637
6540
  if (servers.onexthm) {
5638
6541
  logger.success(".mcp.json registers `onexthm`");
@@ -5665,8 +6568,8 @@ async function mcpDoctorCommand(options = {}) {
5665
6568
  logger.success(`${s.name} up to date`);
5666
6569
  }
5667
6570
  }
5668
- const registryPath = path9.join(targetDir, "sections-registry.ts");
5669
- if (fs.existsSync(registryPath)) {
6571
+ const registryPath = path13.join(targetDir, "sections-registry.ts");
6572
+ if (fs8.existsSync(registryPath)) {
5670
6573
  logger.success("sections-registry.ts present");
5671
6574
  } else {
5672
6575
  logger.warning(
@@ -5677,24 +6580,27 @@ async function mcpDoctorCommand(options = {}) {
5677
6580
 
5678
6581
  // src/cli.ts
5679
6582
  dotenv.config({
5680
- path: path9.join(process.cwd(), ".env.local"),
6583
+ path: path13.join(process.cwd(), ".env.local"),
5681
6584
  override: true
5682
6585
  });
5683
- dotenv.config({ path: path9.join(process.cwd(), ".env") });
6586
+ dotenv.config({ path: path13.join(process.cwd(), ".env") });
5684
6587
  try {
5685
6588
  const projectRoot = getProjectRoot();
5686
- if (path9.resolve(projectRoot) !== path9.resolve(process.cwd())) {
6589
+ if (path13.resolve(projectRoot) !== path13.resolve(process.cwd())) {
5687
6590
  dotenv.config({
5688
- path: path9.join(projectRoot, ".env.local")
6591
+ path: path13.join(projectRoot, ".env.local")
5689
6592
  });
5690
- dotenv.config({ path: path9.join(projectRoot, ".env") });
6593
+ dotenv.config({ path: path13.join(projectRoot, ".env") });
5691
6594
  }
5692
6595
  } catch {
5693
6596
  }
5694
6597
  dotenv.config({
5695
- path: path9.join(os.homedir(), ".onexthm", ".env"),
6598
+ path: path13.join(os.homedir(), ".onexthm", ".env"),
5696
6599
  quiet: true
5697
6600
  });
6601
+ function envOpt() {
6602
+ return new Option("--env <env>", "Target environment: dev, staging, or prod").choices(["dev", "staging", "prod"]).makeOptionMandatory();
6603
+ }
5698
6604
  var require2 = createRequire(import.meta.url);
5699
6605
  var { version } = require2("../package.json");
5700
6606
  var program = new Command();
@@ -5703,11 +6609,7 @@ program.command("init").description("Create a new OneX theme project").argument(
5703
6609
  "-t, --template <template>",
5704
6610
  "Template to use (default, minimal)",
5705
6611
  "default"
5706
- ).option("--no-install", "Skip installing dependencies").option("--git", "Initialize git repository").option("-y, --yes", "Skip prompts and use defaults").option(
5707
- "--env <env>",
5708
- "Target environment: dev or prod (default: dev)",
5709
- "dev"
5710
- ).action(initCommand);
6612
+ ).option("--no-install", "Skip installing dependencies").option("--git", "Initialize git repository").option("-y, --yes", "Skip prompts and use defaults").addOption(envOpt()).action(initCommand);
5711
6613
  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(
5712
6614
  "-c, --category <category>",
5713
6615
  "Section category (headers, content, footers)"
@@ -5734,36 +6636,16 @@ program.command("download").description("Download a published theme via the webs
5734
6636
  "-v, --version <version>",
5735
6637
  "Theme version (default: latest)",
5736
6638
  "latest"
5737
- ).option(
5738
- "--env <env>",
5739
- "Target environment: dev or prod (default: dev)",
5740
- "dev"
5741
- ).option("-b, --bucket <name>", "[deprecated] ignored").option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
6639
+ ).addOption(envOpt()).option("-b, --bucket <name>", "[deprecated] ignored").option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
5742
6640
  program.command("clone").description("Clone theme source code via the website-api").argument("<theme-name>", "Theme to clone").option(
5743
6641
  "-v, --version <version>",
5744
6642
  "Theme version (default: latest)",
5745
6643
  "latest"
5746
- ).option("-n, --name <name>", "New theme name (skips interactive prompt)").option("-o, --output <dir>", "Output directory").option(
5747
- "--env <env>",
5748
- "Target environment: dev or prod (default: dev)",
5749
- "dev"
5750
- ).option("-b, --bucket <name>", "[deprecated] ignored").option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
6644
+ ).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);
5751
6645
  program.command("config").description("Configure OneX CLI credentials (AWS, API keys)").action(configCommand);
5752
- program.command("login").description("Login to OneX platform").option(
5753
- "--env <env>",
5754
- "Target environment: dev or prod (default: dev)",
5755
- "dev"
5756
- ).action(loginCommand);
5757
- program.command("logout").description("Logout from OneX platform").option(
5758
- "--env <env>",
5759
- "Target environment: dev or prod (default: dev)",
5760
- "dev"
5761
- ).action(logoutCommand);
5762
- program.command("whoami").description("Show current logged-in developer").option(
5763
- "--env <env>",
5764
- "Target environment: dev or prod (default: dev)",
5765
- "dev"
5766
- ).action(whoamiCommand);
6646
+ program.command("login").description("Login to OneX platform").addOption(envOpt()).action(loginCommand);
6647
+ program.command("logout").description("Logout from OneX platform").addOption(envOpt()).action(logoutCommand);
6648
+ program.command("whoami").description("Show current logged-in developer").addOption(envOpt()).action(whoamiCommand);
5767
6649
  var mcpCmd = program.command("mcp").description("Manage MCP server registration and AI-context files");
5768
6650
  mcpCmd.command("setup").description(
5769
6651
  "Install .mcp.json + CLAUDE.md + AGENTS.md + .cursorrules into the current theme"
@@ -5775,10 +6657,15 @@ mcpCmd.command("doctor").description("Diagnose MCP setup in the current theme di
5775
6657
  program.command("publish").description("Build, scan, and publish theme to marketplace (requires login)").option("-t, --theme <path>", "Theme directory path").option(
5776
6658
  "--bump <type>",
5777
6659
  "Auto-bump version before publish (patch|minor|major)"
6660
+ ).addOption(envOpt()).option(
6661
+ "--dry-run",
6662
+ "Build locally and print the schema-diff classification without publishing"
6663
+ ).option(
6664
+ "--confirm-defaults",
6665
+ "Confirm that changed section/block defaults should propagate to live sites"
5778
6666
  ).option(
5779
- "--env <env>",
5780
- "Target environment: dev or prod (default: dev)",
5781
- "dev"
6667
+ "--force",
6668
+ "Publish even when the diff gate detects a breaking change"
5782
6669
  ).action(publishCommand);
5783
6670
  program.configureOutput({
5784
6671
  writeErr: (str) => process.stderr.write(chalk4.red(str))