@numueg/theme-cli 0.2.0 → 0.4.1
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/index.js +455 -240
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -715,8 +715,147 @@ var init_touch_target = __esm({
|
|
|
715
715
|
}
|
|
716
716
|
});
|
|
717
717
|
|
|
718
|
+
// src/lint/rules/_render-path.ts
|
|
719
|
+
function computeBrowserOnlyRanges(source) {
|
|
720
|
+
const lines = source.split("\n");
|
|
721
|
+
const ranges = [];
|
|
722
|
+
for (let i = 0; i < lines.length; i++) {
|
|
723
|
+
if (!CALLBACK_OPENERS.test(lines[i])) continue;
|
|
724
|
+
let depth = 0;
|
|
725
|
+
let started = false;
|
|
726
|
+
let end = i;
|
|
727
|
+
for (let j = i; j < lines.length; j++) {
|
|
728
|
+
for (const ch of lines[j]) {
|
|
729
|
+
if (ch === "(" || ch === "{") {
|
|
730
|
+
depth++;
|
|
731
|
+
started = true;
|
|
732
|
+
} else if (ch === ")" || ch === "}") {
|
|
733
|
+
depth--;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
if (started && depth <= 0) {
|
|
737
|
+
end = j;
|
|
738
|
+
break;
|
|
739
|
+
}
|
|
740
|
+
end = j;
|
|
741
|
+
}
|
|
742
|
+
ranges.push([i + 1, end + 1]);
|
|
743
|
+
}
|
|
744
|
+
return ranges;
|
|
745
|
+
}
|
|
746
|
+
function inRanges(line, ranges) {
|
|
747
|
+
return ranges.some(([start, end]) => line >= start && line <= end);
|
|
748
|
+
}
|
|
749
|
+
function isGuardedNearby(lines, idx) {
|
|
750
|
+
for (let i = Math.max(0, idx - 3); i <= idx; i++) {
|
|
751
|
+
if (/typeof\s+(window|document|navigator|localStorage|sessionStorage)\b/.test(lines[i])) {
|
|
752
|
+
return true;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return false;
|
|
756
|
+
}
|
|
757
|
+
function isCommentLine(line) {
|
|
758
|
+
const t = line.trim();
|
|
759
|
+
return t.startsWith("//") || t.startsWith("*") || t.startsWith("/*");
|
|
760
|
+
}
|
|
761
|
+
function isImportLine(line) {
|
|
762
|
+
return /^\s*import\b/.test(line);
|
|
763
|
+
}
|
|
764
|
+
var CALLBACK_OPENERS;
|
|
765
|
+
var init_render_path = __esm({
|
|
766
|
+
"src/lint/rules/_render-path.ts"() {
|
|
767
|
+
"use strict";
|
|
768
|
+
CALLBACK_OPENERS = /\b(useEffect|useLayoutEffect|useInsertionEffect|addEventListener|removeEventListener|setTimeout|setInterval|requestAnimationFrame|requestIdleCallback)\s*\(|\bon[A-Z]\w*\s*=\s*\{/;
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
// src/lint/rules/ssr-unsafe-globals.ts
|
|
773
|
+
var ssr_unsafe_globals_exports = {};
|
|
774
|
+
__export(ssr_unsafe_globals_exports, {
|
|
775
|
+
default: () => ssr_unsafe_globals_default
|
|
776
|
+
});
|
|
777
|
+
var BROWSER_GLOBAL, rule13, ssr_unsafe_globals_default;
|
|
778
|
+
var init_ssr_unsafe_globals = __esm({
|
|
779
|
+
"src/lint/rules/ssr-unsafe-globals.ts"() {
|
|
780
|
+
"use strict";
|
|
781
|
+
init_render_path();
|
|
782
|
+
BROWSER_GLOBAL = /\b(window|document|navigator)\s*\.|\b(localStorage|sessionStorage)\b/;
|
|
783
|
+
rule13 = {
|
|
784
|
+
id: "ssr-unsafe-globals",
|
|
785
|
+
description: "browser globals in the render path crash or de-SSR server rendering",
|
|
786
|
+
check(ctx) {
|
|
787
|
+
const issues = [];
|
|
788
|
+
for (const [file, source] of Object.entries(ctx.sources)) {
|
|
789
|
+
if (!BROWSER_GLOBAL.test(source)) continue;
|
|
790
|
+
const lines = source.split("\n");
|
|
791
|
+
const browserOnly = computeBrowserOnlyRanges(source);
|
|
792
|
+
for (let i = 0; i < lines.length; i++) {
|
|
793
|
+
const line = lines[i];
|
|
794
|
+
if (!BROWSER_GLOBAL.test(line)) continue;
|
|
795
|
+
if (isCommentLine(line) || isImportLine(line)) continue;
|
|
796
|
+
if (inRanges(i + 1, browserOnly)) continue;
|
|
797
|
+
if (isGuardedNearby(lines, i)) continue;
|
|
798
|
+
issues.push({
|
|
799
|
+
rule: rule13.id,
|
|
800
|
+
severity: "warning",
|
|
801
|
+
file,
|
|
802
|
+
line: i + 1,
|
|
803
|
+
message: "Runs during server render once this theme is SSR-capable \u2014 browser globals don't exist there.",
|
|
804
|
+
suggestion: 'Guard with `typeof window !== "undefined"`, or move the access into useEffect / an event handler.'
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return issues;
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
ssr_unsafe_globals_default = rule13;
|
|
812
|
+
}
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
// src/lint/rules/ssr-nondeterministic-render.ts
|
|
816
|
+
var ssr_nondeterministic_render_exports = {};
|
|
817
|
+
__export(ssr_nondeterministic_render_exports, {
|
|
818
|
+
default: () => ssr_nondeterministic_render_default
|
|
819
|
+
});
|
|
820
|
+
var NONDETERMINISTIC, rule14, ssr_nondeterministic_render_default;
|
|
821
|
+
var init_ssr_nondeterministic_render = __esm({
|
|
822
|
+
"src/lint/rules/ssr-nondeterministic-render.ts"() {
|
|
823
|
+
"use strict";
|
|
824
|
+
init_render_path();
|
|
825
|
+
NONDETERMINISTIC = /\bDate\.now\s*\(|\bnew\s+Date\s*\(\s*\)|\bMath\.random\s*\(|\bcrypto\.randomUUID\s*\(/;
|
|
826
|
+
rule14 = {
|
|
827
|
+
id: "ssr-nondeterministic-render",
|
|
828
|
+
description: "time/random calls in the render path differ between server and client \u2192 hydration mismatch",
|
|
829
|
+
check(ctx) {
|
|
830
|
+
const issues = [];
|
|
831
|
+
for (const [file, source] of Object.entries(ctx.sources)) {
|
|
832
|
+
if (!NONDETERMINISTIC.test(source)) continue;
|
|
833
|
+
const lines = source.split("\n");
|
|
834
|
+
const browserOnly = computeBrowserOnlyRanges(source);
|
|
835
|
+
for (let i = 0; i < lines.length; i++) {
|
|
836
|
+
const line = lines[i];
|
|
837
|
+
if (!NONDETERMINISTIC.test(line)) continue;
|
|
838
|
+
if (isCommentLine(line) || isImportLine(line)) continue;
|
|
839
|
+
if (inRanges(i + 1, browserOnly)) continue;
|
|
840
|
+
issues.push({
|
|
841
|
+
rule: rule14.id,
|
|
842
|
+
severity: "warning",
|
|
843
|
+
file,
|
|
844
|
+
line: i + 1,
|
|
845
|
+
message: "Produces a different value on the server than on the client \u2192 hydration mismatch once this theme is SSR-capable.",
|
|
846
|
+
suggestion: "Read time/randomness inside useEffect and store it in state (initial render uses a deterministic placeholder)."
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
return issues;
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
ssr_nondeterministic_render_default = rule14;
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
|
|
718
857
|
// src/index.ts
|
|
719
|
-
var
|
|
858
|
+
var import_commander18 = require("commander");
|
|
720
859
|
|
|
721
860
|
// src/commands/init.ts
|
|
722
861
|
var import_commander = require("commander");
|
|
@@ -912,36 +1051,23 @@ export default function Hero({ settings }: SectionProps) {
|
|
|
912
1051
|
);
|
|
913
1052
|
fs.writeFileSync(
|
|
914
1053
|
path.join(dir, "src/main.tsx"),
|
|
915
|
-
`import {
|
|
916
|
-
import
|
|
917
|
-
|
|
918
|
-
Page,
|
|
919
|
-
Product,
|
|
920
|
-
Collection,
|
|
921
|
-
Store,
|
|
922
|
-
} from "@numueg/theme-sdk";
|
|
923
|
-
import {
|
|
924
|
-
usePage,
|
|
925
|
-
PageContext,
|
|
926
|
-
ProductProvider,
|
|
927
|
-
CollectionProvider,
|
|
928
|
-
NuMuProvider,
|
|
929
|
-
} from "@numueg/theme-sdk";
|
|
1054
|
+
`import type { ComponentType } from "react";
|
|
1055
|
+
import { defineThemeEntry } from "@numueg/theme-sdk";
|
|
1056
|
+
import type { ThemeSettingsV3 } from "@numueg/theme-sdk";
|
|
930
1057
|
import Hero from "./sections/Hero";
|
|
931
1058
|
|
|
1059
|
+
const SECTION_REGISTRY: Record<string, ComponentType<any>> = {
|
|
1060
|
+
hero: Hero,
|
|
1061
|
+
};
|
|
1062
|
+
|
|
932
1063
|
interface ThemeProps {
|
|
933
1064
|
themeSettings: ThemeSettingsV3;
|
|
1065
|
+
currentTemplate: string;
|
|
934
1066
|
}
|
|
935
1067
|
|
|
936
|
-
|
|
937
|
-
hero: Hero,
|
|
938
|
-
};
|
|
939
|
-
|
|
940
|
-
export default function Theme({ themeSettings }: ThemeProps) {
|
|
941
|
-
const page = usePage();
|
|
942
|
-
const pageType = page?.type || "home";
|
|
1068
|
+
export default function Theme({ themeSettings, currentTemplate }: ThemeProps) {
|
|
943
1069
|
const template =
|
|
944
|
-
themeSettings.templates?.[
|
|
1070
|
+
themeSettings.templates?.[currentTemplate] || themeSettings.templates?.home;
|
|
945
1071
|
|
|
946
1072
|
return (
|
|
947
1073
|
<main>
|
|
@@ -963,106 +1089,15 @@ export default function Theme({ themeSettings }: ThemeProps) {
|
|
|
963
1089
|
);
|
|
964
1090
|
}
|
|
965
1091
|
|
|
966
|
-
//
|
|
967
|
-
//
|
|
968
|
-
//
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
storeData?: Store;
|
|
973
|
-
page?: Page & { data?: Record<string, unknown> };
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
// Fallback Store used only when host didn't pass storeData. NuMuProvider
|
|
977
|
-
// hard-requires Store.currency for Intl.NumberFormat \u2014 synthesize one
|
|
978
|
-
// instead of crashing.
|
|
979
|
-
const FALLBACK_STORE: Store = {
|
|
980
|
-
id: "",
|
|
981
|
-
name: "",
|
|
982
|
-
slug: "",
|
|
983
|
-
currency: "USD",
|
|
984
|
-
default_language: "en",
|
|
985
|
-
use_nextjs_storefront: true,
|
|
986
|
-
};
|
|
987
|
-
|
|
988
|
-
function ThemeWithContext({ themeSettings, storeData, page }: MountProps) {
|
|
989
|
-
// Storefront returns snake_case \`default_currency\` / \`default_language\`
|
|
990
|
-
// but SDK Store expects \`currency\`. Map both shapes so NuMuProvider's
|
|
991
|
-
// Intl.NumberFormat doesn't blow up on an empty currency code.
|
|
992
|
-
const raw = (storeData ?? FALLBACK_STORE) as Record<string, unknown> & Store;
|
|
993
|
-
const store: Store = {
|
|
994
|
-
...raw,
|
|
995
|
-
currency:
|
|
996
|
-
(raw.currency as string) ||
|
|
997
|
-
(raw.default_currency as string) ||
|
|
998
|
-
FALLBACK_STORE.currency,
|
|
999
|
-
default_language:
|
|
1000
|
-
(raw.default_language as string) || FALLBACK_STORE.default_language,
|
|
1001
|
-
};
|
|
1002
|
-
|
|
1003
|
-
const product = page?.data?.product as Product | undefined;
|
|
1004
|
-
const collection = page?.data?.collection as Collection | undefined;
|
|
1005
|
-
const pageValue: Page = page
|
|
1006
|
-
? {
|
|
1007
|
-
type: page.type,
|
|
1008
|
-
title: page.title || "",
|
|
1009
|
-
handle: page.handle,
|
|
1010
|
-
data: page.data,
|
|
1011
|
-
}
|
|
1012
|
-
: { type: "home", title: "" };
|
|
1013
|
-
|
|
1014
|
-
let tree = <Theme themeSettings={themeSettings} />;
|
|
1015
|
-
if (collection)
|
|
1016
|
-
tree = <CollectionProvider collection={collection}>{tree}</CollectionProvider>;
|
|
1017
|
-
if (product)
|
|
1018
|
-
tree = <ProductProvider product={product}>{tree}</ProductProvider>;
|
|
1019
|
-
|
|
1020
|
-
return (
|
|
1021
|
-
<NuMuProvider store={store} themeSettings={themeSettings} locale={store.default_language}>
|
|
1022
|
-
<PageContext.Provider value={pageValue}>{tree}</PageContext.Provider>
|
|
1023
|
-
</NuMuProvider>
|
|
1024
|
-
);
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
// The host (\`ByotThemeBoundary\`) prefers the object-shape return:
|
|
1028
|
-
// { unmount, update }
|
|
1029
|
-
// When \`update\` is present, the customizer forwards prop-only changes
|
|
1030
|
-
// (themeSettings / storeData / page) into the SAME React tree without
|
|
1031
|
-
// re-importing the bundle. Without \`update\`, every settings tweak
|
|
1032
|
-
// would trigger a full remount \u2014 fine, but visibly slower.
|
|
1033
|
-
export interface MountHandle {
|
|
1034
|
-
unmount: () => void;
|
|
1035
|
-
update: (next: MountProps) => void;
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
export function mount(el: HTMLElement, props: MountProps): MountHandle {
|
|
1039
|
-
const root: Root = createRoot(el);
|
|
1040
|
-
let current: MountProps = props;
|
|
1041
|
-
root.render(<ThemeWithContext {...current} />);
|
|
1042
|
-
|
|
1043
|
-
// Live preview: the storefront's PreviewBridge forwards customizer edits
|
|
1044
|
-
// as \`numu:theme-update\` window events. Re-render with the new payload.
|
|
1045
|
-
// Also covered by the \`update\` method below \u2014 both paths funnel into
|
|
1046
|
-
// the same root.render() so they can't drift.
|
|
1047
|
-
function handleUpdate(e: Event) {
|
|
1048
|
-
const detail = (e as CustomEvent<ThemeSettingsV3>).detail;
|
|
1049
|
-
if (!detail || typeof detail !== "object") return;
|
|
1050
|
-
current = { ...current, themeSettings: detail };
|
|
1051
|
-
root.render(<ThemeWithContext {...current} />);
|
|
1052
|
-
}
|
|
1053
|
-
window.addEventListener("numu:theme-update", handleUpdate);
|
|
1092
|
+
// SSR rule of thumb: everything rendered above must be deterministic and
|
|
1093
|
+
// browser-free (no window/document/Date.now in the render path \u2014 put those
|
|
1094
|
+
// in useEffect). \`numu-theme lint\` checks this for you.
|
|
1095
|
+
const entry = defineThemeEntry(({ themeSettings, currentTemplate }) => (
|
|
1096
|
+
<Theme themeSettings={themeSettings} currentTemplate={currentTemplate} />
|
|
1097
|
+
));
|
|
1054
1098
|
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
window.removeEventListener("numu:theme-update", handleUpdate);
|
|
1058
|
-
root.unmount();
|
|
1059
|
-
},
|
|
1060
|
-
update: (next: MountProps) => {
|
|
1061
|
-
current = next;
|
|
1062
|
-
root.render(<ThemeWithContext {...current} />);
|
|
1063
|
-
},
|
|
1064
|
-
};
|
|
1065
|
-
}
|
|
1099
|
+
export const mount = entry.mount;
|
|
1100
|
+
export const createApp = entry.createApp;
|
|
1066
1101
|
`
|
|
1067
1102
|
);
|
|
1068
1103
|
fs.writeFileSync(
|
|
@@ -1102,7 +1137,10 @@ const placeholder = {
|
|
|
1102
1137
|
};
|
|
1103
1138
|
|
|
1104
1139
|
const root = document.getElementById("root");
|
|
1105
|
-
if (root)
|
|
1140
|
+
if (root)
|
|
1141
|
+
createRoot(root).render(
|
|
1142
|
+
<Theme themeSettings={placeholder as any} currentTemplate="home" />,
|
|
1143
|
+
);
|
|
1106
1144
|
`
|
|
1107
1145
|
);
|
|
1108
1146
|
fs.writeFileSync(
|
|
@@ -1159,12 +1197,14 @@ export default defineConfig({
|
|
|
1159
1197
|
scripts: {
|
|
1160
1198
|
dev: "numu-theme dev",
|
|
1161
1199
|
build: "numu-theme build",
|
|
1162
|
-
check: "numu-theme check"
|
|
1200
|
+
check: "numu-theme check",
|
|
1201
|
+
// Render-verify every template against fixtures (Phase 2).
|
|
1202
|
+
verify: "numu-theme verify"
|
|
1163
1203
|
},
|
|
1164
|
-
dependencies: { "@numueg/theme-sdk": "^0.
|
|
1204
|
+
dependencies: { "@numueg/theme-sdk": "^0.4.0" },
|
|
1165
1205
|
devDependencies: {
|
|
1166
|
-
"@numueg/theme-cli": "^0.
|
|
1167
|
-
"@numueg/theme-plugin": "^0.
|
|
1206
|
+
"@numueg/theme-cli": "^0.4.0",
|
|
1207
|
+
"@numueg/theme-plugin": "^0.4.0",
|
|
1168
1208
|
"@vitejs/plugin-react": "^4.3.0",
|
|
1169
1209
|
vite: "^6.0.0",
|
|
1170
1210
|
typescript: "^5.8.0",
|
|
@@ -1274,12 +1314,12 @@ function assertHttpsOrLocalhost(urlStr) {
|
|
|
1274
1314
|
}
|
|
1275
1315
|
throw new Error(`Unsupported protocol: ${url.protocol}`);
|
|
1276
1316
|
}
|
|
1277
|
-
async function apiRequest(method,
|
|
1317
|
+
async function apiRequest(method, path16, body) {
|
|
1278
1318
|
const config = loadConfig();
|
|
1279
|
-
const url = assertHttpsOrLocalhost(`${config.api_url}${
|
|
1319
|
+
const url = assertHttpsOrLocalhost(`${config.api_url}${path16}`);
|
|
1280
1320
|
const isHttps = url.protocol === "https:";
|
|
1281
1321
|
const transport = isHttps ? https : http;
|
|
1282
|
-
return new Promise((
|
|
1322
|
+
return new Promise((resolve10, reject) => {
|
|
1283
1323
|
const headers = {};
|
|
1284
1324
|
if (config.token) headers["Authorization"] = `Bearer ${config.token}`;
|
|
1285
1325
|
let postData;
|
|
@@ -1301,7 +1341,7 @@ async function apiRequest(method, path15, body) {
|
|
|
1301
1341
|
res.on("data", (chunk) => data += chunk);
|
|
1302
1342
|
res.on("end", () => {
|
|
1303
1343
|
const { unwrapped, raw } = parseBody(data);
|
|
1304
|
-
|
|
1344
|
+
resolve10({
|
|
1305
1345
|
status: res.statusCode ?? 0,
|
|
1306
1346
|
data: unwrapped,
|
|
1307
1347
|
raw
|
|
@@ -1334,7 +1374,7 @@ Content-Type: application/zip\r
|
|
|
1334
1374
|
const fullBody = Buffer.concat([head, fileBuffer, tail]);
|
|
1335
1375
|
const isHttps = url.protocol === "https:";
|
|
1336
1376
|
const transport = isHttps ? https : http;
|
|
1337
|
-
return new Promise((
|
|
1377
|
+
return new Promise((resolve10, reject) => {
|
|
1338
1378
|
const headers = {
|
|
1339
1379
|
"Content-Type": `multipart/form-data; boundary=${boundary}`,
|
|
1340
1380
|
"Content-Length": String(fullBody.byteLength)
|
|
@@ -1357,7 +1397,7 @@ Content-Type: application/zip\r
|
|
|
1357
1397
|
res.on("data", (chunk) => data += chunk);
|
|
1358
1398
|
res.on("end", () => {
|
|
1359
1399
|
const { unwrapped, raw } = parseBody(data);
|
|
1360
|
-
|
|
1400
|
+
resolve10({
|
|
1361
1401
|
status: res.statusCode ?? 0,
|
|
1362
1402
|
data: unwrapped,
|
|
1363
1403
|
raw
|
|
@@ -1532,6 +1572,48 @@ function validateTheme(themeDir) {
|
|
|
1532
1572
|
if (!themeJson.presets || Object.keys(themeJson.presets).length === 0) {
|
|
1533
1573
|
warnings.push("theme.json has no presets \u2014 merchants will start with an empty page");
|
|
1534
1574
|
}
|
|
1575
|
+
const REQUIRED_TEMPLATES = [
|
|
1576
|
+
"home",
|
|
1577
|
+
"product",
|
|
1578
|
+
"collection",
|
|
1579
|
+
"cart",
|
|
1580
|
+
"page",
|
|
1581
|
+
"search",
|
|
1582
|
+
"404"
|
|
1583
|
+
];
|
|
1584
|
+
if (themeJson.presets && typeof themeJson.presets === "object") {
|
|
1585
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
1586
|
+
for (const bucket of [
|
|
1587
|
+
themeJson.presets.templates,
|
|
1588
|
+
themeJson.presets.section_groups
|
|
1589
|
+
]) {
|
|
1590
|
+
if (!bucket || typeof bucket !== "object") continue;
|
|
1591
|
+
for (const entry of Object.values(bucket)) {
|
|
1592
|
+
const sections = entry?.sections;
|
|
1593
|
+
const instances = Array.isArray(sections) ? sections : sections && typeof sections === "object" ? Object.values(sections) : [];
|
|
1594
|
+
for (const inst of instances) {
|
|
1595
|
+
if (inst && typeof inst.type === "string") {
|
|
1596
|
+
referenced.add(inst.type.toLowerCase());
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
for (const type of referenced) {
|
|
1602
|
+
if (!schemaNames.has(type)) {
|
|
1603
|
+
errors.push(
|
|
1604
|
+
`theme.json preset references section type "${type}" but there is no schemas/sections/${type}.json \u2014 the storefront drops unknown sections at render.`
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
const templates = themeJson.presets.templates && typeof themeJson.presets.templates === "object" ? themeJson.presets.templates : {};
|
|
1609
|
+
for (const tpl of REQUIRED_TEMPLATES) {
|
|
1610
|
+
if (!(tpl in templates)) {
|
|
1611
|
+
warnings.push(
|
|
1612
|
+
`theme.json has no preset for the "${tpl}" template \u2014 the storefront will use its built-in fallback.`
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1535
1617
|
return { valid: errors.length === 0, errors, warnings };
|
|
1536
1618
|
}
|
|
1537
1619
|
|
|
@@ -1641,7 +1723,9 @@ async function runAllRules(themeDir, options) {
|
|
|
1641
1723
|
() => Promise.resolve().then(() => (init_use_app_no_availability_check(), use_app_no_availability_check_exports)),
|
|
1642
1724
|
() => Promise.resolve().then(() => (init_manifest_required_fields(), manifest_required_fields_exports)),
|
|
1643
1725
|
() => Promise.resolve().then(() => (init_contrast_hint(), contrast_hint_exports)),
|
|
1644
|
-
() => Promise.resolve().then(() => (init_touch_target(), touch_target_exports))
|
|
1726
|
+
() => Promise.resolve().then(() => (init_touch_target(), touch_target_exports)),
|
|
1727
|
+
() => Promise.resolve().then(() => (init_ssr_unsafe_globals(), ssr_unsafe_globals_exports)),
|
|
1728
|
+
() => Promise.resolve().then(() => (init_ssr_nondeterministic_render(), ssr_nondeterministic_render_exports))
|
|
1645
1729
|
];
|
|
1646
1730
|
const issues = [];
|
|
1647
1731
|
for (const load of ruleLoaders) {
|
|
@@ -1656,14 +1740,14 @@ async function runAllRules(themeDir, options) {
|
|
|
1656
1740
|
});
|
|
1657
1741
|
continue;
|
|
1658
1742
|
}
|
|
1659
|
-
const
|
|
1660
|
-
if (options.enabledRules && !options.enabledRules.has(
|
|
1743
|
+
const rule15 = mod.default;
|
|
1744
|
+
if (options.enabledRules && !options.enabledRules.has(rule15.id)) continue;
|
|
1661
1745
|
try {
|
|
1662
|
-
const ruleIssues = await
|
|
1746
|
+
const ruleIssues = await rule15.check(ctx);
|
|
1663
1747
|
for (const issue of ruleIssues) issues.push(issue);
|
|
1664
1748
|
} catch (e) {
|
|
1665
1749
|
issues.push({
|
|
1666
|
-
rule:
|
|
1750
|
+
rule: rule15.id,
|
|
1667
1751
|
severity: "warning",
|
|
1668
1752
|
message: `Rule crashed: ${e.message}`
|
|
1669
1753
|
});
|
|
@@ -1887,14 +1971,102 @@ Bundle size: ${sizeMB} MB`);
|
|
|
1887
1971
|
"\u26A0 Bundle exceeds 5MB limit \u2014 optimize before submitting to marketplace"
|
|
1888
1972
|
);
|
|
1889
1973
|
}
|
|
1974
|
+
const serverBundle = path6.join(distDir, "theme.server.js");
|
|
1975
|
+
if (fs7.existsSync(serverBundle)) {
|
|
1976
|
+
const kb = (fs7.statSync(serverBundle).size / 1024).toFixed(1);
|
|
1977
|
+
console.log(`SSR bundle: theme.server.js (${kb} KB) \u2014 hosts will server-render this theme`);
|
|
1978
|
+
} else {
|
|
1979
|
+
console.log(
|
|
1980
|
+
"SSR bundle: not emitted \u2014 theme ships client-only. Export `createApp` via defineThemeEntry (SDK \u2265 0.3) and build with federate:true to enable server rendering."
|
|
1981
|
+
);
|
|
1982
|
+
}
|
|
1890
1983
|
}
|
|
1891
1984
|
console.log("\n\u2713 Build complete");
|
|
1892
1985
|
}
|
|
1893
1986
|
);
|
|
1894
1987
|
|
|
1895
|
-
// src/commands/
|
|
1988
|
+
// src/commands/verify.ts
|
|
1896
1989
|
var import_commander6 = require("commander");
|
|
1990
|
+
var import_child_process5 = require("child_process");
|
|
1991
|
+
var import_fs = require("fs");
|
|
1992
|
+
var import_module = require("module");
|
|
1897
1993
|
var path7 = __toESM(require("path"));
|
|
1994
|
+
var import_url = require("url");
|
|
1995
|
+
var dynamicImport = new Function("u", "return import(u)");
|
|
1996
|
+
var verifyCommand = new import_commander6.Command("verify").description(
|
|
1997
|
+
"Server-render every template against fixtures to catch runtime crashes"
|
|
1998
|
+
).option("-d, --dir <directory>", "Theme directory", ".").option("--locale <code>", "Render under a locale (e.g. `ar` for RTL)").option("--no-build", "Verify the existing dist/ without rebuilding").action(
|
|
1999
|
+
async (options) => {
|
|
2000
|
+
const dir = path7.resolve(process.cwd(), options.dir);
|
|
2001
|
+
if (options.build !== false) {
|
|
2002
|
+
console.log("Building theme (SSR bundle)\u2026");
|
|
2003
|
+
try {
|
|
2004
|
+
(0, import_child_process5.execSync)("npx vite build", { cwd: dir, stdio: "inherit" });
|
|
2005
|
+
} catch {
|
|
2006
|
+
console.error("Build failed");
|
|
2007
|
+
process.exit(1);
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
const serverBundle = path7.join(dir, "dist", "theme.server.js");
|
|
2011
|
+
if (!(0, import_fs.existsSync)(serverBundle)) {
|
|
2012
|
+
console.error(
|
|
2013
|
+
"No dist/theme.server.js \u2014 this theme is client-only and can't be render-verified. Export `createApp` via defineThemeEntry() and build with federate:true (SDK >= 0.3)."
|
|
2014
|
+
);
|
|
2015
|
+
process.exit(1);
|
|
2016
|
+
}
|
|
2017
|
+
const requireFromTheme = (0, import_module.createRequire)(path7.join(dir, "package.json"));
|
|
2018
|
+
let harness;
|
|
2019
|
+
try {
|
|
2020
|
+
const verifyEntry = requireFromTheme.resolve("@numueg/theme-sdk/verify");
|
|
2021
|
+
harness = await dynamicImport(
|
|
2022
|
+
(0, import_url.pathToFileURL)(verifyEntry).href
|
|
2023
|
+
);
|
|
2024
|
+
} catch {
|
|
2025
|
+
console.error(
|
|
2026
|
+
"This theme's @numueg/theme-sdk has no `/verify` entry \u2014 upgrade @numueg/theme-sdk to a version that ships the render harness."
|
|
2027
|
+
);
|
|
2028
|
+
process.exit(1);
|
|
2029
|
+
}
|
|
2030
|
+
let serverModule;
|
|
2031
|
+
try {
|
|
2032
|
+
serverModule = await dynamicImport((0, import_url.pathToFileURL)(serverBundle).href);
|
|
2033
|
+
} catch (e) {
|
|
2034
|
+
console.error(
|
|
2035
|
+
"Failed to load dist/theme.server.js:",
|
|
2036
|
+
e instanceof Error ? e.message : String(e)
|
|
2037
|
+
);
|
|
2038
|
+
process.exit(1);
|
|
2039
|
+
}
|
|
2040
|
+
const def = serverModule.default;
|
|
2041
|
+
const mod = {
|
|
2042
|
+
createApp: serverModule.createApp ?? def?.createApp
|
|
2043
|
+
};
|
|
2044
|
+
const result = await harness.verifyThemeRender(mod, {
|
|
2045
|
+
locale: options.locale
|
|
2046
|
+
});
|
|
2047
|
+
console.log("\nRender verification:");
|
|
2048
|
+
for (const r of result.results) {
|
|
2049
|
+
const mark = r.ok ? "\u2713" : "\u2718";
|
|
2050
|
+
console.log(
|
|
2051
|
+
` ${mark} ${r.template}${r.ok ? ` (${r.htmlLength} chars)` : ""}`
|
|
2052
|
+
);
|
|
2053
|
+
if (!r.ok && r.error) {
|
|
2054
|
+
console.log(` ${String(r.error).split("\n")[0]}`);
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
if (!result.ok) {
|
|
2058
|
+
console.error(
|
|
2059
|
+
"\n\u2718 Render verification failed \u2014 fix the crashing templates above."
|
|
2060
|
+
);
|
|
2061
|
+
process.exit(1);
|
|
2062
|
+
}
|
|
2063
|
+
console.log("\n\u2713 All templates render against fixture data.");
|
|
2064
|
+
}
|
|
2065
|
+
);
|
|
2066
|
+
|
|
2067
|
+
// src/commands/push.ts
|
|
2068
|
+
var import_commander7 = require("commander");
|
|
2069
|
+
var path8 = __toESM(require("path"));
|
|
1898
2070
|
var os2 = __toESM(require("os"));
|
|
1899
2071
|
|
|
1900
2072
|
// src/utils/zipper.ts
|
|
@@ -1902,10 +2074,10 @@ var fs8 = __toESM(require("fs"));
|
|
|
1902
2074
|
var import_archiver = __toESM(require("archiver"));
|
|
1903
2075
|
async function zipDirectory(sourceDir, outputPath, optsOrLegacyExcludes = {}) {
|
|
1904
2076
|
const opts = Array.isArray(optsOrLegacyExcludes) ? { excludePatterns: optsOrLegacyExcludes } : optsOrLegacyExcludes;
|
|
1905
|
-
return new Promise((
|
|
2077
|
+
return new Promise((resolve10, reject) => {
|
|
1906
2078
|
const output = fs8.createWriteStream(outputPath);
|
|
1907
2079
|
const archive = (0, import_archiver.default)("zip", { zlib: { level: 9 } });
|
|
1908
|
-
output.on("close", () =>
|
|
2080
|
+
output.on("close", () => resolve10(outputPath));
|
|
1909
2081
|
archive.on("error", reject);
|
|
1910
2082
|
archive.pipe(output);
|
|
1911
2083
|
const defaultExcludes = [
|
|
@@ -1927,7 +2099,7 @@ async function zipDirectory(sourceDir, outputPath, optsOrLegacyExcludes = {}) {
|
|
|
1927
2099
|
}
|
|
1928
2100
|
|
|
1929
2101
|
// src/commands/push.ts
|
|
1930
|
-
var pushCommand = new
|
|
2102
|
+
var pushCommand = new import_commander7.Command("push").description("Upload built theme to your developer account for testing").option("-d, --dir <directory>", "Theme directory", ".").action(async (options) => {
|
|
1931
2103
|
const config = loadConfig();
|
|
1932
2104
|
if (!config.token) {
|
|
1933
2105
|
console.error("Not logged in. Run: numu-theme login");
|
|
@@ -1940,7 +2112,7 @@ var pushCommand = new import_commander6.Command("push").description("Upload buil
|
|
|
1940
2112
|
process.exit(1);
|
|
1941
2113
|
}
|
|
1942
2114
|
console.log("Packaging theme...");
|
|
1943
|
-
const zipPath =
|
|
2115
|
+
const zipPath = path8.join(os2.tmpdir(), `numu-theme-${Date.now()}.zip`);
|
|
1944
2116
|
await zipDirectory(options.dir, zipPath);
|
|
1945
2117
|
console.log("Uploading...");
|
|
1946
2118
|
const res = await uploadFile("/themes/upload", zipPath);
|
|
@@ -1961,11 +2133,11 @@ Push failed (${res.status}): ${JSON.stringify(res.data)}`);
|
|
|
1961
2133
|
});
|
|
1962
2134
|
|
|
1963
2135
|
// src/commands/submit.ts
|
|
1964
|
-
var
|
|
1965
|
-
var
|
|
2136
|
+
var import_commander8 = require("commander");
|
|
2137
|
+
var path9 = __toESM(require("path"));
|
|
1966
2138
|
var os3 = __toESM(require("os"));
|
|
1967
2139
|
var fs9 = __toESM(require("fs"));
|
|
1968
|
-
var submitCommand = new
|
|
2140
|
+
var submitCommand = new import_commander8.Command("submit").description("Submit theme to the NUMU Marketplace for review").option("-d, --dir <directory>", "Theme directory", ".").requiredOption(
|
|
1969
2141
|
"-t, --theme-id <theme_id>",
|
|
1970
2142
|
"Marketplace theme listing UUID (create via dashboard first)"
|
|
1971
2143
|
).option("-n, --notes <notes>", "Release notes for this version").action(
|
|
@@ -1988,7 +2160,7 @@ var submitCommand = new import_commander7.Command("submit").description("Submit
|
|
|
1988
2160
|
console.log("\nWarnings:");
|
|
1989
2161
|
result.warnings.forEach((w) => console.log(` \u26A0 ${w}`));
|
|
1990
2162
|
}
|
|
1991
|
-
const themeJsonPath =
|
|
2163
|
+
const themeJsonPath = path9.join(options.dir, "theme.json");
|
|
1992
2164
|
let version;
|
|
1993
2165
|
try {
|
|
1994
2166
|
const tj = JSON.parse(fs9.readFileSync(themeJsonPath, "utf-8"));
|
|
@@ -2002,7 +2174,7 @@ var submitCommand = new import_commander7.Command("submit").description("Submit
|
|
|
2002
2174
|
process.exit(1);
|
|
2003
2175
|
}
|
|
2004
2176
|
console.log("\nPackaging theme source...");
|
|
2005
|
-
const zipPath =
|
|
2177
|
+
const zipPath = path9.join(
|
|
2006
2178
|
os3.tmpdir(),
|
|
2007
2179
|
`numu-theme-submit-${Date.now()}.zip`
|
|
2008
2180
|
);
|
|
@@ -2055,12 +2227,12 @@ Submission failed (${submitRes.status}): ${JSON.stringify(submitRes.data)}`
|
|
|
2055
2227
|
);
|
|
2056
2228
|
|
|
2057
2229
|
// src/commands/install.ts
|
|
2058
|
-
var
|
|
2059
|
-
var
|
|
2060
|
-
var
|
|
2230
|
+
var import_commander9 = require("commander");
|
|
2231
|
+
var import_child_process6 = require("child_process");
|
|
2232
|
+
var path10 = __toESM(require("path"));
|
|
2061
2233
|
var os4 = __toESM(require("os"));
|
|
2062
2234
|
var fs10 = __toESM(require("fs"));
|
|
2063
|
-
var installCommand = new
|
|
2235
|
+
var installCommand = new import_commander9.Command("install").description(
|
|
2064
2236
|
"Build + upload + install a theme directly on a store you own (bypasses marketplace review)"
|
|
2065
2237
|
).option("-d, --dir <directory>", "Theme directory", ".").requiredOption(
|
|
2066
2238
|
"-t, --theme-id <theme_id>",
|
|
@@ -2098,7 +2270,7 @@ var installCommand = new import_commander8.Command("install").description(
|
|
|
2098
2270
|
process.exit(1);
|
|
2099
2271
|
}
|
|
2100
2272
|
result.warnings.forEach((w) => console.log(` \u26A0 ${w}`));
|
|
2101
|
-
const themeJsonPath =
|
|
2273
|
+
const themeJsonPath = path10.join(options.dir, "theme.json");
|
|
2102
2274
|
let baseVersion;
|
|
2103
2275
|
try {
|
|
2104
2276
|
const tj = JSON.parse(fs10.readFileSync(themeJsonPath, "utf-8"));
|
|
@@ -2117,7 +2289,7 @@ var installCommand = new import_commander8.Command("install").description(
|
|
|
2117
2289
|
console.log(` install tag: ${version}`);
|
|
2118
2290
|
console.log("Building locally (so worker can skip npm install)...");
|
|
2119
2291
|
try {
|
|
2120
|
-
(0,
|
|
2292
|
+
(0, import_child_process6.execSync)("npm run build", {
|
|
2121
2293
|
cwd: options.dir,
|
|
2122
2294
|
stdio: "inherit",
|
|
2123
2295
|
env: { ...process.env, NODE_ENV: "production" }
|
|
@@ -2128,7 +2300,7 @@ var installCommand = new import_commander8.Command("install").description(
|
|
|
2128
2300
|
);
|
|
2129
2301
|
process.exit(1);
|
|
2130
2302
|
}
|
|
2131
|
-
const distEntry =
|
|
2303
|
+
const distEntry = path10.join(options.dir, "dist", "theme.js");
|
|
2132
2304
|
if (!fs10.existsSync(distEntry)) {
|
|
2133
2305
|
console.error(
|
|
2134
2306
|
"\nBuild completed but dist/theme.js is missing. Check vite.config.ts entry / output filename."
|
|
@@ -2136,7 +2308,7 @@ var installCommand = new import_commander8.Command("install").description(
|
|
|
2136
2308
|
process.exit(1);
|
|
2137
2309
|
}
|
|
2138
2310
|
console.log("Packaging source + dist...");
|
|
2139
|
-
const zipPath =
|
|
2311
|
+
const zipPath = path10.join(
|
|
2140
2312
|
os4.tmpdir(),
|
|
2141
2313
|
`numu-theme-install-${Date.now()}.zip`
|
|
2142
2314
|
);
|
|
@@ -2250,9 +2422,9 @@ Timed out waiting for build (${options.pollTimeout}s). Run \`numu-theme status -
|
|
|
2250
2422
|
);
|
|
2251
2423
|
|
|
2252
2424
|
// src/commands/login.ts
|
|
2253
|
-
var
|
|
2425
|
+
var import_commander10 = require("commander");
|
|
2254
2426
|
var import_inquirer = __toESM(require("inquirer"));
|
|
2255
|
-
var loginCommand = new
|
|
2427
|
+
var loginCommand = new import_commander10.Command("login").description("Authenticate with the NUMU API").option("--token <token>", "API token (skip interactive login)").option("--api-url <url>", "Custom API URL").action(async (options) => {
|
|
2256
2428
|
if (options.apiUrl) {
|
|
2257
2429
|
saveConfig({ api_url: options.apiUrl });
|
|
2258
2430
|
console.log(`API URL set to: ${options.apiUrl}`);
|
|
@@ -2299,8 +2471,8 @@ Login failed: ${err.message}`);
|
|
|
2299
2471
|
});
|
|
2300
2472
|
|
|
2301
2473
|
// src/commands/status.ts
|
|
2302
|
-
var
|
|
2303
|
-
var statusCommand = new
|
|
2474
|
+
var import_commander11 = require("commander");
|
|
2475
|
+
var statusCommand = new import_commander11.Command("status").description("Poll the status of a theme build or marketplace version").option("--build <build_id>", "Build ID returned by `numu-theme push`").option(
|
|
2304
2476
|
"--version <version_id>",
|
|
2305
2477
|
"Marketplace version ID returned by `numu-theme submit`"
|
|
2306
2478
|
).option("-w, --watch", "Poll until the build reaches a terminal state").option(
|
|
@@ -2427,13 +2599,13 @@ function formatBytes(n) {
|
|
|
2427
2599
|
}
|
|
2428
2600
|
|
|
2429
2601
|
// src/commands/doctor.ts
|
|
2430
|
-
var
|
|
2602
|
+
var import_commander12 = require("commander");
|
|
2431
2603
|
var fs11 = __toESM(require("fs"));
|
|
2432
|
-
var
|
|
2433
|
-
var doctorCommand = new
|
|
2604
|
+
var path11 = __toESM(require("path"));
|
|
2605
|
+
var doctorCommand = new import_commander12.Command("doctor").description("Diagnose common dev-loop problems (run inside a theme directory)").option("-d, --dir <directory>", "Theme directory", ".").option("-p, --port <port>", "Expected dev server port", "5173").action(async (options) => {
|
|
2434
2606
|
let issues = 0;
|
|
2435
2607
|
let warnings = 0;
|
|
2436
|
-
const themeDir =
|
|
2608
|
+
const themeDir = path11.resolve(options.dir);
|
|
2437
2609
|
function ok(line) {
|
|
2438
2610
|
console.log(` \x1B[32m\u2713\x1B[0m ${line}`);
|
|
2439
2611
|
}
|
|
@@ -2446,8 +2618,8 @@ var doctorCommand = new import_commander11.Command("doctor").description("Diagno
|
|
|
2446
2618
|
console.log(` \x1B[31m\u2717\x1B[0m ${line}`);
|
|
2447
2619
|
}
|
|
2448
2620
|
console.log("\nProject");
|
|
2449
|
-
const themeJsonPath =
|
|
2450
|
-
const settingsPath =
|
|
2621
|
+
const themeJsonPath = path11.join(themeDir, "theme.json");
|
|
2622
|
+
const settingsPath = path11.join(themeDir, "settings_schema.json");
|
|
2451
2623
|
if (!fs11.existsSync(themeJsonPath)) {
|
|
2452
2624
|
fail(`No theme.json found in ${themeDir}`);
|
|
2453
2625
|
console.log(
|
|
@@ -2473,21 +2645,53 @@ var doctorCommand = new import_commander11.Command("doctor").description("Diagno
|
|
|
2473
2645
|
"src/index.ts"
|
|
2474
2646
|
];
|
|
2475
2647
|
const entry = entryCandidates.find(
|
|
2476
|
-
(p) => fs11.existsSync(
|
|
2648
|
+
(p) => fs11.existsSync(path11.join(themeDir, p))
|
|
2477
2649
|
);
|
|
2478
2650
|
if (!entry) {
|
|
2479
2651
|
fail(`No entry point found (expected one of: ${entryCandidates.join(", ")})`);
|
|
2480
2652
|
} else {
|
|
2481
|
-
const src = fs11.readFileSync(
|
|
2482
|
-
const exportsMount = /\bexport\s+(?:async\s+)?function\s+mount\b/.test(src) || /\bexport\s+(?:const|let|var)\s+mount\b/.test(src) || /\bexport\s*\{[^}]*\bmount\b[^}]*\}/.test(src);
|
|
2653
|
+
const src = fs11.readFileSync(path11.join(themeDir, entry), "utf-8");
|
|
2654
|
+
const exportsMount = /\bexport\s+(?:async\s+)?function\s+mount\b/.test(src) || /\bexport\s+(?:const|let|var)\s+mount\b/.test(src) || /\bexport\s+(?:const|let|var)\s*\{[^}]*\bmount\b[^}]*\}/.test(src) || /\bexport\s*\{[^}]*\bmount\b[^}]*\}/.test(src);
|
|
2483
2655
|
if (exportsMount) ok(`${entry} exports mount(el, props)`);
|
|
2484
2656
|
else
|
|
2485
2657
|
fail(
|
|
2486
2658
|
`${entry} does NOT export mount(el, props) \u2014 BYOT host can't render this theme. See THEME_AUTHORING.md for the contract, or scaffold a fresh theme with \`numu-theme init\`.`
|
|
2487
2659
|
);
|
|
2660
|
+
const exportsCreateApp = /\bexport\s+(?:async\s+)?function\s+createApp\b/.test(src) || /\bexport\s+(?:const|let|var)\s+createApp\b/.test(src) || /\bexport\s+(?:const|let|var)\s*\{[^}]*\bcreateApp\b[^}]*\}/.test(src) || /\bexport\s*\{[^}]*\bcreateApp\b[^}]*\}/.test(src);
|
|
2661
|
+
if (exportsCreateApp) ok(`${entry} exports createApp(ctx) \u2014 SSR-capable`);
|
|
2662
|
+
else
|
|
2663
|
+
warn(
|
|
2664
|
+
`${entry} does not export createApp \u2014 theme renders client-only (no server-rendered first paint). Use defineThemeEntry from @numueg/theme-sdk \u2265 0.3 to export mount + createApp from one component.`
|
|
2665
|
+
);
|
|
2666
|
+
}
|
|
2667
|
+
console.log("\nSSR toolchain");
|
|
2668
|
+
try {
|
|
2669
|
+
const pkg = JSON.parse(
|
|
2670
|
+
fs11.readFileSync(path11.join(themeDir, "package.json"), "utf-8")
|
|
2671
|
+
);
|
|
2672
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
2673
|
+
const checkMinor = (name, minMinor) => {
|
|
2674
|
+
const range = allDeps[name];
|
|
2675
|
+
if (!range) {
|
|
2676
|
+
warn(`${name} not in package.json \u2014 npm install it`);
|
|
2677
|
+
return;
|
|
2678
|
+
}
|
|
2679
|
+
const m = /(\d+)\.(\d+)\./.exec(range);
|
|
2680
|
+
if (m && Number(m[1]) === 0 && Number(m[2]) < minMinor) {
|
|
2681
|
+
warn(
|
|
2682
|
+
`${name}@${range} predates the SSR contract \u2014 bump to ^0.${minMinor}.0 for server rendering`
|
|
2683
|
+
);
|
|
2684
|
+
} else {
|
|
2685
|
+
ok(`${name}@${range}`);
|
|
2686
|
+
}
|
|
2687
|
+
};
|
|
2688
|
+
checkMinor("@numueg/theme-sdk", 3);
|
|
2689
|
+
checkMinor("@numueg/theme-plugin", 3);
|
|
2690
|
+
} catch {
|
|
2691
|
+
warn("Could not read package.json to verify SDK/plugin versions");
|
|
2488
2692
|
}
|
|
2489
2693
|
console.log("\nLocales");
|
|
2490
|
-
const localesDir =
|
|
2694
|
+
const localesDir = path11.join(themeDir, "locales");
|
|
2491
2695
|
if (!fs11.existsSync(localesDir)) {
|
|
2492
2696
|
warn(
|
|
2493
2697
|
"locales/ directory not found \u2014 `useTranslation` will fall through to keys. Add locales/en.default.json (required) and locales/ar.json (recommended for MENA stores)."
|
|
@@ -2504,14 +2708,14 @@ var doctorCommand = new import_commander11.Command("doctor").description("Diagno
|
|
|
2504
2708
|
}
|
|
2505
2709
|
for (const f of localeFiles) {
|
|
2506
2710
|
try {
|
|
2507
|
-
JSON.parse(fs11.readFileSync(
|
|
2711
|
+
JSON.parse(fs11.readFileSync(path11.join(localesDir, f), "utf-8"));
|
|
2508
2712
|
} catch (err) {
|
|
2509
2713
|
fail(`locales/${f} is not valid JSON: ${err.message}`);
|
|
2510
2714
|
}
|
|
2511
2715
|
}
|
|
2512
2716
|
}
|
|
2513
2717
|
console.log("\nAssets");
|
|
2514
|
-
const assetsDir =
|
|
2718
|
+
const assetsDir = path11.join(themeDir, "assets");
|
|
2515
2719
|
if (!fs11.existsSync(assetsDir)) {
|
|
2516
2720
|
warn(
|
|
2517
2721
|
"assets/ directory not found \u2014 assetUrl() helpers will fall back to bare paths under /assets/. Create assets/ and put images, fonts, and JSON fixtures there for the plugin to copy with content-hashed filenames."
|
|
@@ -2532,13 +2736,13 @@ var doctorCommand = new import_commander11.Command("doctor").description("Diagno
|
|
|
2532
2736
|
fs11.readFileSync(themeJsonPath, "utf-8")
|
|
2533
2737
|
);
|
|
2534
2738
|
const presets = themeJson.presets ?? {};
|
|
2535
|
-
const sectionsDir =
|
|
2536
|
-
const schemaDir =
|
|
2739
|
+
const sectionsDir = path11.join(themeDir, "src", "sections");
|
|
2740
|
+
const schemaDir = path11.join(themeDir, "schemas", "sections");
|
|
2537
2741
|
const componentNames = fs11.existsSync(sectionsDir) ? new Set(
|
|
2538
|
-
fs11.readdirSync(sectionsDir).filter((f) => /\.(tsx|ts|jsx|js)$/.test(f)).map((f) =>
|
|
2742
|
+
fs11.readdirSync(sectionsDir).filter((f) => /\.(tsx|ts|jsx|js)$/.test(f)).map((f) => path11.basename(f, path11.extname(f)).toLowerCase())
|
|
2539
2743
|
) : /* @__PURE__ */ new Set();
|
|
2540
2744
|
const schemaNames = fs11.existsSync(schemaDir) ? new Set(
|
|
2541
|
-
fs11.readdirSync(schemaDir).filter((f) => f.endsWith(".json")).map((f) =>
|
|
2745
|
+
fs11.readdirSync(schemaDir).filter((f) => f.endsWith(".json")).map((f) => path11.basename(f, ".json").toLowerCase())
|
|
2542
2746
|
) : /* @__PURE__ */ new Set();
|
|
2543
2747
|
let missingRefs = 0;
|
|
2544
2748
|
for (const [presetName, presetVal] of Object.entries(presets)) {
|
|
@@ -2569,8 +2773,8 @@ var doctorCommand = new import_commander11.Command("doctor").description("Diagno
|
|
|
2569
2773
|
warn(`Could not parse theme.json presets: ${err.message}`);
|
|
2570
2774
|
}
|
|
2571
2775
|
console.log("\nBuild");
|
|
2572
|
-
const distJs =
|
|
2573
|
-
const distCss =
|
|
2776
|
+
const distJs = path11.join(themeDir, "dist", "theme.js");
|
|
2777
|
+
const distCss = path11.join(themeDir, "dist", "theme.css");
|
|
2574
2778
|
if (!fs11.existsSync(distJs)) {
|
|
2575
2779
|
warn("dist/theme.js not found \u2014 run `numu-theme build` first");
|
|
2576
2780
|
} else {
|
|
@@ -2586,6 +2790,16 @@ var doctorCommand = new import_commander11.Command("doctor").description("Diagno
|
|
|
2586
2790
|
} else {
|
|
2587
2791
|
ok("dist/theme.css present");
|
|
2588
2792
|
}
|
|
2793
|
+
const distServer = path11.join(themeDir, "dist", "theme.server.js");
|
|
2794
|
+
if (fs11.existsSync(distServer)) {
|
|
2795
|
+
ok(
|
|
2796
|
+
`dist/theme.server.js (${(fs11.statSync(distServer).size / 1024).toFixed(1)} KB) \u2014 SSR artifact present`
|
|
2797
|
+
);
|
|
2798
|
+
} else if (fs11.existsSync(distJs)) {
|
|
2799
|
+
warn(
|
|
2800
|
+
"dist/theme.server.js not found \u2014 last build produced a client-only theme (plugin < 0.3, federate:false, or SSR pass failed)"
|
|
2801
|
+
);
|
|
2802
|
+
}
|
|
2589
2803
|
console.log("\nAuth");
|
|
2590
2804
|
const config = loadConfig();
|
|
2591
2805
|
if (!config.token) {
|
|
@@ -2653,9 +2867,9 @@ var doctorCommand = new import_commander11.Command("doctor").description("Diagno
|
|
|
2653
2867
|
});
|
|
2654
2868
|
|
|
2655
2869
|
// src/commands/add-section.ts
|
|
2656
|
-
var
|
|
2870
|
+
var import_commander13 = require("commander");
|
|
2657
2871
|
var fs12 = __toESM(require("fs"));
|
|
2658
|
-
var
|
|
2872
|
+
var path12 = __toESM(require("path"));
|
|
2659
2873
|
|
|
2660
2874
|
// src/section-library/entries/hero-with-cta.ts
|
|
2661
2875
|
var heroWithCta = {
|
|
@@ -3700,7 +3914,7 @@ function findEntry(slug) {
|
|
|
3700
3914
|
}
|
|
3701
3915
|
|
|
3702
3916
|
// src/commands/add-section.ts
|
|
3703
|
-
var addSectionCommand = new
|
|
3917
|
+
var addSectionCommand = new import_commander13.Command("add-section").description("Scaffold a new section (optionally from the built-in library)").argument("[name]", "Section slug (kebab-case, e.g. 'hero-banner')").option(
|
|
3704
3918
|
"--from-library <slug>",
|
|
3705
3919
|
"Copy from the built-in section library (run with --list to see options)"
|
|
3706
3920
|
).option("--list", "List the built-in section library and exit").option("-d, --dir <directory>", "Theme directory", ".").action(
|
|
@@ -3721,8 +3935,8 @@ var addSectionCommand = new import_commander12.Command("add-section").descriptio
|
|
|
3721
3935
|
);
|
|
3722
3936
|
process.exit(1);
|
|
3723
3937
|
}
|
|
3724
|
-
const themeDir =
|
|
3725
|
-
if (!fs12.existsSync(
|
|
3938
|
+
const themeDir = path12.resolve(process.cwd(), options.dir);
|
|
3939
|
+
if (!fs12.existsSync(path12.join(themeDir, "theme.json"))) {
|
|
3726
3940
|
console.error(
|
|
3727
3941
|
"No theme.json in this directory. Run from a theme project root."
|
|
3728
3942
|
);
|
|
@@ -3758,14 +3972,14 @@ var addSectionCommand = new import_commander12.Command("add-section").descriptio
|
|
|
3758
3972
|
]
|
|
3759
3973
|
};
|
|
3760
3974
|
}
|
|
3761
|
-
const componentPath =
|
|
3975
|
+
const componentPath = path12.join(themeDir, "src/sections", `${pascal}.tsx`);
|
|
3762
3976
|
ensureDirOf(componentPath);
|
|
3763
3977
|
if (fs12.existsSync(componentPath)) {
|
|
3764
3978
|
console.error(`Already exists: ${componentPath}`);
|
|
3765
3979
|
process.exit(1);
|
|
3766
3980
|
}
|
|
3767
3981
|
fs12.writeFileSync(componentPath, componentSource);
|
|
3768
|
-
const schemaPath =
|
|
3982
|
+
const schemaPath = path12.join(themeDir, "schemas/sections", `${slug}.json`);
|
|
3769
3983
|
ensureDirOf(schemaPath);
|
|
3770
3984
|
fs12.writeFileSync(schemaPath, JSON.stringify(schemaJson, null, 2));
|
|
3771
3985
|
tryWireMain(themeDir, slug, pascal);
|
|
@@ -3789,7 +4003,7 @@ function humanize(slug) {
|
|
|
3789
4003
|
return slug.split("-").filter(Boolean).map((p) => p[0].toUpperCase() + p.slice(1)).join(" ");
|
|
3790
4004
|
}
|
|
3791
4005
|
function ensureDirOf(p) {
|
|
3792
|
-
fs12.mkdirSync(
|
|
4006
|
+
fs12.mkdirSync(path12.dirname(p), { recursive: true });
|
|
3793
4007
|
}
|
|
3794
4008
|
function emptySectionStub(pascal) {
|
|
3795
4009
|
return `import type { SectionProps } from "@numueg/theme-sdk";
|
|
@@ -3807,7 +4021,7 @@ export default function ${pascal}({ settings }: SectionProps) {
|
|
|
3807
4021
|
`;
|
|
3808
4022
|
}
|
|
3809
4023
|
function tryWireMain(themeDir, slug, pascal) {
|
|
3810
|
-
const mainPath =
|
|
4024
|
+
const mainPath = path12.join(themeDir, "src/main.tsx");
|
|
3811
4025
|
if (!fs12.existsSync(mainPath)) return;
|
|
3812
4026
|
const src = fs12.readFileSync(mainPath, "utf-8");
|
|
3813
4027
|
if (src.includes(`./sections/${pascal}`)) return;
|
|
@@ -3848,7 +4062,7 @@ function tryWireMain(themeDir, slug, pascal) {
|
|
|
3848
4062
|
fs12.writeFileSync(mainPath, lines.join("\n"));
|
|
3849
4063
|
}
|
|
3850
4064
|
function tryAddToHomePreset(themeDir, slug) {
|
|
3851
|
-
const themeJsonPath =
|
|
4065
|
+
const themeJsonPath = path12.join(themeDir, "theme.json");
|
|
3852
4066
|
if (!fs12.existsSync(themeJsonPath)) return;
|
|
3853
4067
|
let parsed;
|
|
3854
4068
|
try {
|
|
@@ -3875,19 +4089,19 @@ function tryAddToHomePreset(themeDir, slug) {
|
|
|
3875
4089
|
}
|
|
3876
4090
|
|
|
3877
4091
|
// src/commands/add-block.ts
|
|
3878
|
-
var
|
|
4092
|
+
var import_commander14 = require("commander");
|
|
3879
4093
|
var fs13 = __toESM(require("fs"));
|
|
3880
|
-
var
|
|
3881
|
-
var addBlockCommand = new
|
|
3882
|
-
const themeDir =
|
|
3883
|
-
if (!fs13.existsSync(
|
|
4094
|
+
var path13 = __toESM(require("path"));
|
|
4095
|
+
var addBlockCommand = new import_commander14.Command("add-block").description("Scaffold a new block inside an existing section schema").argument("<section>", "Section type, e.g. 'product_grid'").argument("<name>", "Block type, e.g. 'feature'").option("-d, --dir <directory>", "Theme directory", ".").action((section, name, options) => {
|
|
4096
|
+
const themeDir = path13.resolve(options.dir);
|
|
4097
|
+
if (!fs13.existsSync(path13.join(themeDir, "theme.json"))) {
|
|
3884
4098
|
console.error(`No theme.json in ${themeDir}.`);
|
|
3885
4099
|
process.exit(1);
|
|
3886
4100
|
}
|
|
3887
4101
|
const sectionSnake = toSnakeCase(section);
|
|
3888
4102
|
const blockSnake = toSnakeCase(name);
|
|
3889
4103
|
const blockHuman = humanize2(toPascalCase(name));
|
|
3890
|
-
const schemaPath =
|
|
4104
|
+
const schemaPath = path13.join(
|
|
3891
4105
|
themeDir,
|
|
3892
4106
|
"schemas",
|
|
3893
4107
|
"sections",
|
|
@@ -3930,7 +4144,7 @@ var addBlockCommand = new import_commander13.Command("add-block").description("S
|
|
|
3930
4144
|
`
|
|
3931
4145
|
\x1B[32m\u2713\x1B[0m Added block "${blockSnake}" to section "${sectionSnake}":`,
|
|
3932
4146
|
`
|
|
3933
|
-
- ${
|
|
4147
|
+
- ${path13.relative(themeDir, schemaPath)}`,
|
|
3934
4148
|
`
|
|
3935
4149
|
|
|
3936
4150
|
\x1B[33m\u26A0\x1B[0m The section component should iterate \`blockOrder\` and render each block by type.`,
|
|
@@ -3962,17 +4176,17 @@ function humanize2(name) {
|
|
|
3962
4176
|
}
|
|
3963
4177
|
|
|
3964
4178
|
// src/commands/pull.ts
|
|
3965
|
-
var
|
|
4179
|
+
var import_commander15 = require("commander");
|
|
3966
4180
|
var fs14 = __toESM(require("fs"));
|
|
3967
|
-
var
|
|
4181
|
+
var path14 = __toESM(require("path"));
|
|
3968
4182
|
var https2 = __toESM(require("https"));
|
|
3969
4183
|
var http2 = __toESM(require("http"));
|
|
3970
4184
|
async function downloadToFile(url, dest) {
|
|
3971
|
-
return new Promise((
|
|
4185
|
+
return new Promise((resolve10, reject) => {
|
|
3972
4186
|
const client = url.startsWith("https:") ? https2 : http2;
|
|
3973
4187
|
const req = client.get(url, (res) => {
|
|
3974
4188
|
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
3975
|
-
downloadToFile(res.headers.location, dest).then(
|
|
4189
|
+
downloadToFile(res.headers.location, dest).then(resolve10, reject);
|
|
3976
4190
|
return;
|
|
3977
4191
|
}
|
|
3978
4192
|
if (res.statusCode !== 200) {
|
|
@@ -3981,7 +4195,7 @@ async function downloadToFile(url, dest) {
|
|
|
3981
4195
|
}
|
|
3982
4196
|
const out = fs14.createWriteStream(dest);
|
|
3983
4197
|
res.pipe(out);
|
|
3984
|
-
out.on("finish", () => out.close(() =>
|
|
4198
|
+
out.on("finish", () => out.close(() => resolve10()));
|
|
3985
4199
|
out.on("error", reject);
|
|
3986
4200
|
});
|
|
3987
4201
|
req.on("error", reject);
|
|
@@ -4000,7 +4214,7 @@ async function unzip(zipPath, targetDir) {
|
|
|
4000
4214
|
});
|
|
4001
4215
|
}
|
|
4002
4216
|
}
|
|
4003
|
-
var pullCommand = new
|
|
4217
|
+
var pullCommand = new import_commander15.Command("pull").description("Download a published theme's source to a local directory").argument("<theme-id>", "Theme slug or UUID").option(
|
|
4004
4218
|
"-d, --dir <directory>",
|
|
4005
4219
|
"Target directory (default: ./<theme-id>)"
|
|
4006
4220
|
).option(
|
|
@@ -4008,7 +4222,7 @@ var pullCommand = new import_commander14.Command("pull").description("Download a
|
|
|
4008
4222
|
"Specific version_string (default: latest)"
|
|
4009
4223
|
).option("--force", "Overwrite the target directory if it exists").action(
|
|
4010
4224
|
async (themeId, options) => {
|
|
4011
|
-
const targetDir =
|
|
4225
|
+
const targetDir = path14.resolve(options.dir || themeId);
|
|
4012
4226
|
if (fs14.existsSync(targetDir) && !options.force) {
|
|
4013
4227
|
const isEmpty = fs14.readdirSync(targetDir).length === 0;
|
|
4014
4228
|
if (!isEmpty) {
|
|
@@ -4055,7 +4269,7 @@ var pullCommand = new import_commander14.Command("pull").description("Download a
|
|
|
4055
4269
|
process.exit(1);
|
|
4056
4270
|
}
|
|
4057
4271
|
console.log(`Downloading ${theme.name || themeId} ${target.version_string}\u2026`);
|
|
4058
|
-
const tmpZip =
|
|
4272
|
+
const tmpZip = path14.join(
|
|
4059
4273
|
require("os").tmpdir(),
|
|
4060
4274
|
`numu-pull-${process.pid}-${Date.now()}.zip`
|
|
4061
4275
|
);
|
|
@@ -4072,28 +4286,28 @@ var pullCommand = new import_commander14.Command("pull").description("Download a
|
|
|
4072
4286
|
console.log(`\u2714 Pulled ${theme.slug || themeId}@${target.version_string} into ${targetDir}`);
|
|
4073
4287
|
console.log("");
|
|
4074
4288
|
console.log("Next steps:");
|
|
4075
|
-
console.log(` cd ${
|
|
4289
|
+
console.log(` cd ${path14.relative(process.cwd(), targetDir) || "."}`);
|
|
4076
4290
|
console.log(" npm install");
|
|
4077
4291
|
console.log(" numu-theme dev");
|
|
4078
4292
|
}
|
|
4079
4293
|
);
|
|
4080
4294
|
|
|
4081
4295
|
// src/commands/delete.ts
|
|
4082
|
-
var
|
|
4296
|
+
var import_commander16 = require("commander");
|
|
4083
4297
|
var readline = __toESM(require("readline"));
|
|
4084
4298
|
async function confirm(prompt) {
|
|
4085
4299
|
const rl = readline.createInterface({
|
|
4086
4300
|
input: process.stdin,
|
|
4087
4301
|
output: process.stdout
|
|
4088
4302
|
});
|
|
4089
|
-
return new Promise((
|
|
4303
|
+
return new Promise((resolve10) => {
|
|
4090
4304
|
rl.question(`${prompt} [y/N] `, (answer) => {
|
|
4091
4305
|
rl.close();
|
|
4092
|
-
|
|
4306
|
+
resolve10(/^y(es)?$/i.test(answer.trim()));
|
|
4093
4307
|
});
|
|
4094
4308
|
});
|
|
4095
4309
|
}
|
|
4096
|
-
var deleteCommand = new
|
|
4310
|
+
var deleteCommand = new import_commander16.Command("delete").description("Delete a theme or a specific version from the marketplace").argument("<theme-id>", "Theme slug or UUID").option("-v, --version <version>", "Delete this version only (default: whole theme)").option("-y, --yes", "Skip the confirmation prompt").action(
|
|
4097
4311
|
async (themeId, options) => {
|
|
4098
4312
|
const isVersion = Boolean(options.version);
|
|
4099
4313
|
const scope = isVersion ? `version ${options.version} of ${themeId}` : `the entire theme ${themeId}`;
|
|
@@ -4106,8 +4320,8 @@ var deleteCommand = new import_commander15.Command("delete").description("Delete
|
|
|
4106
4320
|
return;
|
|
4107
4321
|
}
|
|
4108
4322
|
}
|
|
4109
|
-
const
|
|
4110
|
-
const res = await apiRequest("DELETE",
|
|
4323
|
+
const path16 = isVersion ? `/api/v1/marketplace/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(options.version)}` : `/api/v1/marketplace/themes/${encodeURIComponent(themeId)}`;
|
|
4324
|
+
const res = await apiRequest("DELETE", path16);
|
|
4111
4325
|
if (res.status === 200 || res.status === 204) {
|
|
4112
4326
|
console.log(`\u2714 Deleted ${scope}.`);
|
|
4113
4327
|
return;
|
|
@@ -4126,9 +4340,9 @@ var deleteCommand = new import_commander15.Command("delete").description("Delete
|
|
|
4126
4340
|
);
|
|
4127
4341
|
|
|
4128
4342
|
// src/commands/migrate.ts
|
|
4129
|
-
var
|
|
4343
|
+
var import_commander17 = require("commander");
|
|
4130
4344
|
var fs15 = __toESM(require("fs"));
|
|
4131
|
-
var
|
|
4345
|
+
var path15 = __toESM(require("path"));
|
|
4132
4346
|
var import_chalk = __toESM(require("chalk"));
|
|
4133
4347
|
var V2_HOOK_PATTERNS = [
|
|
4134
4348
|
{
|
|
@@ -4226,14 +4440,14 @@ function findSectionFiles(dir) {
|
|
|
4226
4440
|
if (!fs15.existsSync(dir)) return [];
|
|
4227
4441
|
const out = [];
|
|
4228
4442
|
for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
|
|
4229
|
-
const full =
|
|
4443
|
+
const full = path15.join(dir, entry.name);
|
|
4230
4444
|
if (entry.isDirectory()) out.push(...findSectionFiles(full));
|
|
4231
4445
|
else if (entry.isFile() && /\.(tsx|ts)$/.test(entry.name)) out.push(full);
|
|
4232
4446
|
}
|
|
4233
4447
|
return out;
|
|
4234
4448
|
}
|
|
4235
4449
|
function sectionTypeFromFilename(filename) {
|
|
4236
|
-
const base =
|
|
4450
|
+
const base = path15.basename(filename).replace(/\.(tsx|ts)$/, "");
|
|
4237
4451
|
return base.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[^a-zA-Z0-9-]+/g, "-").toLowerCase();
|
|
4238
4452
|
}
|
|
4239
4453
|
function makeV2Bridge() {
|
|
@@ -4733,7 +4947,7 @@ function makeTemplates() {
|
|
|
4733
4947
|
`
|
|
4734
4948
|
};
|
|
4735
4949
|
}
|
|
4736
|
-
var migrateCommand = new
|
|
4950
|
+
var migrateCommand = new import_commander17.Command("migrate").description("Scaffold a V3 theme project from a V2 theme directory").argument(
|
|
4737
4951
|
"<v2-path>",
|
|
4738
4952
|
"Path to the V2 theme directory (e.g. ../numu-egyptian-bazaar/src/themes/empire)"
|
|
4739
4953
|
).option(
|
|
@@ -4744,15 +4958,15 @@ var migrateCommand = new import_commander16.Command("migrate").description("Scaf
|
|
|
4744
4958
|
"Override the display name (default: title-cased v2 id)"
|
|
4745
4959
|
).action(
|
|
4746
4960
|
async (v2Path, options) => {
|
|
4747
|
-
const absV2Path =
|
|
4961
|
+
const absV2Path = path15.resolve(process.cwd(), v2Path);
|
|
4748
4962
|
if (!fs15.existsSync(absV2Path) || !fs15.statSync(absV2Path).isDirectory()) {
|
|
4749
4963
|
console.error(import_chalk.default.red(`V2 path does not exist or is not a directory: ${absV2Path}`));
|
|
4750
4964
|
process.exit(1);
|
|
4751
4965
|
}
|
|
4752
|
-
const v2Id =
|
|
4966
|
+
const v2Id = path15.basename(absV2Path).toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
4753
4967
|
const themeId = `${v2Id}-v3`;
|
|
4754
4968
|
const displayName = options.name ?? v2Id.charAt(0).toUpperCase() + v2Id.slice(1).replace(/-/g, " ") + " (V3)";
|
|
4755
|
-
const outDir =
|
|
4969
|
+
const outDir = path15.resolve(
|
|
4756
4970
|
process.cwd(),
|
|
4757
4971
|
options.out ?? `${v2Id}-engine-V3`
|
|
4758
4972
|
);
|
|
@@ -4773,25 +4987,25 @@ Migrating V2 \u2192 V3:`));
|
|
|
4773
4987
|
"schemas/blocks",
|
|
4774
4988
|
"templates"
|
|
4775
4989
|
]) {
|
|
4776
|
-
fs15.mkdirSync(
|
|
4990
|
+
fs15.mkdirSync(path15.join(outDir, d), { recursive: true });
|
|
4777
4991
|
}
|
|
4778
4992
|
fs15.writeFileSync(
|
|
4779
|
-
|
|
4993
|
+
path15.join(outDir, "src/v2-bridge", "index.tsx"),
|
|
4780
4994
|
makeV2Bridge()
|
|
4781
4995
|
);
|
|
4782
|
-
const v2Styles =
|
|
4996
|
+
const v2Styles = path15.join(absV2Path, "styles.css");
|
|
4783
4997
|
if (fs15.existsSync(v2Styles)) {
|
|
4784
|
-
fs15.copyFileSync(v2Styles,
|
|
4998
|
+
fs15.copyFileSync(v2Styles, path15.join(outDir, "styles.css"));
|
|
4785
4999
|
console.log(import_chalk.default.green(" \u2713 Copied styles.css"));
|
|
4786
5000
|
} else {
|
|
4787
5001
|
fs15.writeFileSync(
|
|
4788
|
-
|
|
5002
|
+
path15.join(outDir, "styles.css"),
|
|
4789
5003
|
`/* ${displayName} styles */
|
|
4790
5004
|
`
|
|
4791
5005
|
);
|
|
4792
5006
|
console.log(import_chalk.default.yellow(" \u26A0 No styles.css found \u2014 created an empty one"));
|
|
4793
5007
|
}
|
|
4794
|
-
const v2SectionsDir =
|
|
5008
|
+
const v2SectionsDir = path15.join(absV2Path, "sections");
|
|
4795
5009
|
const sectionFiles = fs15.existsSync(v2SectionsDir) ? findSectionFiles(v2SectionsDir) : [];
|
|
4796
5010
|
const sectionTypes = [];
|
|
4797
5011
|
const allNotes = [];
|
|
@@ -4802,18 +5016,18 @@ Migrating V2 \u2192 V3:`));
|
|
|
4802
5016
|
const v2Source = fs15.readFileSync(file, "utf-8");
|
|
4803
5017
|
const notes = migrationNotesFor(v2Source);
|
|
4804
5018
|
allNotes.push({ file, type, notes });
|
|
4805
|
-
const relativeV2 =
|
|
5019
|
+
const relativeV2 = path15.relative(process.cwd(), file);
|
|
4806
5020
|
const header = adapterCommentBlock(notes, relativeV2);
|
|
4807
5021
|
const rewritten = rewriteV2Imports(v2Source);
|
|
4808
5022
|
const ported = `${header}
|
|
4809
5023
|
|
|
4810
5024
|
${rewritten}`;
|
|
4811
5025
|
fs15.writeFileSync(
|
|
4812
|
-
|
|
5026
|
+
path15.join(outDir, "src/sections", `${type}.tsx`),
|
|
4813
5027
|
ported
|
|
4814
5028
|
);
|
|
4815
5029
|
fs15.writeFileSync(
|
|
4816
|
-
|
|
5030
|
+
path15.join(outDir, "schemas/sections", `${type}.json`),
|
|
4817
5031
|
JSON.stringify(
|
|
4818
5032
|
{
|
|
4819
5033
|
type,
|
|
@@ -4827,38 +5041,38 @@ ${rewritten}`;
|
|
|
4827
5041
|
}
|
|
4828
5042
|
console.log(import_chalk.default.green(` \u2713 Ported ${sectionTypes.length} section file(s)`));
|
|
4829
5043
|
fs15.writeFileSync(
|
|
4830
|
-
|
|
5044
|
+
path15.join(outDir, "theme.json"),
|
|
4831
5045
|
JSON.stringify(makeThemeJson(themeId, displayName, sectionTypes), null, 2)
|
|
4832
5046
|
);
|
|
4833
5047
|
fs15.writeFileSync(
|
|
4834
|
-
|
|
5048
|
+
path15.join(outDir, "settings_schema.json"),
|
|
4835
5049
|
JSON.stringify(makeSettingsSchema(), null, 2)
|
|
4836
5050
|
);
|
|
4837
5051
|
fs15.writeFileSync(
|
|
4838
|
-
|
|
5052
|
+
path15.join(outDir, "src/main.tsx"),
|
|
4839
5053
|
makeMainTsx(themeId, displayName, sectionTypes)
|
|
4840
5054
|
);
|
|
4841
5055
|
fs15.writeFileSync(
|
|
4842
|
-
|
|
5056
|
+
path15.join(outDir, "vite.config.ts"),
|
|
4843
5057
|
makeViteConfig()
|
|
4844
5058
|
);
|
|
4845
5059
|
fs15.writeFileSync(
|
|
4846
|
-
|
|
5060
|
+
path15.join(outDir, "package.json"),
|
|
4847
5061
|
JSON.stringify(makePackageJson(themeId, displayName), null, 2)
|
|
4848
5062
|
);
|
|
4849
5063
|
fs15.writeFileSync(
|
|
4850
|
-
|
|
5064
|
+
path15.join(outDir, "tsconfig.json"),
|
|
4851
5065
|
JSON.stringify(makeTsConfig(), null, 2)
|
|
4852
5066
|
);
|
|
4853
5067
|
fs15.writeFileSync(
|
|
4854
|
-
|
|
5068
|
+
path15.join(outDir, "index.html"),
|
|
4855
5069
|
makeIndexHtml(displayName)
|
|
4856
5070
|
);
|
|
4857
5071
|
const tpl = makeTemplates();
|
|
4858
|
-
fs15.writeFileSync(
|
|
4859
|
-
fs15.writeFileSync(
|
|
5072
|
+
fs15.writeFileSync(path15.join(outDir, "templates/error.html"), tpl.error);
|
|
5073
|
+
fs15.writeFileSync(path15.join(outDir, "templates/loading.html"), tpl.loading);
|
|
4860
5074
|
fs15.writeFileSync(
|
|
4861
|
-
|
|
5075
|
+
path15.join(outDir, ".gitignore"),
|
|
4862
5076
|
"node_modules/\ndist/\n.DS_Store\n"
|
|
4863
5077
|
);
|
|
4864
5078
|
console.log(import_chalk.default.green(" \u2713 Wrote scaffold files\n"));
|
|
@@ -4879,7 +5093,7 @@ ${rewritten}`;
|
|
|
4879
5093
|
console.log();
|
|
4880
5094
|
}
|
|
4881
5095
|
console.log(import_chalk.default.bold("\nNext steps:"));
|
|
4882
|
-
console.log(` ${import_chalk.default.cyan("cd")} ${
|
|
5096
|
+
console.log(` ${import_chalk.default.cyan("cd")} ${path15.relative(process.cwd(), outDir)}`);
|
|
4883
5097
|
console.log(` ${import_chalk.default.cyan("npm install")}`);
|
|
4884
5098
|
console.log(` ${import_chalk.default.cyan("npm run dev")} ${import_chalk.default.dim("# dev preview on :5173")}`);
|
|
4885
5099
|
console.log(` ${import_chalk.default.cyan("npx numu-theme build")} ${import_chalk.default.dim("# validate + build")}`);
|
|
@@ -4891,13 +5105,14 @@ ${import_chalk.default.dim("Open each src/sections/*.tsx and follow the ADAPTER
|
|
|
4891
5105
|
);
|
|
4892
5106
|
|
|
4893
5107
|
// src/index.ts
|
|
4894
|
-
var program = new
|
|
5108
|
+
var program = new import_commander18.Command();
|
|
4895
5109
|
program.name("numu-theme").description("CLI for developing, validating, building, and publishing NUMU themes").version("0.1.0");
|
|
4896
5110
|
program.addCommand(initCommand);
|
|
4897
5111
|
program.addCommand(devCommand);
|
|
4898
5112
|
program.addCommand(checkCommand);
|
|
4899
5113
|
program.addCommand(lintCommand);
|
|
4900
5114
|
program.addCommand(buildCommand);
|
|
5115
|
+
program.addCommand(verifyCommand);
|
|
4901
5116
|
program.addCommand(pushCommand);
|
|
4902
5117
|
program.addCommand(submitCommand);
|
|
4903
5118
|
program.addCommand(installCommand);
|