@onexapis/cli 1.1.1 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +221 -97
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +220 -97
- package/dist/cli.mjs.map +1 -1
- package/dist/index.js +211 -92
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +210 -92
- package/dist/index.mjs.map +1 -1
- package/dist/preview/preview-app.tsx +195 -37
- package/package.json +6 -3
- package/templates/default/.env.example +4 -0
- package/templates/default/package.json.ejs +2 -1
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import React, { useState, useEffect, useRef, useCallback } from "react";
|
|
4
4
|
import { createRoot } from "react-dom/client";
|
|
5
5
|
import * as jsxRuntime from "react/jsx-runtime";
|
|
6
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
6
7
|
|
|
7
8
|
// Core subpath imports — MUST match what setup-theme-globals.ts sets
|
|
8
9
|
// Only import browser-safe subpaths (NOT the root @onexapis/core which pulls server code)
|
|
@@ -11,6 +12,8 @@ import * as coreUtils from "@onexapis/core/utils";
|
|
|
11
12
|
import * as coreContexts from "@onexapis/core/contexts";
|
|
12
13
|
import * as coreComponents from "@onexapis/core/components";
|
|
13
14
|
import * as coreRegistry from "@onexapis/core/registry";
|
|
15
|
+
import * as coreCommerce from "@onexapis/core/commerce";
|
|
16
|
+
import * as coreCommerceHooks from "@onexapis/core/commerce/hooks";
|
|
14
17
|
|
|
15
18
|
// Set React globals
|
|
16
19
|
(globalThis as any).__ONEX_REACT__ = React;
|
|
@@ -26,8 +29,40 @@ import * as coreRegistry from "@onexapis/core/registry";
|
|
|
26
29
|
contexts: coreContexts,
|
|
27
30
|
components: coreComponents,
|
|
28
31
|
registry: coreRegistry,
|
|
32
|
+
commerce: coreCommerce,
|
|
33
|
+
"commerce/hooks": coreCommerceHooks,
|
|
29
34
|
};
|
|
30
35
|
|
|
36
|
+
// Initialize CommerceClient for preview
|
|
37
|
+
// If .env has NEXT_PUBLIC_API_URL + NEXT_PUBLIC_COMPANY_ID, use real API data.
|
|
38
|
+
// Otherwise, use a stub that returns empty data.
|
|
39
|
+
try {
|
|
40
|
+
const { CommerceClient, setCommerceClient } = coreCommerce as any;
|
|
41
|
+
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
|
|
42
|
+
const companyId = process.env.NEXT_PUBLIC_COMPANY_ID;
|
|
43
|
+
|
|
44
|
+
if (apiUrl && companyId && CommerceClient && setCommerceClient) {
|
|
45
|
+
// Real API connection — preview will show actual products/blogs
|
|
46
|
+
setCommerceClient(new CommerceClient({ apiUrl, companyId }));
|
|
47
|
+
} else if (setCommerceClient) {
|
|
48
|
+
// No API config — use stub that returns empty data
|
|
49
|
+
const emptyPagination = { total: 0, page: 1, limit: 10, totalPages: 0, hasNext: false, hasPrev: false };
|
|
50
|
+
const emptyList = { data: [], pagination: emptyPagination };
|
|
51
|
+
setCommerceClient({
|
|
52
|
+
getProducts: () => Promise.resolve(emptyList),
|
|
53
|
+
getProductBySlug: () => Promise.resolve(null),
|
|
54
|
+
getProductById: () => Promise.resolve(null),
|
|
55
|
+
getProductCategories: () => Promise.resolve([]),
|
|
56
|
+
getBlogs: () => Promise.resolve(emptyList),
|
|
57
|
+
getBlogBySlug: () => Promise.resolve(null),
|
|
58
|
+
getBlogById: () => Promise.resolve(null),
|
|
59
|
+
getBlogCategories: () => Promise.resolve([]),
|
|
60
|
+
getSettings: () => Promise.resolve({}),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
// CommerceClient not available — safe to ignore
|
|
65
|
+
}
|
|
31
66
|
|
|
32
67
|
// ===== 2. THEME LOADING =====
|
|
33
68
|
|
|
@@ -39,11 +74,12 @@ interface ThemeExports {
|
|
|
39
74
|
|
|
40
75
|
async function loadThemeBundle(timestamp?: number): Promise<ThemeExports> {
|
|
41
76
|
const cacheBust = timestamp ? `?t=${timestamp}` : `?t=${Date.now()}`;
|
|
42
|
-
const module = await import(
|
|
77
|
+
const module = await import(
|
|
78
|
+
/* @vite-ignore */ `/bundle-entry.js${cacheBust}`
|
|
79
|
+
);
|
|
43
80
|
return module;
|
|
44
81
|
}
|
|
45
82
|
|
|
46
|
-
|
|
47
83
|
// ===== 3. SECTION RESOLUTION (adapted from storefront section-renderer.tsx) =====
|
|
48
84
|
|
|
49
85
|
function getSectionComponent(
|
|
@@ -62,12 +98,18 @@ function getSectionComponent(
|
|
|
62
98
|
const componentKey = toPascalCase(baseName) + toPascalCase(template);
|
|
63
99
|
const schemaKey = toCamelCase(baseName) + "Schema";
|
|
64
100
|
|
|
65
|
-
const Component = themeExports[componentKey] as
|
|
101
|
+
const Component = themeExports[componentKey] as
|
|
102
|
+
| React.ComponentType<any>
|
|
103
|
+
| undefined;
|
|
66
104
|
const schema = themeExports[schemaKey] as any | undefined;
|
|
67
105
|
|
|
68
106
|
if (!Component) return null;
|
|
69
107
|
|
|
70
|
-
return {
|
|
108
|
+
return {
|
|
109
|
+
Component,
|
|
110
|
+
schema,
|
|
111
|
+
template: { id: template, name: capitalize(template) },
|
|
112
|
+
};
|
|
71
113
|
}
|
|
72
114
|
|
|
73
115
|
function capitalize(str: string): string {
|
|
@@ -79,10 +121,12 @@ function toCamelCase(str: string): string {
|
|
|
79
121
|
}
|
|
80
122
|
|
|
81
123
|
function toPascalCase(str: string): string {
|
|
82
|
-
return str
|
|
124
|
+
return str
|
|
125
|
+
.split("-")
|
|
126
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
127
|
+
.join("");
|
|
83
128
|
}
|
|
84
129
|
|
|
85
|
-
|
|
86
130
|
// ===== 4. SECTION DATA ENRICHMENT =====
|
|
87
131
|
|
|
88
132
|
/**
|
|
@@ -111,7 +155,6 @@ function enrichSectionWithDefaults(section: any, schema: any): any {
|
|
|
111
155
|
};
|
|
112
156
|
}
|
|
113
157
|
|
|
114
|
-
|
|
115
158
|
// ===== 5. PAGE CONFIG DISCOVERY =====
|
|
116
159
|
|
|
117
160
|
function discoverPageConfigs(
|
|
@@ -144,12 +187,31 @@ function discoverPageConfigs(
|
|
|
144
187
|
return pages;
|
|
145
188
|
}
|
|
146
189
|
|
|
147
|
-
|
|
148
190
|
// ===== 6. PREVIEW APP COMPONENT =====
|
|
149
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Match URL pathname to a page index.
|
|
194
|
+
* "/" or "/home" → home page, "/showcase" → showcase page, etc.
|
|
195
|
+
*/
|
|
196
|
+
function getInitialPageFromURL(
|
|
197
|
+
pages: Array<{ key: string; label: string; config: any }>
|
|
198
|
+
): number {
|
|
199
|
+
const pathname = window.location.pathname
|
|
200
|
+
.replace(/^\/+|\/+$/g, "")
|
|
201
|
+
.toLowerCase();
|
|
202
|
+
if (!pathname || pathname === "home") return 0; // home is always first after sorting
|
|
203
|
+
|
|
204
|
+
const idx = pages.findIndex((p) => {
|
|
205
|
+
const pageSlug = p.label.toLowerCase().replace(/\s+/g, "-");
|
|
206
|
+
const pageKey = p.key.replace("PageConfig", "").toLowerCase();
|
|
207
|
+
return pageSlug === pathname || pageKey === pathname;
|
|
208
|
+
});
|
|
209
|
+
return idx >= 0 ? idx : -2; // -2 = page not found
|
|
210
|
+
}
|
|
211
|
+
|
|
150
212
|
function PreviewApp() {
|
|
151
213
|
const [themeExports, setThemeExports] = useState<ThemeExports | null>(null);
|
|
152
|
-
const [selectedPage, setSelectedPage] = useState(
|
|
214
|
+
const [selectedPage, setSelectedPage] = useState(-1); // -1 = not yet resolved from URL
|
|
153
215
|
const [wsStatus, setWsStatus] = useState<
|
|
154
216
|
"connected" | "disconnected" | "rebuilding"
|
|
155
217
|
>("disconnected");
|
|
@@ -173,6 +235,16 @@ function PreviewApp() {
|
|
|
173
235
|
loadTheme();
|
|
174
236
|
}, [loadTheme]);
|
|
175
237
|
|
|
238
|
+
// Handle browser back/forward navigation
|
|
239
|
+
useEffect(() => {
|
|
240
|
+
const handlePopState = () => {
|
|
241
|
+
// Reset to -1 to re-resolve from URL
|
|
242
|
+
setSelectedPage(-1);
|
|
243
|
+
};
|
|
244
|
+
window.addEventListener("popstate", handlePopState);
|
|
245
|
+
return () => window.removeEventListener("popstate", handlePopState);
|
|
246
|
+
}, []);
|
|
247
|
+
|
|
176
248
|
// WebSocket connection for hot reload
|
|
177
249
|
useEffect(() => {
|
|
178
250
|
function connect() {
|
|
@@ -232,7 +304,9 @@ function PreviewApp() {
|
|
|
232
304
|
|
|
233
305
|
if (!themeExports) {
|
|
234
306
|
return (
|
|
235
|
-
<div
|
|
307
|
+
<div
|
|
308
|
+
style={{ padding: 40, textAlign: "center", fontFamily: "system-ui" }}
|
|
309
|
+
>
|
|
236
310
|
<p>Loading theme...</p>
|
|
237
311
|
</div>
|
|
238
312
|
);
|
|
@@ -242,14 +316,81 @@ function PreviewApp() {
|
|
|
242
316
|
const pages = discoverPageConfigs(themeExports);
|
|
243
317
|
const themeId = themeExports.themeConfig?.id || "";
|
|
244
318
|
const themePrefix = themeId ? `${themeId}-` : "";
|
|
245
|
-
|
|
319
|
+
|
|
320
|
+
// Resolve initial page from URL path (once pages are discovered)
|
|
321
|
+
const resolvedPage =
|
|
322
|
+
selectedPage === -1 ? getInitialPageFromURL(pages) : selectedPage;
|
|
323
|
+
|
|
324
|
+
// Page not found (-2)
|
|
325
|
+
if (resolvedPage === -2) {
|
|
326
|
+
const pathname = window.location.pathname;
|
|
327
|
+
return (
|
|
328
|
+
<div
|
|
329
|
+
style={{
|
|
330
|
+
padding: "80px 40px",
|
|
331
|
+
textAlign: "center",
|
|
332
|
+
fontFamily: "system-ui",
|
|
333
|
+
}}
|
|
334
|
+
>
|
|
335
|
+
<h1 style={{ fontSize: "4rem", margin: 0, color: "#E11D48" }}>404</h1>
|
|
336
|
+
<p style={{ fontSize: "1.25rem", color: "#333", marginTop: 12 }}>
|
|
337
|
+
Page not found:{" "}
|
|
338
|
+
<code
|
|
339
|
+
style={{
|
|
340
|
+
background: "#f1f5f9",
|
|
341
|
+
padding: "2px 8px",
|
|
342
|
+
borderRadius: 4,
|
|
343
|
+
}}
|
|
344
|
+
>
|
|
345
|
+
{pathname}
|
|
346
|
+
</code>
|
|
347
|
+
</p>
|
|
348
|
+
<p style={{ color: "#888", marginTop: 8 }}>Available pages:</p>
|
|
349
|
+
<div
|
|
350
|
+
style={{
|
|
351
|
+
display: "flex",
|
|
352
|
+
gap: 8,
|
|
353
|
+
justifyContent: "center",
|
|
354
|
+
marginTop: 12,
|
|
355
|
+
}}
|
|
356
|
+
>
|
|
357
|
+
{pages.map((page) => {
|
|
358
|
+
const pageSlug = page.key.replace("PageConfig", "").toLowerCase();
|
|
359
|
+
const pagePath = pageSlug === "home" ? "/" : `/${pageSlug}`;
|
|
360
|
+
return (
|
|
361
|
+
<a
|
|
362
|
+
key={page.key}
|
|
363
|
+
href={pagePath}
|
|
364
|
+
style={{
|
|
365
|
+
padding: "6px 16px",
|
|
366
|
+
borderRadius: 6,
|
|
367
|
+
background: "#E11D48",
|
|
368
|
+
color: "white",
|
|
369
|
+
textDecoration: "none",
|
|
370
|
+
fontSize: 14,
|
|
371
|
+
fontWeight: 500,
|
|
372
|
+
}}
|
|
373
|
+
>
|
|
374
|
+
{page.label}
|
|
375
|
+
</a>
|
|
376
|
+
);
|
|
377
|
+
})}
|
|
378
|
+
</div>
|
|
379
|
+
</div>
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const currentPage = pages[resolvedPage] || pages[0];
|
|
246
384
|
|
|
247
385
|
if (!currentPage) {
|
|
248
386
|
return (
|
|
249
|
-
<div
|
|
387
|
+
<div
|
|
388
|
+
style={{ padding: 40, textAlign: "center", fontFamily: "system-ui" }}
|
|
389
|
+
>
|
|
250
390
|
<p>No page configs found in theme exports.</p>
|
|
251
391
|
<p style={{ color: "#888", marginTop: 8 }}>
|
|
252
|
-
Ensure your bundle-entry.ts exports page configs (e.g.,
|
|
392
|
+
Ensure your bundle-entry.ts exports page configs (e.g.,
|
|
393
|
+
homePageConfig).
|
|
253
394
|
</p>
|
|
254
395
|
</div>
|
|
255
396
|
);
|
|
@@ -290,28 +431,33 @@ function PreviewApp() {
|
|
|
290
431
|
gap: 4,
|
|
291
432
|
}}
|
|
292
433
|
>
|
|
293
|
-
{pages.map((page, i) =>
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
434
|
+
{pages.map((page, i) => {
|
|
435
|
+
const pageSlug = page.key.replace("PageConfig", "").toLowerCase();
|
|
436
|
+
const pagePath = pageSlug === "home" ? "/" : `/${pageSlug}`;
|
|
437
|
+
return (
|
|
438
|
+
<button
|
|
439
|
+
key={page.key}
|
|
440
|
+
onClick={() => {
|
|
441
|
+
setSelectedPage(i);
|
|
442
|
+
window.history.pushState(null, "", pagePath);
|
|
443
|
+
const el = document.getElementById("page-indicator");
|
|
444
|
+
if (el) el.textContent = page.label;
|
|
445
|
+
}}
|
|
446
|
+
style={{
|
|
447
|
+
padding: "4px 12px",
|
|
448
|
+
border: "none",
|
|
449
|
+
borderRadius: 4,
|
|
450
|
+
cursor: "pointer",
|
|
451
|
+
background: i === resolvedPage ? "#E11D48" : "transparent",
|
|
452
|
+
color: i === resolvedPage ? "white" : "#333",
|
|
453
|
+
fontSize: 12,
|
|
454
|
+
fontWeight: 500,
|
|
455
|
+
}}
|
|
456
|
+
>
|
|
457
|
+
{page.label}
|
|
458
|
+
</button>
|
|
459
|
+
);
|
|
460
|
+
})}
|
|
315
461
|
</div>
|
|
316
462
|
)}
|
|
317
463
|
|
|
@@ -361,8 +507,20 @@ function updateToolbar(status: string, label: string) {
|
|
|
361
507
|
}
|
|
362
508
|
}
|
|
363
509
|
|
|
364
|
-
|
|
365
510
|
// ===== 7. MOUNT =====
|
|
366
511
|
|
|
512
|
+
const queryClient = new QueryClient({
|
|
513
|
+
defaultOptions: {
|
|
514
|
+
queries: {
|
|
515
|
+
retry: false,
|
|
516
|
+
refetchOnWindowFocus: false,
|
|
517
|
+
},
|
|
518
|
+
},
|
|
519
|
+
});
|
|
520
|
+
|
|
367
521
|
const root = createRoot(document.getElementById("onex-preview-root")!);
|
|
368
|
-
root.render(
|
|
522
|
+
root.render(
|
|
523
|
+
<QueryClientProvider client={queryClient}>
|
|
524
|
+
<PreviewApp />
|
|
525
|
+
</QueryClientProvider>
|
|
526
|
+
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onexapis/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "CLI tool for OneX theme development - scaffolds themes using @onexapis/core",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -48,20 +48,23 @@
|
|
|
48
48
|
"registry": "https://registry.npmjs.org/"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
+
"@onexapis/core": "workspace:*",
|
|
52
|
+
"@tanstack/react-query": "^5.90.16",
|
|
51
53
|
"@aws-sdk/client-s3": "^3.470.0",
|
|
52
54
|
"adm-zip": "^0.5.16",
|
|
53
55
|
"archiver": "^7.0.1",
|
|
54
56
|
"chalk": "^5.3.0",
|
|
57
|
+
"chokidar": "^4.0.0",
|
|
55
58
|
"commander": "^12.1.0",
|
|
56
59
|
"dotenv": "^17.3.1",
|
|
57
60
|
"ejs": "^3.1.10",
|
|
61
|
+
"esbuild": "^0.25.0",
|
|
58
62
|
"form-data": "^4.0.5",
|
|
59
63
|
"fs-extra": "^11.2.0",
|
|
60
64
|
"glob": "^10.3.10",
|
|
61
65
|
"inquirer": "^9.2.12",
|
|
66
|
+
"jiti": "^2.6.1",
|
|
62
67
|
"node-fetch": "^3.3.2",
|
|
63
|
-
"chokidar": "^4.0.0",
|
|
64
|
-
"esbuild": "^0.25.0",
|
|
65
68
|
"open": "^10.1.0",
|
|
66
69
|
"ora": "^8.0.1",
|
|
67
70
|
"ws": "^8.18.0"
|