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