@numueg/theme-cli 0.2.0 → 0.4.0
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 +452 -239
- 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(
|
|
@@ -1161,10 +1199,10 @@ export default defineConfig({
|
|
|
1161
1199
|
build: "numu-theme build",
|
|
1162
1200
|
check: "numu-theme check"
|
|
1163
1201
|
},
|
|
1164
|
-
dependencies: { "@numueg/theme-sdk": "^0.
|
|
1202
|
+
dependencies: { "@numueg/theme-sdk": "^0.3.0" },
|
|
1165
1203
|
devDependencies: {
|
|
1166
|
-
"@numueg/theme-cli": "^0.
|
|
1167
|
-
"@numueg/theme-plugin": "^0.
|
|
1204
|
+
"@numueg/theme-cli": "^0.3.0",
|
|
1205
|
+
"@numueg/theme-plugin": "^0.3.0",
|
|
1168
1206
|
"@vitejs/plugin-react": "^4.3.0",
|
|
1169
1207
|
vite: "^6.0.0",
|
|
1170
1208
|
typescript: "^5.8.0",
|
|
@@ -1274,12 +1312,12 @@ function assertHttpsOrLocalhost(urlStr) {
|
|
|
1274
1312
|
}
|
|
1275
1313
|
throw new Error(`Unsupported protocol: ${url.protocol}`);
|
|
1276
1314
|
}
|
|
1277
|
-
async function apiRequest(method,
|
|
1315
|
+
async function apiRequest(method, path16, body) {
|
|
1278
1316
|
const config = loadConfig();
|
|
1279
|
-
const url = assertHttpsOrLocalhost(`${config.api_url}${
|
|
1317
|
+
const url = assertHttpsOrLocalhost(`${config.api_url}${path16}`);
|
|
1280
1318
|
const isHttps = url.protocol === "https:";
|
|
1281
1319
|
const transport = isHttps ? https : http;
|
|
1282
|
-
return new Promise((
|
|
1320
|
+
return new Promise((resolve10, reject) => {
|
|
1283
1321
|
const headers = {};
|
|
1284
1322
|
if (config.token) headers["Authorization"] = `Bearer ${config.token}`;
|
|
1285
1323
|
let postData;
|
|
@@ -1301,7 +1339,7 @@ async function apiRequest(method, path15, body) {
|
|
|
1301
1339
|
res.on("data", (chunk) => data += chunk);
|
|
1302
1340
|
res.on("end", () => {
|
|
1303
1341
|
const { unwrapped, raw } = parseBody(data);
|
|
1304
|
-
|
|
1342
|
+
resolve10({
|
|
1305
1343
|
status: res.statusCode ?? 0,
|
|
1306
1344
|
data: unwrapped,
|
|
1307
1345
|
raw
|
|
@@ -1334,7 +1372,7 @@ Content-Type: application/zip\r
|
|
|
1334
1372
|
const fullBody = Buffer.concat([head, fileBuffer, tail]);
|
|
1335
1373
|
const isHttps = url.protocol === "https:";
|
|
1336
1374
|
const transport = isHttps ? https : http;
|
|
1337
|
-
return new Promise((
|
|
1375
|
+
return new Promise((resolve10, reject) => {
|
|
1338
1376
|
const headers = {
|
|
1339
1377
|
"Content-Type": `multipart/form-data; boundary=${boundary}`,
|
|
1340
1378
|
"Content-Length": String(fullBody.byteLength)
|
|
@@ -1357,7 +1395,7 @@ Content-Type: application/zip\r
|
|
|
1357
1395
|
res.on("data", (chunk) => data += chunk);
|
|
1358
1396
|
res.on("end", () => {
|
|
1359
1397
|
const { unwrapped, raw } = parseBody(data);
|
|
1360
|
-
|
|
1398
|
+
resolve10({
|
|
1361
1399
|
status: res.statusCode ?? 0,
|
|
1362
1400
|
data: unwrapped,
|
|
1363
1401
|
raw
|
|
@@ -1532,6 +1570,48 @@ function validateTheme(themeDir) {
|
|
|
1532
1570
|
if (!themeJson.presets || Object.keys(themeJson.presets).length === 0) {
|
|
1533
1571
|
warnings.push("theme.json has no presets \u2014 merchants will start with an empty page");
|
|
1534
1572
|
}
|
|
1573
|
+
const REQUIRED_TEMPLATES = [
|
|
1574
|
+
"home",
|
|
1575
|
+
"product",
|
|
1576
|
+
"collection",
|
|
1577
|
+
"cart",
|
|
1578
|
+
"page",
|
|
1579
|
+
"search",
|
|
1580
|
+
"404"
|
|
1581
|
+
];
|
|
1582
|
+
if (themeJson.presets && typeof themeJson.presets === "object") {
|
|
1583
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
1584
|
+
for (const bucket of [
|
|
1585
|
+
themeJson.presets.templates,
|
|
1586
|
+
themeJson.presets.section_groups
|
|
1587
|
+
]) {
|
|
1588
|
+
if (!bucket || typeof bucket !== "object") continue;
|
|
1589
|
+
for (const entry of Object.values(bucket)) {
|
|
1590
|
+
const sections = entry?.sections;
|
|
1591
|
+
const instances = Array.isArray(sections) ? sections : sections && typeof sections === "object" ? Object.values(sections) : [];
|
|
1592
|
+
for (const inst of instances) {
|
|
1593
|
+
if (inst && typeof inst.type === "string") {
|
|
1594
|
+
referenced.add(inst.type.toLowerCase());
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
for (const type of referenced) {
|
|
1600
|
+
if (!schemaNames.has(type)) {
|
|
1601
|
+
errors.push(
|
|
1602
|
+
`theme.json preset references section type "${type}" but there is no schemas/sections/${type}.json \u2014 the storefront drops unknown sections at render.`
|
|
1603
|
+
);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
const templates = themeJson.presets.templates && typeof themeJson.presets.templates === "object" ? themeJson.presets.templates : {};
|
|
1607
|
+
for (const tpl of REQUIRED_TEMPLATES) {
|
|
1608
|
+
if (!(tpl in templates)) {
|
|
1609
|
+
warnings.push(
|
|
1610
|
+
`theme.json has no preset for the "${tpl}" template \u2014 the storefront will use its built-in fallback.`
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1535
1615
|
return { valid: errors.length === 0, errors, warnings };
|
|
1536
1616
|
}
|
|
1537
1617
|
|
|
@@ -1641,7 +1721,9 @@ async function runAllRules(themeDir, options) {
|
|
|
1641
1721
|
() => Promise.resolve().then(() => (init_use_app_no_availability_check(), use_app_no_availability_check_exports)),
|
|
1642
1722
|
() => Promise.resolve().then(() => (init_manifest_required_fields(), manifest_required_fields_exports)),
|
|
1643
1723
|
() => Promise.resolve().then(() => (init_contrast_hint(), contrast_hint_exports)),
|
|
1644
|
-
() => Promise.resolve().then(() => (init_touch_target(), touch_target_exports))
|
|
1724
|
+
() => Promise.resolve().then(() => (init_touch_target(), touch_target_exports)),
|
|
1725
|
+
() => Promise.resolve().then(() => (init_ssr_unsafe_globals(), ssr_unsafe_globals_exports)),
|
|
1726
|
+
() => Promise.resolve().then(() => (init_ssr_nondeterministic_render(), ssr_nondeterministic_render_exports))
|
|
1645
1727
|
];
|
|
1646
1728
|
const issues = [];
|
|
1647
1729
|
for (const load of ruleLoaders) {
|
|
@@ -1656,14 +1738,14 @@ async function runAllRules(themeDir, options) {
|
|
|
1656
1738
|
});
|
|
1657
1739
|
continue;
|
|
1658
1740
|
}
|
|
1659
|
-
const
|
|
1660
|
-
if (options.enabledRules && !options.enabledRules.has(
|
|
1741
|
+
const rule15 = mod.default;
|
|
1742
|
+
if (options.enabledRules && !options.enabledRules.has(rule15.id)) continue;
|
|
1661
1743
|
try {
|
|
1662
|
-
const ruleIssues = await
|
|
1744
|
+
const ruleIssues = await rule15.check(ctx);
|
|
1663
1745
|
for (const issue of ruleIssues) issues.push(issue);
|
|
1664
1746
|
} catch (e) {
|
|
1665
1747
|
issues.push({
|
|
1666
|
-
rule:
|
|
1748
|
+
rule: rule15.id,
|
|
1667
1749
|
severity: "warning",
|
|
1668
1750
|
message: `Rule crashed: ${e.message}`
|
|
1669
1751
|
});
|
|
@@ -1887,14 +1969,102 @@ Bundle size: ${sizeMB} MB`);
|
|
|
1887
1969
|
"\u26A0 Bundle exceeds 5MB limit \u2014 optimize before submitting to marketplace"
|
|
1888
1970
|
);
|
|
1889
1971
|
}
|
|
1972
|
+
const serverBundle = path6.join(distDir, "theme.server.js");
|
|
1973
|
+
if (fs7.existsSync(serverBundle)) {
|
|
1974
|
+
const kb = (fs7.statSync(serverBundle).size / 1024).toFixed(1);
|
|
1975
|
+
console.log(`SSR bundle: theme.server.js (${kb} KB) \u2014 hosts will server-render this theme`);
|
|
1976
|
+
} else {
|
|
1977
|
+
console.log(
|
|
1978
|
+
"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."
|
|
1979
|
+
);
|
|
1980
|
+
}
|
|
1890
1981
|
}
|
|
1891
1982
|
console.log("\n\u2713 Build complete");
|
|
1892
1983
|
}
|
|
1893
1984
|
);
|
|
1894
1985
|
|
|
1895
|
-
// src/commands/
|
|
1986
|
+
// src/commands/verify.ts
|
|
1896
1987
|
var import_commander6 = require("commander");
|
|
1988
|
+
var import_child_process5 = require("child_process");
|
|
1989
|
+
var import_fs = require("fs");
|
|
1990
|
+
var import_module = require("module");
|
|
1897
1991
|
var path7 = __toESM(require("path"));
|
|
1992
|
+
var import_url = require("url");
|
|
1993
|
+
var dynamicImport = new Function("u", "return import(u)");
|
|
1994
|
+
var verifyCommand = new import_commander6.Command("verify").description(
|
|
1995
|
+
"Server-render every template against fixtures to catch runtime crashes"
|
|
1996
|
+
).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(
|
|
1997
|
+
async (options) => {
|
|
1998
|
+
const dir = path7.resolve(process.cwd(), options.dir);
|
|
1999
|
+
if (options.build !== false) {
|
|
2000
|
+
console.log("Building theme (SSR bundle)\u2026");
|
|
2001
|
+
try {
|
|
2002
|
+
(0, import_child_process5.execSync)("npx vite build", { cwd: dir, stdio: "inherit" });
|
|
2003
|
+
} catch {
|
|
2004
|
+
console.error("Build failed");
|
|
2005
|
+
process.exit(1);
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
const serverBundle = path7.join(dir, "dist", "theme.server.js");
|
|
2009
|
+
if (!(0, import_fs.existsSync)(serverBundle)) {
|
|
2010
|
+
console.error(
|
|
2011
|
+
"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)."
|
|
2012
|
+
);
|
|
2013
|
+
process.exit(1);
|
|
2014
|
+
}
|
|
2015
|
+
const requireFromTheme = (0, import_module.createRequire)(path7.join(dir, "package.json"));
|
|
2016
|
+
let harness;
|
|
2017
|
+
try {
|
|
2018
|
+
const verifyEntry = requireFromTheme.resolve("@numueg/theme-sdk/verify");
|
|
2019
|
+
harness = await dynamicImport(
|
|
2020
|
+
(0, import_url.pathToFileURL)(verifyEntry).href
|
|
2021
|
+
);
|
|
2022
|
+
} catch {
|
|
2023
|
+
console.error(
|
|
2024
|
+
"This theme's @numueg/theme-sdk has no `/verify` entry \u2014 upgrade @numueg/theme-sdk to a version that ships the render harness."
|
|
2025
|
+
);
|
|
2026
|
+
process.exit(1);
|
|
2027
|
+
}
|
|
2028
|
+
let serverModule;
|
|
2029
|
+
try {
|
|
2030
|
+
serverModule = await dynamicImport((0, import_url.pathToFileURL)(serverBundle).href);
|
|
2031
|
+
} catch (e) {
|
|
2032
|
+
console.error(
|
|
2033
|
+
"Failed to load dist/theme.server.js:",
|
|
2034
|
+
e instanceof Error ? e.message : String(e)
|
|
2035
|
+
);
|
|
2036
|
+
process.exit(1);
|
|
2037
|
+
}
|
|
2038
|
+
const def = serverModule.default;
|
|
2039
|
+
const mod = {
|
|
2040
|
+
createApp: serverModule.createApp ?? def?.createApp
|
|
2041
|
+
};
|
|
2042
|
+
const result = await harness.verifyThemeRender(mod, {
|
|
2043
|
+
locale: options.locale
|
|
2044
|
+
});
|
|
2045
|
+
console.log("\nRender verification:");
|
|
2046
|
+
for (const r of result.results) {
|
|
2047
|
+
const mark = r.ok ? "\u2713" : "\u2718";
|
|
2048
|
+
console.log(
|
|
2049
|
+
` ${mark} ${r.template}${r.ok ? ` (${r.htmlLength} chars)` : ""}`
|
|
2050
|
+
);
|
|
2051
|
+
if (!r.ok && r.error) {
|
|
2052
|
+
console.log(` ${String(r.error).split("\n")[0]}`);
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
if (!result.ok) {
|
|
2056
|
+
console.error(
|
|
2057
|
+
"\n\u2718 Render verification failed \u2014 fix the crashing templates above."
|
|
2058
|
+
);
|
|
2059
|
+
process.exit(1);
|
|
2060
|
+
}
|
|
2061
|
+
console.log("\n\u2713 All templates render against fixture data.");
|
|
2062
|
+
}
|
|
2063
|
+
);
|
|
2064
|
+
|
|
2065
|
+
// src/commands/push.ts
|
|
2066
|
+
var import_commander7 = require("commander");
|
|
2067
|
+
var path8 = __toESM(require("path"));
|
|
1898
2068
|
var os2 = __toESM(require("os"));
|
|
1899
2069
|
|
|
1900
2070
|
// src/utils/zipper.ts
|
|
@@ -1902,10 +2072,10 @@ var fs8 = __toESM(require("fs"));
|
|
|
1902
2072
|
var import_archiver = __toESM(require("archiver"));
|
|
1903
2073
|
async function zipDirectory(sourceDir, outputPath, optsOrLegacyExcludes = {}) {
|
|
1904
2074
|
const opts = Array.isArray(optsOrLegacyExcludes) ? { excludePatterns: optsOrLegacyExcludes } : optsOrLegacyExcludes;
|
|
1905
|
-
return new Promise((
|
|
2075
|
+
return new Promise((resolve10, reject) => {
|
|
1906
2076
|
const output = fs8.createWriteStream(outputPath);
|
|
1907
2077
|
const archive = (0, import_archiver.default)("zip", { zlib: { level: 9 } });
|
|
1908
|
-
output.on("close", () =>
|
|
2078
|
+
output.on("close", () => resolve10(outputPath));
|
|
1909
2079
|
archive.on("error", reject);
|
|
1910
2080
|
archive.pipe(output);
|
|
1911
2081
|
const defaultExcludes = [
|
|
@@ -1927,7 +2097,7 @@ async function zipDirectory(sourceDir, outputPath, optsOrLegacyExcludes = {}) {
|
|
|
1927
2097
|
}
|
|
1928
2098
|
|
|
1929
2099
|
// src/commands/push.ts
|
|
1930
|
-
var pushCommand = new
|
|
2100
|
+
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
2101
|
const config = loadConfig();
|
|
1932
2102
|
if (!config.token) {
|
|
1933
2103
|
console.error("Not logged in. Run: numu-theme login");
|
|
@@ -1940,7 +2110,7 @@ var pushCommand = new import_commander6.Command("push").description("Upload buil
|
|
|
1940
2110
|
process.exit(1);
|
|
1941
2111
|
}
|
|
1942
2112
|
console.log("Packaging theme...");
|
|
1943
|
-
const zipPath =
|
|
2113
|
+
const zipPath = path8.join(os2.tmpdir(), `numu-theme-${Date.now()}.zip`);
|
|
1944
2114
|
await zipDirectory(options.dir, zipPath);
|
|
1945
2115
|
console.log("Uploading...");
|
|
1946
2116
|
const res = await uploadFile("/themes/upload", zipPath);
|
|
@@ -1961,11 +2131,11 @@ Push failed (${res.status}): ${JSON.stringify(res.data)}`);
|
|
|
1961
2131
|
});
|
|
1962
2132
|
|
|
1963
2133
|
// src/commands/submit.ts
|
|
1964
|
-
var
|
|
1965
|
-
var
|
|
2134
|
+
var import_commander8 = require("commander");
|
|
2135
|
+
var path9 = __toESM(require("path"));
|
|
1966
2136
|
var os3 = __toESM(require("os"));
|
|
1967
2137
|
var fs9 = __toESM(require("fs"));
|
|
1968
|
-
var submitCommand = new
|
|
2138
|
+
var submitCommand = new import_commander8.Command("submit").description("Submit theme to the NUMU Marketplace for review").option("-d, --dir <directory>", "Theme directory", ".").requiredOption(
|
|
1969
2139
|
"-t, --theme-id <theme_id>",
|
|
1970
2140
|
"Marketplace theme listing UUID (create via dashboard first)"
|
|
1971
2141
|
).option("-n, --notes <notes>", "Release notes for this version").action(
|
|
@@ -1988,7 +2158,7 @@ var submitCommand = new import_commander7.Command("submit").description("Submit
|
|
|
1988
2158
|
console.log("\nWarnings:");
|
|
1989
2159
|
result.warnings.forEach((w) => console.log(` \u26A0 ${w}`));
|
|
1990
2160
|
}
|
|
1991
|
-
const themeJsonPath =
|
|
2161
|
+
const themeJsonPath = path9.join(options.dir, "theme.json");
|
|
1992
2162
|
let version;
|
|
1993
2163
|
try {
|
|
1994
2164
|
const tj = JSON.parse(fs9.readFileSync(themeJsonPath, "utf-8"));
|
|
@@ -2002,7 +2172,7 @@ var submitCommand = new import_commander7.Command("submit").description("Submit
|
|
|
2002
2172
|
process.exit(1);
|
|
2003
2173
|
}
|
|
2004
2174
|
console.log("\nPackaging theme source...");
|
|
2005
|
-
const zipPath =
|
|
2175
|
+
const zipPath = path9.join(
|
|
2006
2176
|
os3.tmpdir(),
|
|
2007
2177
|
`numu-theme-submit-${Date.now()}.zip`
|
|
2008
2178
|
);
|
|
@@ -2055,12 +2225,12 @@ Submission failed (${submitRes.status}): ${JSON.stringify(submitRes.data)}`
|
|
|
2055
2225
|
);
|
|
2056
2226
|
|
|
2057
2227
|
// src/commands/install.ts
|
|
2058
|
-
var
|
|
2059
|
-
var
|
|
2060
|
-
var
|
|
2228
|
+
var import_commander9 = require("commander");
|
|
2229
|
+
var import_child_process6 = require("child_process");
|
|
2230
|
+
var path10 = __toESM(require("path"));
|
|
2061
2231
|
var os4 = __toESM(require("os"));
|
|
2062
2232
|
var fs10 = __toESM(require("fs"));
|
|
2063
|
-
var installCommand = new
|
|
2233
|
+
var installCommand = new import_commander9.Command("install").description(
|
|
2064
2234
|
"Build + upload + install a theme directly on a store you own (bypasses marketplace review)"
|
|
2065
2235
|
).option("-d, --dir <directory>", "Theme directory", ".").requiredOption(
|
|
2066
2236
|
"-t, --theme-id <theme_id>",
|
|
@@ -2098,7 +2268,7 @@ var installCommand = new import_commander8.Command("install").description(
|
|
|
2098
2268
|
process.exit(1);
|
|
2099
2269
|
}
|
|
2100
2270
|
result.warnings.forEach((w) => console.log(` \u26A0 ${w}`));
|
|
2101
|
-
const themeJsonPath =
|
|
2271
|
+
const themeJsonPath = path10.join(options.dir, "theme.json");
|
|
2102
2272
|
let baseVersion;
|
|
2103
2273
|
try {
|
|
2104
2274
|
const tj = JSON.parse(fs10.readFileSync(themeJsonPath, "utf-8"));
|
|
@@ -2117,7 +2287,7 @@ var installCommand = new import_commander8.Command("install").description(
|
|
|
2117
2287
|
console.log(` install tag: ${version}`);
|
|
2118
2288
|
console.log("Building locally (so worker can skip npm install)...");
|
|
2119
2289
|
try {
|
|
2120
|
-
(0,
|
|
2290
|
+
(0, import_child_process6.execSync)("npm run build", {
|
|
2121
2291
|
cwd: options.dir,
|
|
2122
2292
|
stdio: "inherit",
|
|
2123
2293
|
env: { ...process.env, NODE_ENV: "production" }
|
|
@@ -2128,7 +2298,7 @@ var installCommand = new import_commander8.Command("install").description(
|
|
|
2128
2298
|
);
|
|
2129
2299
|
process.exit(1);
|
|
2130
2300
|
}
|
|
2131
|
-
const distEntry =
|
|
2301
|
+
const distEntry = path10.join(options.dir, "dist", "theme.js");
|
|
2132
2302
|
if (!fs10.existsSync(distEntry)) {
|
|
2133
2303
|
console.error(
|
|
2134
2304
|
"\nBuild completed but dist/theme.js is missing. Check vite.config.ts entry / output filename."
|
|
@@ -2136,7 +2306,7 @@ var installCommand = new import_commander8.Command("install").description(
|
|
|
2136
2306
|
process.exit(1);
|
|
2137
2307
|
}
|
|
2138
2308
|
console.log("Packaging source + dist...");
|
|
2139
|
-
const zipPath =
|
|
2309
|
+
const zipPath = path10.join(
|
|
2140
2310
|
os4.tmpdir(),
|
|
2141
2311
|
`numu-theme-install-${Date.now()}.zip`
|
|
2142
2312
|
);
|
|
@@ -2250,9 +2420,9 @@ Timed out waiting for build (${options.pollTimeout}s). Run \`numu-theme status -
|
|
|
2250
2420
|
);
|
|
2251
2421
|
|
|
2252
2422
|
// src/commands/login.ts
|
|
2253
|
-
var
|
|
2423
|
+
var import_commander10 = require("commander");
|
|
2254
2424
|
var import_inquirer = __toESM(require("inquirer"));
|
|
2255
|
-
var loginCommand = new
|
|
2425
|
+
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
2426
|
if (options.apiUrl) {
|
|
2257
2427
|
saveConfig({ api_url: options.apiUrl });
|
|
2258
2428
|
console.log(`API URL set to: ${options.apiUrl}`);
|
|
@@ -2299,8 +2469,8 @@ Login failed: ${err.message}`);
|
|
|
2299
2469
|
});
|
|
2300
2470
|
|
|
2301
2471
|
// src/commands/status.ts
|
|
2302
|
-
var
|
|
2303
|
-
var statusCommand = new
|
|
2472
|
+
var import_commander11 = require("commander");
|
|
2473
|
+
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
2474
|
"--version <version_id>",
|
|
2305
2475
|
"Marketplace version ID returned by `numu-theme submit`"
|
|
2306
2476
|
).option("-w, --watch", "Poll until the build reaches a terminal state").option(
|
|
@@ -2427,13 +2597,13 @@ function formatBytes(n) {
|
|
|
2427
2597
|
}
|
|
2428
2598
|
|
|
2429
2599
|
// src/commands/doctor.ts
|
|
2430
|
-
var
|
|
2600
|
+
var import_commander12 = require("commander");
|
|
2431
2601
|
var fs11 = __toESM(require("fs"));
|
|
2432
|
-
var
|
|
2433
|
-
var doctorCommand = new
|
|
2602
|
+
var path11 = __toESM(require("path"));
|
|
2603
|
+
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
2604
|
let issues = 0;
|
|
2435
2605
|
let warnings = 0;
|
|
2436
|
-
const themeDir =
|
|
2606
|
+
const themeDir = path11.resolve(options.dir);
|
|
2437
2607
|
function ok(line) {
|
|
2438
2608
|
console.log(` \x1B[32m\u2713\x1B[0m ${line}`);
|
|
2439
2609
|
}
|
|
@@ -2446,8 +2616,8 @@ var doctorCommand = new import_commander11.Command("doctor").description("Diagno
|
|
|
2446
2616
|
console.log(` \x1B[31m\u2717\x1B[0m ${line}`);
|
|
2447
2617
|
}
|
|
2448
2618
|
console.log("\nProject");
|
|
2449
|
-
const themeJsonPath =
|
|
2450
|
-
const settingsPath =
|
|
2619
|
+
const themeJsonPath = path11.join(themeDir, "theme.json");
|
|
2620
|
+
const settingsPath = path11.join(themeDir, "settings_schema.json");
|
|
2451
2621
|
if (!fs11.existsSync(themeJsonPath)) {
|
|
2452
2622
|
fail(`No theme.json found in ${themeDir}`);
|
|
2453
2623
|
console.log(
|
|
@@ -2473,21 +2643,53 @@ var doctorCommand = new import_commander11.Command("doctor").description("Diagno
|
|
|
2473
2643
|
"src/index.ts"
|
|
2474
2644
|
];
|
|
2475
2645
|
const entry = entryCandidates.find(
|
|
2476
|
-
(p) => fs11.existsSync(
|
|
2646
|
+
(p) => fs11.existsSync(path11.join(themeDir, p))
|
|
2477
2647
|
);
|
|
2478
2648
|
if (!entry) {
|
|
2479
2649
|
fail(`No entry point found (expected one of: ${entryCandidates.join(", ")})`);
|
|
2480
2650
|
} 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);
|
|
2651
|
+
const src = fs11.readFileSync(path11.join(themeDir, entry), "utf-8");
|
|
2652
|
+
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
2653
|
if (exportsMount) ok(`${entry} exports mount(el, props)`);
|
|
2484
2654
|
else
|
|
2485
2655
|
fail(
|
|
2486
2656
|
`${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
2657
|
);
|
|
2658
|
+
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);
|
|
2659
|
+
if (exportsCreateApp) ok(`${entry} exports createApp(ctx) \u2014 SSR-capable`);
|
|
2660
|
+
else
|
|
2661
|
+
warn(
|
|
2662
|
+
`${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.`
|
|
2663
|
+
);
|
|
2664
|
+
}
|
|
2665
|
+
console.log("\nSSR toolchain");
|
|
2666
|
+
try {
|
|
2667
|
+
const pkg = JSON.parse(
|
|
2668
|
+
fs11.readFileSync(path11.join(themeDir, "package.json"), "utf-8")
|
|
2669
|
+
);
|
|
2670
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
2671
|
+
const checkMinor = (name, minMinor) => {
|
|
2672
|
+
const range = allDeps[name];
|
|
2673
|
+
if (!range) {
|
|
2674
|
+
warn(`${name} not in package.json \u2014 npm install it`);
|
|
2675
|
+
return;
|
|
2676
|
+
}
|
|
2677
|
+
const m = /(\d+)\.(\d+)\./.exec(range);
|
|
2678
|
+
if (m && Number(m[1]) === 0 && Number(m[2]) < minMinor) {
|
|
2679
|
+
warn(
|
|
2680
|
+
`${name}@${range} predates the SSR contract \u2014 bump to ^0.${minMinor}.0 for server rendering`
|
|
2681
|
+
);
|
|
2682
|
+
} else {
|
|
2683
|
+
ok(`${name}@${range}`);
|
|
2684
|
+
}
|
|
2685
|
+
};
|
|
2686
|
+
checkMinor("@numueg/theme-sdk", 3);
|
|
2687
|
+
checkMinor("@numueg/theme-plugin", 3);
|
|
2688
|
+
} catch {
|
|
2689
|
+
warn("Could not read package.json to verify SDK/plugin versions");
|
|
2488
2690
|
}
|
|
2489
2691
|
console.log("\nLocales");
|
|
2490
|
-
const localesDir =
|
|
2692
|
+
const localesDir = path11.join(themeDir, "locales");
|
|
2491
2693
|
if (!fs11.existsSync(localesDir)) {
|
|
2492
2694
|
warn(
|
|
2493
2695
|
"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 +2706,14 @@ var doctorCommand = new import_commander11.Command("doctor").description("Diagno
|
|
|
2504
2706
|
}
|
|
2505
2707
|
for (const f of localeFiles) {
|
|
2506
2708
|
try {
|
|
2507
|
-
JSON.parse(fs11.readFileSync(
|
|
2709
|
+
JSON.parse(fs11.readFileSync(path11.join(localesDir, f), "utf-8"));
|
|
2508
2710
|
} catch (err) {
|
|
2509
2711
|
fail(`locales/${f} is not valid JSON: ${err.message}`);
|
|
2510
2712
|
}
|
|
2511
2713
|
}
|
|
2512
2714
|
}
|
|
2513
2715
|
console.log("\nAssets");
|
|
2514
|
-
const assetsDir =
|
|
2716
|
+
const assetsDir = path11.join(themeDir, "assets");
|
|
2515
2717
|
if (!fs11.existsSync(assetsDir)) {
|
|
2516
2718
|
warn(
|
|
2517
2719
|
"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 +2734,13 @@ var doctorCommand = new import_commander11.Command("doctor").description("Diagno
|
|
|
2532
2734
|
fs11.readFileSync(themeJsonPath, "utf-8")
|
|
2533
2735
|
);
|
|
2534
2736
|
const presets = themeJson.presets ?? {};
|
|
2535
|
-
const sectionsDir =
|
|
2536
|
-
const schemaDir =
|
|
2737
|
+
const sectionsDir = path11.join(themeDir, "src", "sections");
|
|
2738
|
+
const schemaDir = path11.join(themeDir, "schemas", "sections");
|
|
2537
2739
|
const componentNames = fs11.existsSync(sectionsDir) ? new Set(
|
|
2538
|
-
fs11.readdirSync(sectionsDir).filter((f) => /\.(tsx|ts|jsx|js)$/.test(f)).map((f) =>
|
|
2740
|
+
fs11.readdirSync(sectionsDir).filter((f) => /\.(tsx|ts|jsx|js)$/.test(f)).map((f) => path11.basename(f, path11.extname(f)).toLowerCase())
|
|
2539
2741
|
) : /* @__PURE__ */ new Set();
|
|
2540
2742
|
const schemaNames = fs11.existsSync(schemaDir) ? new Set(
|
|
2541
|
-
fs11.readdirSync(schemaDir).filter((f) => f.endsWith(".json")).map((f) =>
|
|
2743
|
+
fs11.readdirSync(schemaDir).filter((f) => f.endsWith(".json")).map((f) => path11.basename(f, ".json").toLowerCase())
|
|
2542
2744
|
) : /* @__PURE__ */ new Set();
|
|
2543
2745
|
let missingRefs = 0;
|
|
2544
2746
|
for (const [presetName, presetVal] of Object.entries(presets)) {
|
|
@@ -2569,8 +2771,8 @@ var doctorCommand = new import_commander11.Command("doctor").description("Diagno
|
|
|
2569
2771
|
warn(`Could not parse theme.json presets: ${err.message}`);
|
|
2570
2772
|
}
|
|
2571
2773
|
console.log("\nBuild");
|
|
2572
|
-
const distJs =
|
|
2573
|
-
const distCss =
|
|
2774
|
+
const distJs = path11.join(themeDir, "dist", "theme.js");
|
|
2775
|
+
const distCss = path11.join(themeDir, "dist", "theme.css");
|
|
2574
2776
|
if (!fs11.existsSync(distJs)) {
|
|
2575
2777
|
warn("dist/theme.js not found \u2014 run `numu-theme build` first");
|
|
2576
2778
|
} else {
|
|
@@ -2586,6 +2788,16 @@ var doctorCommand = new import_commander11.Command("doctor").description("Diagno
|
|
|
2586
2788
|
} else {
|
|
2587
2789
|
ok("dist/theme.css present");
|
|
2588
2790
|
}
|
|
2791
|
+
const distServer = path11.join(themeDir, "dist", "theme.server.js");
|
|
2792
|
+
if (fs11.existsSync(distServer)) {
|
|
2793
|
+
ok(
|
|
2794
|
+
`dist/theme.server.js (${(fs11.statSync(distServer).size / 1024).toFixed(1)} KB) \u2014 SSR artifact present`
|
|
2795
|
+
);
|
|
2796
|
+
} else if (fs11.existsSync(distJs)) {
|
|
2797
|
+
warn(
|
|
2798
|
+
"dist/theme.server.js not found \u2014 last build produced a client-only theme (plugin < 0.3, federate:false, or SSR pass failed)"
|
|
2799
|
+
);
|
|
2800
|
+
}
|
|
2589
2801
|
console.log("\nAuth");
|
|
2590
2802
|
const config = loadConfig();
|
|
2591
2803
|
if (!config.token) {
|
|
@@ -2653,9 +2865,9 @@ var doctorCommand = new import_commander11.Command("doctor").description("Diagno
|
|
|
2653
2865
|
});
|
|
2654
2866
|
|
|
2655
2867
|
// src/commands/add-section.ts
|
|
2656
|
-
var
|
|
2868
|
+
var import_commander13 = require("commander");
|
|
2657
2869
|
var fs12 = __toESM(require("fs"));
|
|
2658
|
-
var
|
|
2870
|
+
var path12 = __toESM(require("path"));
|
|
2659
2871
|
|
|
2660
2872
|
// src/section-library/entries/hero-with-cta.ts
|
|
2661
2873
|
var heroWithCta = {
|
|
@@ -3700,7 +3912,7 @@ function findEntry(slug) {
|
|
|
3700
3912
|
}
|
|
3701
3913
|
|
|
3702
3914
|
// src/commands/add-section.ts
|
|
3703
|
-
var addSectionCommand = new
|
|
3915
|
+
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
3916
|
"--from-library <slug>",
|
|
3705
3917
|
"Copy from the built-in section library (run with --list to see options)"
|
|
3706
3918
|
).option("--list", "List the built-in section library and exit").option("-d, --dir <directory>", "Theme directory", ".").action(
|
|
@@ -3721,8 +3933,8 @@ var addSectionCommand = new import_commander12.Command("add-section").descriptio
|
|
|
3721
3933
|
);
|
|
3722
3934
|
process.exit(1);
|
|
3723
3935
|
}
|
|
3724
|
-
const themeDir =
|
|
3725
|
-
if (!fs12.existsSync(
|
|
3936
|
+
const themeDir = path12.resolve(process.cwd(), options.dir);
|
|
3937
|
+
if (!fs12.existsSync(path12.join(themeDir, "theme.json"))) {
|
|
3726
3938
|
console.error(
|
|
3727
3939
|
"No theme.json in this directory. Run from a theme project root."
|
|
3728
3940
|
);
|
|
@@ -3758,14 +3970,14 @@ var addSectionCommand = new import_commander12.Command("add-section").descriptio
|
|
|
3758
3970
|
]
|
|
3759
3971
|
};
|
|
3760
3972
|
}
|
|
3761
|
-
const componentPath =
|
|
3973
|
+
const componentPath = path12.join(themeDir, "src/sections", `${pascal}.tsx`);
|
|
3762
3974
|
ensureDirOf(componentPath);
|
|
3763
3975
|
if (fs12.existsSync(componentPath)) {
|
|
3764
3976
|
console.error(`Already exists: ${componentPath}`);
|
|
3765
3977
|
process.exit(1);
|
|
3766
3978
|
}
|
|
3767
3979
|
fs12.writeFileSync(componentPath, componentSource);
|
|
3768
|
-
const schemaPath =
|
|
3980
|
+
const schemaPath = path12.join(themeDir, "schemas/sections", `${slug}.json`);
|
|
3769
3981
|
ensureDirOf(schemaPath);
|
|
3770
3982
|
fs12.writeFileSync(schemaPath, JSON.stringify(schemaJson, null, 2));
|
|
3771
3983
|
tryWireMain(themeDir, slug, pascal);
|
|
@@ -3789,7 +4001,7 @@ function humanize(slug) {
|
|
|
3789
4001
|
return slug.split("-").filter(Boolean).map((p) => p[0].toUpperCase() + p.slice(1)).join(" ");
|
|
3790
4002
|
}
|
|
3791
4003
|
function ensureDirOf(p) {
|
|
3792
|
-
fs12.mkdirSync(
|
|
4004
|
+
fs12.mkdirSync(path12.dirname(p), { recursive: true });
|
|
3793
4005
|
}
|
|
3794
4006
|
function emptySectionStub(pascal) {
|
|
3795
4007
|
return `import type { SectionProps } from "@numueg/theme-sdk";
|
|
@@ -3807,7 +4019,7 @@ export default function ${pascal}({ settings }: SectionProps) {
|
|
|
3807
4019
|
`;
|
|
3808
4020
|
}
|
|
3809
4021
|
function tryWireMain(themeDir, slug, pascal) {
|
|
3810
|
-
const mainPath =
|
|
4022
|
+
const mainPath = path12.join(themeDir, "src/main.tsx");
|
|
3811
4023
|
if (!fs12.existsSync(mainPath)) return;
|
|
3812
4024
|
const src = fs12.readFileSync(mainPath, "utf-8");
|
|
3813
4025
|
if (src.includes(`./sections/${pascal}`)) return;
|
|
@@ -3848,7 +4060,7 @@ function tryWireMain(themeDir, slug, pascal) {
|
|
|
3848
4060
|
fs12.writeFileSync(mainPath, lines.join("\n"));
|
|
3849
4061
|
}
|
|
3850
4062
|
function tryAddToHomePreset(themeDir, slug) {
|
|
3851
|
-
const themeJsonPath =
|
|
4063
|
+
const themeJsonPath = path12.join(themeDir, "theme.json");
|
|
3852
4064
|
if (!fs12.existsSync(themeJsonPath)) return;
|
|
3853
4065
|
let parsed;
|
|
3854
4066
|
try {
|
|
@@ -3875,19 +4087,19 @@ function tryAddToHomePreset(themeDir, slug) {
|
|
|
3875
4087
|
}
|
|
3876
4088
|
|
|
3877
4089
|
// src/commands/add-block.ts
|
|
3878
|
-
var
|
|
4090
|
+
var import_commander14 = require("commander");
|
|
3879
4091
|
var fs13 = __toESM(require("fs"));
|
|
3880
|
-
var
|
|
3881
|
-
var addBlockCommand = new
|
|
3882
|
-
const themeDir =
|
|
3883
|
-
if (!fs13.existsSync(
|
|
4092
|
+
var path13 = __toESM(require("path"));
|
|
4093
|
+
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) => {
|
|
4094
|
+
const themeDir = path13.resolve(options.dir);
|
|
4095
|
+
if (!fs13.existsSync(path13.join(themeDir, "theme.json"))) {
|
|
3884
4096
|
console.error(`No theme.json in ${themeDir}.`);
|
|
3885
4097
|
process.exit(1);
|
|
3886
4098
|
}
|
|
3887
4099
|
const sectionSnake = toSnakeCase(section);
|
|
3888
4100
|
const blockSnake = toSnakeCase(name);
|
|
3889
4101
|
const blockHuman = humanize2(toPascalCase(name));
|
|
3890
|
-
const schemaPath =
|
|
4102
|
+
const schemaPath = path13.join(
|
|
3891
4103
|
themeDir,
|
|
3892
4104
|
"schemas",
|
|
3893
4105
|
"sections",
|
|
@@ -3930,7 +4142,7 @@ var addBlockCommand = new import_commander13.Command("add-block").description("S
|
|
|
3930
4142
|
`
|
|
3931
4143
|
\x1B[32m\u2713\x1B[0m Added block "${blockSnake}" to section "${sectionSnake}":`,
|
|
3932
4144
|
`
|
|
3933
|
-
- ${
|
|
4145
|
+
- ${path13.relative(themeDir, schemaPath)}`,
|
|
3934
4146
|
`
|
|
3935
4147
|
|
|
3936
4148
|
\x1B[33m\u26A0\x1B[0m The section component should iterate \`blockOrder\` and render each block by type.`,
|
|
@@ -3962,17 +4174,17 @@ function humanize2(name) {
|
|
|
3962
4174
|
}
|
|
3963
4175
|
|
|
3964
4176
|
// src/commands/pull.ts
|
|
3965
|
-
var
|
|
4177
|
+
var import_commander15 = require("commander");
|
|
3966
4178
|
var fs14 = __toESM(require("fs"));
|
|
3967
|
-
var
|
|
4179
|
+
var path14 = __toESM(require("path"));
|
|
3968
4180
|
var https2 = __toESM(require("https"));
|
|
3969
4181
|
var http2 = __toESM(require("http"));
|
|
3970
4182
|
async function downloadToFile(url, dest) {
|
|
3971
|
-
return new Promise((
|
|
4183
|
+
return new Promise((resolve10, reject) => {
|
|
3972
4184
|
const client = url.startsWith("https:") ? https2 : http2;
|
|
3973
4185
|
const req = client.get(url, (res) => {
|
|
3974
4186
|
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
3975
|
-
downloadToFile(res.headers.location, dest).then(
|
|
4187
|
+
downloadToFile(res.headers.location, dest).then(resolve10, reject);
|
|
3976
4188
|
return;
|
|
3977
4189
|
}
|
|
3978
4190
|
if (res.statusCode !== 200) {
|
|
@@ -3981,7 +4193,7 @@ async function downloadToFile(url, dest) {
|
|
|
3981
4193
|
}
|
|
3982
4194
|
const out = fs14.createWriteStream(dest);
|
|
3983
4195
|
res.pipe(out);
|
|
3984
|
-
out.on("finish", () => out.close(() =>
|
|
4196
|
+
out.on("finish", () => out.close(() => resolve10()));
|
|
3985
4197
|
out.on("error", reject);
|
|
3986
4198
|
});
|
|
3987
4199
|
req.on("error", reject);
|
|
@@ -4000,7 +4212,7 @@ async function unzip(zipPath, targetDir) {
|
|
|
4000
4212
|
});
|
|
4001
4213
|
}
|
|
4002
4214
|
}
|
|
4003
|
-
var pullCommand = new
|
|
4215
|
+
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
4216
|
"-d, --dir <directory>",
|
|
4005
4217
|
"Target directory (default: ./<theme-id>)"
|
|
4006
4218
|
).option(
|
|
@@ -4008,7 +4220,7 @@ var pullCommand = new import_commander14.Command("pull").description("Download a
|
|
|
4008
4220
|
"Specific version_string (default: latest)"
|
|
4009
4221
|
).option("--force", "Overwrite the target directory if it exists").action(
|
|
4010
4222
|
async (themeId, options) => {
|
|
4011
|
-
const targetDir =
|
|
4223
|
+
const targetDir = path14.resolve(options.dir || themeId);
|
|
4012
4224
|
if (fs14.existsSync(targetDir) && !options.force) {
|
|
4013
4225
|
const isEmpty = fs14.readdirSync(targetDir).length === 0;
|
|
4014
4226
|
if (!isEmpty) {
|
|
@@ -4055,7 +4267,7 @@ var pullCommand = new import_commander14.Command("pull").description("Download a
|
|
|
4055
4267
|
process.exit(1);
|
|
4056
4268
|
}
|
|
4057
4269
|
console.log(`Downloading ${theme.name || themeId} ${target.version_string}\u2026`);
|
|
4058
|
-
const tmpZip =
|
|
4270
|
+
const tmpZip = path14.join(
|
|
4059
4271
|
require("os").tmpdir(),
|
|
4060
4272
|
`numu-pull-${process.pid}-${Date.now()}.zip`
|
|
4061
4273
|
);
|
|
@@ -4072,28 +4284,28 @@ var pullCommand = new import_commander14.Command("pull").description("Download a
|
|
|
4072
4284
|
console.log(`\u2714 Pulled ${theme.slug || themeId}@${target.version_string} into ${targetDir}`);
|
|
4073
4285
|
console.log("");
|
|
4074
4286
|
console.log("Next steps:");
|
|
4075
|
-
console.log(` cd ${
|
|
4287
|
+
console.log(` cd ${path14.relative(process.cwd(), targetDir) || "."}`);
|
|
4076
4288
|
console.log(" npm install");
|
|
4077
4289
|
console.log(" numu-theme dev");
|
|
4078
4290
|
}
|
|
4079
4291
|
);
|
|
4080
4292
|
|
|
4081
4293
|
// src/commands/delete.ts
|
|
4082
|
-
var
|
|
4294
|
+
var import_commander16 = require("commander");
|
|
4083
4295
|
var readline = __toESM(require("readline"));
|
|
4084
4296
|
async function confirm(prompt) {
|
|
4085
4297
|
const rl = readline.createInterface({
|
|
4086
4298
|
input: process.stdin,
|
|
4087
4299
|
output: process.stdout
|
|
4088
4300
|
});
|
|
4089
|
-
return new Promise((
|
|
4301
|
+
return new Promise((resolve10) => {
|
|
4090
4302
|
rl.question(`${prompt} [y/N] `, (answer) => {
|
|
4091
4303
|
rl.close();
|
|
4092
|
-
|
|
4304
|
+
resolve10(/^y(es)?$/i.test(answer.trim()));
|
|
4093
4305
|
});
|
|
4094
4306
|
});
|
|
4095
4307
|
}
|
|
4096
|
-
var deleteCommand = new
|
|
4308
|
+
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
4309
|
async (themeId, options) => {
|
|
4098
4310
|
const isVersion = Boolean(options.version);
|
|
4099
4311
|
const scope = isVersion ? `version ${options.version} of ${themeId}` : `the entire theme ${themeId}`;
|
|
@@ -4106,8 +4318,8 @@ var deleteCommand = new import_commander15.Command("delete").description("Delete
|
|
|
4106
4318
|
return;
|
|
4107
4319
|
}
|
|
4108
4320
|
}
|
|
4109
|
-
const
|
|
4110
|
-
const res = await apiRequest("DELETE",
|
|
4321
|
+
const path16 = isVersion ? `/api/v1/marketplace/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(options.version)}` : `/api/v1/marketplace/themes/${encodeURIComponent(themeId)}`;
|
|
4322
|
+
const res = await apiRequest("DELETE", path16);
|
|
4111
4323
|
if (res.status === 200 || res.status === 204) {
|
|
4112
4324
|
console.log(`\u2714 Deleted ${scope}.`);
|
|
4113
4325
|
return;
|
|
@@ -4126,9 +4338,9 @@ var deleteCommand = new import_commander15.Command("delete").description("Delete
|
|
|
4126
4338
|
);
|
|
4127
4339
|
|
|
4128
4340
|
// src/commands/migrate.ts
|
|
4129
|
-
var
|
|
4341
|
+
var import_commander17 = require("commander");
|
|
4130
4342
|
var fs15 = __toESM(require("fs"));
|
|
4131
|
-
var
|
|
4343
|
+
var path15 = __toESM(require("path"));
|
|
4132
4344
|
var import_chalk = __toESM(require("chalk"));
|
|
4133
4345
|
var V2_HOOK_PATTERNS = [
|
|
4134
4346
|
{
|
|
@@ -4226,14 +4438,14 @@ function findSectionFiles(dir) {
|
|
|
4226
4438
|
if (!fs15.existsSync(dir)) return [];
|
|
4227
4439
|
const out = [];
|
|
4228
4440
|
for (const entry of fs15.readdirSync(dir, { withFileTypes: true })) {
|
|
4229
|
-
const full =
|
|
4441
|
+
const full = path15.join(dir, entry.name);
|
|
4230
4442
|
if (entry.isDirectory()) out.push(...findSectionFiles(full));
|
|
4231
4443
|
else if (entry.isFile() && /\.(tsx|ts)$/.test(entry.name)) out.push(full);
|
|
4232
4444
|
}
|
|
4233
4445
|
return out;
|
|
4234
4446
|
}
|
|
4235
4447
|
function sectionTypeFromFilename(filename) {
|
|
4236
|
-
const base =
|
|
4448
|
+
const base = path15.basename(filename).replace(/\.(tsx|ts)$/, "");
|
|
4237
4449
|
return base.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[^a-zA-Z0-9-]+/g, "-").toLowerCase();
|
|
4238
4450
|
}
|
|
4239
4451
|
function makeV2Bridge() {
|
|
@@ -4733,7 +4945,7 @@ function makeTemplates() {
|
|
|
4733
4945
|
`
|
|
4734
4946
|
};
|
|
4735
4947
|
}
|
|
4736
|
-
var migrateCommand = new
|
|
4948
|
+
var migrateCommand = new import_commander17.Command("migrate").description("Scaffold a V3 theme project from a V2 theme directory").argument(
|
|
4737
4949
|
"<v2-path>",
|
|
4738
4950
|
"Path to the V2 theme directory (e.g. ../numu-egyptian-bazaar/src/themes/empire)"
|
|
4739
4951
|
).option(
|
|
@@ -4744,15 +4956,15 @@ var migrateCommand = new import_commander16.Command("migrate").description("Scaf
|
|
|
4744
4956
|
"Override the display name (default: title-cased v2 id)"
|
|
4745
4957
|
).action(
|
|
4746
4958
|
async (v2Path, options) => {
|
|
4747
|
-
const absV2Path =
|
|
4959
|
+
const absV2Path = path15.resolve(process.cwd(), v2Path);
|
|
4748
4960
|
if (!fs15.existsSync(absV2Path) || !fs15.statSync(absV2Path).isDirectory()) {
|
|
4749
4961
|
console.error(import_chalk.default.red(`V2 path does not exist or is not a directory: ${absV2Path}`));
|
|
4750
4962
|
process.exit(1);
|
|
4751
4963
|
}
|
|
4752
|
-
const v2Id =
|
|
4964
|
+
const v2Id = path15.basename(absV2Path).toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
4753
4965
|
const themeId = `${v2Id}-v3`;
|
|
4754
4966
|
const displayName = options.name ?? v2Id.charAt(0).toUpperCase() + v2Id.slice(1).replace(/-/g, " ") + " (V3)";
|
|
4755
|
-
const outDir =
|
|
4967
|
+
const outDir = path15.resolve(
|
|
4756
4968
|
process.cwd(),
|
|
4757
4969
|
options.out ?? `${v2Id}-engine-V3`
|
|
4758
4970
|
);
|
|
@@ -4773,25 +4985,25 @@ Migrating V2 \u2192 V3:`));
|
|
|
4773
4985
|
"schemas/blocks",
|
|
4774
4986
|
"templates"
|
|
4775
4987
|
]) {
|
|
4776
|
-
fs15.mkdirSync(
|
|
4988
|
+
fs15.mkdirSync(path15.join(outDir, d), { recursive: true });
|
|
4777
4989
|
}
|
|
4778
4990
|
fs15.writeFileSync(
|
|
4779
|
-
|
|
4991
|
+
path15.join(outDir, "src/v2-bridge", "index.tsx"),
|
|
4780
4992
|
makeV2Bridge()
|
|
4781
4993
|
);
|
|
4782
|
-
const v2Styles =
|
|
4994
|
+
const v2Styles = path15.join(absV2Path, "styles.css");
|
|
4783
4995
|
if (fs15.existsSync(v2Styles)) {
|
|
4784
|
-
fs15.copyFileSync(v2Styles,
|
|
4996
|
+
fs15.copyFileSync(v2Styles, path15.join(outDir, "styles.css"));
|
|
4785
4997
|
console.log(import_chalk.default.green(" \u2713 Copied styles.css"));
|
|
4786
4998
|
} else {
|
|
4787
4999
|
fs15.writeFileSync(
|
|
4788
|
-
|
|
5000
|
+
path15.join(outDir, "styles.css"),
|
|
4789
5001
|
`/* ${displayName} styles */
|
|
4790
5002
|
`
|
|
4791
5003
|
);
|
|
4792
5004
|
console.log(import_chalk.default.yellow(" \u26A0 No styles.css found \u2014 created an empty one"));
|
|
4793
5005
|
}
|
|
4794
|
-
const v2SectionsDir =
|
|
5006
|
+
const v2SectionsDir = path15.join(absV2Path, "sections");
|
|
4795
5007
|
const sectionFiles = fs15.existsSync(v2SectionsDir) ? findSectionFiles(v2SectionsDir) : [];
|
|
4796
5008
|
const sectionTypes = [];
|
|
4797
5009
|
const allNotes = [];
|
|
@@ -4802,18 +5014,18 @@ Migrating V2 \u2192 V3:`));
|
|
|
4802
5014
|
const v2Source = fs15.readFileSync(file, "utf-8");
|
|
4803
5015
|
const notes = migrationNotesFor(v2Source);
|
|
4804
5016
|
allNotes.push({ file, type, notes });
|
|
4805
|
-
const relativeV2 =
|
|
5017
|
+
const relativeV2 = path15.relative(process.cwd(), file);
|
|
4806
5018
|
const header = adapterCommentBlock(notes, relativeV2);
|
|
4807
5019
|
const rewritten = rewriteV2Imports(v2Source);
|
|
4808
5020
|
const ported = `${header}
|
|
4809
5021
|
|
|
4810
5022
|
${rewritten}`;
|
|
4811
5023
|
fs15.writeFileSync(
|
|
4812
|
-
|
|
5024
|
+
path15.join(outDir, "src/sections", `${type}.tsx`),
|
|
4813
5025
|
ported
|
|
4814
5026
|
);
|
|
4815
5027
|
fs15.writeFileSync(
|
|
4816
|
-
|
|
5028
|
+
path15.join(outDir, "schemas/sections", `${type}.json`),
|
|
4817
5029
|
JSON.stringify(
|
|
4818
5030
|
{
|
|
4819
5031
|
type,
|
|
@@ -4827,38 +5039,38 @@ ${rewritten}`;
|
|
|
4827
5039
|
}
|
|
4828
5040
|
console.log(import_chalk.default.green(` \u2713 Ported ${sectionTypes.length} section file(s)`));
|
|
4829
5041
|
fs15.writeFileSync(
|
|
4830
|
-
|
|
5042
|
+
path15.join(outDir, "theme.json"),
|
|
4831
5043
|
JSON.stringify(makeThemeJson(themeId, displayName, sectionTypes), null, 2)
|
|
4832
5044
|
);
|
|
4833
5045
|
fs15.writeFileSync(
|
|
4834
|
-
|
|
5046
|
+
path15.join(outDir, "settings_schema.json"),
|
|
4835
5047
|
JSON.stringify(makeSettingsSchema(), null, 2)
|
|
4836
5048
|
);
|
|
4837
5049
|
fs15.writeFileSync(
|
|
4838
|
-
|
|
5050
|
+
path15.join(outDir, "src/main.tsx"),
|
|
4839
5051
|
makeMainTsx(themeId, displayName, sectionTypes)
|
|
4840
5052
|
);
|
|
4841
5053
|
fs15.writeFileSync(
|
|
4842
|
-
|
|
5054
|
+
path15.join(outDir, "vite.config.ts"),
|
|
4843
5055
|
makeViteConfig()
|
|
4844
5056
|
);
|
|
4845
5057
|
fs15.writeFileSync(
|
|
4846
|
-
|
|
5058
|
+
path15.join(outDir, "package.json"),
|
|
4847
5059
|
JSON.stringify(makePackageJson(themeId, displayName), null, 2)
|
|
4848
5060
|
);
|
|
4849
5061
|
fs15.writeFileSync(
|
|
4850
|
-
|
|
5062
|
+
path15.join(outDir, "tsconfig.json"),
|
|
4851
5063
|
JSON.stringify(makeTsConfig(), null, 2)
|
|
4852
5064
|
);
|
|
4853
5065
|
fs15.writeFileSync(
|
|
4854
|
-
|
|
5066
|
+
path15.join(outDir, "index.html"),
|
|
4855
5067
|
makeIndexHtml(displayName)
|
|
4856
5068
|
);
|
|
4857
5069
|
const tpl = makeTemplates();
|
|
4858
|
-
fs15.writeFileSync(
|
|
4859
|
-
fs15.writeFileSync(
|
|
5070
|
+
fs15.writeFileSync(path15.join(outDir, "templates/error.html"), tpl.error);
|
|
5071
|
+
fs15.writeFileSync(path15.join(outDir, "templates/loading.html"), tpl.loading);
|
|
4860
5072
|
fs15.writeFileSync(
|
|
4861
|
-
|
|
5073
|
+
path15.join(outDir, ".gitignore"),
|
|
4862
5074
|
"node_modules/\ndist/\n.DS_Store\n"
|
|
4863
5075
|
);
|
|
4864
5076
|
console.log(import_chalk.default.green(" \u2713 Wrote scaffold files\n"));
|
|
@@ -4879,7 +5091,7 @@ ${rewritten}`;
|
|
|
4879
5091
|
console.log();
|
|
4880
5092
|
}
|
|
4881
5093
|
console.log(import_chalk.default.bold("\nNext steps:"));
|
|
4882
|
-
console.log(` ${import_chalk.default.cyan("cd")} ${
|
|
5094
|
+
console.log(` ${import_chalk.default.cyan("cd")} ${path15.relative(process.cwd(), outDir)}`);
|
|
4883
5095
|
console.log(` ${import_chalk.default.cyan("npm install")}`);
|
|
4884
5096
|
console.log(` ${import_chalk.default.cyan("npm run dev")} ${import_chalk.default.dim("# dev preview on :5173")}`);
|
|
4885
5097
|
console.log(` ${import_chalk.default.cyan("npx numu-theme build")} ${import_chalk.default.dim("# validate + build")}`);
|
|
@@ -4891,13 +5103,14 @@ ${import_chalk.default.dim("Open each src/sections/*.tsx and follow the ADAPTER
|
|
|
4891
5103
|
);
|
|
4892
5104
|
|
|
4893
5105
|
// src/index.ts
|
|
4894
|
-
var program = new
|
|
5106
|
+
var program = new import_commander18.Command();
|
|
4895
5107
|
program.name("numu-theme").description("CLI for developing, validating, building, and publishing NUMU themes").version("0.1.0");
|
|
4896
5108
|
program.addCommand(initCommand);
|
|
4897
5109
|
program.addCommand(devCommand);
|
|
4898
5110
|
program.addCommand(checkCommand);
|
|
4899
5111
|
program.addCommand(lintCommand);
|
|
4900
5112
|
program.addCommand(buildCommand);
|
|
5113
|
+
program.addCommand(verifyCommand);
|
|
4901
5114
|
program.addCommand(pushCommand);
|
|
4902
5115
|
program.addCommand(submitCommand);
|
|
4903
5116
|
program.addCommand(installCommand);
|