@feeef.dev/cli 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +129 -35
- package/dist/bin.js +224 -58
- package/dist/bin.js.map +1 -1
- package/dist/index.js +224 -58
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/scaffolds/blank/AGENTS.md +14 -11
- package/scaffolds/blank/README.md +8 -4
- package/scaffolds/blank/docs/00-INDEX.md +6 -4
- package/scaffolds/blank/docs/04-WORKFLOW.md +29 -24
- package/scaffolds/blank/docs/05-ANTI-PATTERNS.md +3 -1
- package/scaffolds/blank/package.json +3 -2
- package/vendor/template-kit/bin/feeef-template.mjs +32 -0
- package/vendor/template-kit/lib/build.mjs +22 -6
- package/vendor/template-kit/lib/library.mjs +2 -2
- package/vendor/template-kit/lib/smoke.mjs +258 -0
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
import { createRequire } from "module";
|
|
2
3
|
import { Command } from "commander";
|
|
3
4
|
|
|
4
5
|
// src/client.ts
|
|
@@ -89,22 +90,22 @@ function createClient(opts = {}) {
|
|
|
89
90
|
if (requireAuth && !token) {
|
|
90
91
|
throw new Error("Not signed in. Run: feeef signin");
|
|
91
92
|
}
|
|
92
|
-
const
|
|
93
|
+
const http3 = createHttp(baseURL, token);
|
|
93
94
|
const ff = new FeeeF({
|
|
94
95
|
apiKey: token || "",
|
|
95
96
|
baseURL,
|
|
96
|
-
client:
|
|
97
|
+
client: http3,
|
|
97
98
|
cache: false
|
|
98
99
|
});
|
|
99
|
-
return { ff, http:
|
|
100
|
+
return { ff, http: http3, cfg, baseURL, token };
|
|
100
101
|
}
|
|
101
102
|
async function signinWithPassword(input) {
|
|
102
103
|
const baseURL = (input.apiUrl || defaultApiUrl()).replace(/\/$/, "");
|
|
103
|
-
const
|
|
104
|
+
const http3 = createHttp(baseURL, void 0);
|
|
104
105
|
const ff = new FeeeF({
|
|
105
106
|
apiKey: "",
|
|
106
107
|
baseURL,
|
|
107
|
-
client:
|
|
108
|
+
client: http3,
|
|
108
109
|
cache: false
|
|
109
110
|
});
|
|
110
111
|
const auth = await ff.users.signin({
|
|
@@ -604,14 +605,14 @@ function resolveThemeDir(dirArg) {
|
|
|
604
605
|
if (dirArg) {
|
|
605
606
|
const abs = path2.resolve(dirArg);
|
|
606
607
|
if (!fs2.existsSync(abs)) {
|
|
607
|
-
throw new Error(`
|
|
608
|
+
throw new Error(`Template directory not found: ${abs}`);
|
|
608
609
|
}
|
|
609
610
|
return findThemeRoot(abs) || abs;
|
|
610
611
|
}
|
|
611
612
|
const fromCwd = findThemeRoot(process.cwd());
|
|
612
613
|
if (fromCwd) return fromCwd;
|
|
613
614
|
throw new Error(
|
|
614
|
-
"No
|
|
615
|
+
"No template found. Run inside a template folder (feeef.template.json) or: feeef template init"
|
|
615
616
|
);
|
|
616
617
|
}
|
|
617
618
|
|
|
@@ -938,6 +939,13 @@ async function loadBuild() {
|
|
|
938
939
|
}
|
|
939
940
|
return import(pathToFileURL(buildPath).href);
|
|
940
941
|
}
|
|
942
|
+
async function loadSmoke() {
|
|
943
|
+
const smokePath = path5.join(templateKitRoot(), "lib", "smoke.mjs");
|
|
944
|
+
if (!fs4.existsSync(smokePath)) {
|
|
945
|
+
throw new Error(`template-kit smoke missing at ${smokePath}`);
|
|
946
|
+
}
|
|
947
|
+
return import(pathToFileURL(smokePath).href);
|
|
948
|
+
}
|
|
941
949
|
async function loadUnpack() {
|
|
942
950
|
const unpackPath = path5.join(templateKitRoot(), "lib", "unpack.mjs");
|
|
943
951
|
if (!fs4.existsSync(unpackPath)) {
|
|
@@ -957,7 +965,7 @@ async function runTemplateBuild(dirArg, opts = {}) {
|
|
|
957
965
|
const srcDir = resolveThemeDir(dirArg);
|
|
958
966
|
const outPath = path6.join(srcDir, "dist", "data.json");
|
|
959
967
|
const s = spinner2();
|
|
960
|
-
s.start(opts.checkOnly ? "Checking
|
|
968
|
+
s.start(opts.checkOnly ? "Checking template\u2026" : "Building template\u2026");
|
|
961
969
|
const { buildTemplate } = await loadBuild();
|
|
962
970
|
const result = buildTemplate({
|
|
963
971
|
srcDir,
|
|
@@ -1010,7 +1018,7 @@ function assertStorefrontPath() {
|
|
|
1010
1018
|
throw new Error(
|
|
1011
1019
|
[
|
|
1012
1020
|
"Lithium storefront not found.",
|
|
1013
|
-
"Set it once (
|
|
1021
|
+
"Set it once (template repos do not need storefront source inside them):",
|
|
1014
1022
|
" feeef config set storefrontPath /path/to/storefront",
|
|
1015
1023
|
"or:",
|
|
1016
1024
|
" export FEEEF_STOREFRONT_PATH=/path/to/storefront"
|
|
@@ -1020,6 +1028,85 @@ function assertStorefrontPath() {
|
|
|
1020
1028
|
return root;
|
|
1021
1029
|
}
|
|
1022
1030
|
|
|
1031
|
+
// src/dev-error-server.ts
|
|
1032
|
+
import http2 from "http";
|
|
1033
|
+
|
|
1034
|
+
// src/dev-error-format.ts
|
|
1035
|
+
function formatFeeefLiveErrorLine(body) {
|
|
1036
|
+
const debug = body.debug || {};
|
|
1037
|
+
const kind = String(body.kind || "error");
|
|
1038
|
+
const message = String(body.message || "unknown");
|
|
1039
|
+
const lines = [
|
|
1040
|
+
`[feeef:custom-live] ${kind}`,
|
|
1041
|
+
debug.sourcePath ? ` path: ${debug.sourcePath}` : null,
|
|
1042
|
+
debug.instanceId ? ` instanceId: ${debug.instanceId}` : null,
|
|
1043
|
+
debug.title ? ` title: ${debug.title}` : null,
|
|
1044
|
+
debug.type ? ` type: ${debug.type}` : null,
|
|
1045
|
+
` message: ${message}`,
|
|
1046
|
+
body.props && typeof body.props === "object" ? ` props: ${JSON.stringify(body.props)}` : null,
|
|
1047
|
+
body.href ? ` href: ${body.href}` : null
|
|
1048
|
+
].filter(Boolean);
|
|
1049
|
+
return lines.join("\n");
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// src/dev-error-server.ts
|
|
1053
|
+
async function startDevErrorServer(opts) {
|
|
1054
|
+
const preferred = opts?.preferredPort ?? (Number(process.env["FEEEF_DEV_ERROR_PORT"]) || 0);
|
|
1055
|
+
const server = http2.createServer((req, res) => {
|
|
1056
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1057
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
1058
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
1059
|
+
if (req.method === "OPTIONS") {
|
|
1060
|
+
res.writeHead(204);
|
|
1061
|
+
res.end();
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
if (req.method === "GET" && (req.url === "/" || req.url === "/health")) {
|
|
1065
|
+
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
1066
|
+
res.end("feeef-dev-error-ok");
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
if (req.method === "POST" && req.url?.startsWith("/feeef-error")) {
|
|
1070
|
+
const chunks = [];
|
|
1071
|
+
req.on("data", (c) => chunks.push(c));
|
|
1072
|
+
req.on("end", () => {
|
|
1073
|
+
try {
|
|
1074
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
1075
|
+
const body = JSON.parse(raw);
|
|
1076
|
+
const line = formatFeeefLiveErrorLine(body);
|
|
1077
|
+
console.error("\n" + line + "\n");
|
|
1078
|
+
res.writeHead(204);
|
|
1079
|
+
res.end();
|
|
1080
|
+
} catch (e) {
|
|
1081
|
+
console.error("[feeef:custom-live] bad error payload", e);
|
|
1082
|
+
res.writeHead(400);
|
|
1083
|
+
res.end("bad json");
|
|
1084
|
+
}
|
|
1085
|
+
});
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
res.writeHead(404);
|
|
1089
|
+
res.end("not found");
|
|
1090
|
+
});
|
|
1091
|
+
await new Promise((resolve, reject) => {
|
|
1092
|
+
server.once("error", reject);
|
|
1093
|
+
server.listen(preferred, "127.0.0.1", () => resolve());
|
|
1094
|
+
});
|
|
1095
|
+
const addr = server.address();
|
|
1096
|
+
if (!addr || typeof addr === "string") {
|
|
1097
|
+
throw new Error("Failed to bind feeef dev error server");
|
|
1098
|
+
}
|
|
1099
|
+
const port = addr.port;
|
|
1100
|
+
const url = `http://127.0.0.1:${port}`;
|
|
1101
|
+
return {
|
|
1102
|
+
port,
|
|
1103
|
+
url,
|
|
1104
|
+
close: () => new Promise((resolve) => {
|
|
1105
|
+
server.close(() => resolve());
|
|
1106
|
+
})
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1023
1110
|
// src/commands/template/dev.ts
|
|
1024
1111
|
function killChildren(children) {
|
|
1025
1112
|
for (const c of children) {
|
|
@@ -1053,7 +1140,7 @@ async function resolveBoundStore(srcDir) {
|
|
|
1053
1140
|
});
|
|
1054
1141
|
if (!storeRef) {
|
|
1055
1142
|
throw new Error(
|
|
1056
|
-
"No store linked. Run: feeef use <slug>\n(Remote preview pushes a draft to your store \u2014 live
|
|
1143
|
+
"No store linked. Run: feeef use <slug>\n(Remote preview pushes a draft to your store \u2014 live template stays unchanged.)"
|
|
1057
1144
|
);
|
|
1058
1145
|
}
|
|
1059
1146
|
const { ff } = createClient({ requireAuth: true });
|
|
@@ -1070,10 +1157,21 @@ async function runRemoteDraftPreview(srcDir) {
|
|
|
1070
1157
|
const { storeId, slug } = await resolveBoundStore(srcDir);
|
|
1071
1158
|
const { ff } = createClient({ requireAuth: true });
|
|
1072
1159
|
const s = spinner2();
|
|
1073
|
-
s.start("Building
|
|
1160
|
+
s.start("Building template\u2026");
|
|
1074
1161
|
const { buildTemplate } = await loadBuild();
|
|
1075
1162
|
buildTemplate({ srcDir, outPath, write: true });
|
|
1076
1163
|
s.stop("dist ready");
|
|
1164
|
+
const errorServer = await startDevErrorServer();
|
|
1165
|
+
info(`Component errors \u2192 ${pc.cyan(errorServer.url)} (terminal)`);
|
|
1166
|
+
const withDevLog = (previewUrl) => {
|
|
1167
|
+
try {
|
|
1168
|
+
const u = new URL(previewUrl);
|
|
1169
|
+
u.searchParams.set("feeef_dev_log", errorServer.url);
|
|
1170
|
+
return u.toString();
|
|
1171
|
+
} catch {
|
|
1172
|
+
return previewUrl;
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1077
1175
|
let lastPreviewUrl;
|
|
1078
1176
|
let pushInFlight = false;
|
|
1079
1177
|
let pushQueued = false;
|
|
@@ -1090,8 +1188,8 @@ async function runRemoteDraftPreview(srcDir) {
|
|
|
1090
1188
|
}
|
|
1091
1189
|
const data = JSON.parse(fs6.readFileSync(outPath, "utf8"));
|
|
1092
1190
|
const result = await ff.stores.putTemplatePreview(storeId, { data });
|
|
1093
|
-
lastPreviewUrl = result.previewUrl;
|
|
1094
|
-
ok(`Draft preview updated \u2192 ${pc.cyan(
|
|
1191
|
+
lastPreviewUrl = withDevLog(result.previewUrl);
|
|
1192
|
+
ok(`Draft preview updated \u2192 ${pc.cyan(lastPreviewUrl)}`);
|
|
1095
1193
|
} catch (e) {
|
|
1096
1194
|
fail(e);
|
|
1097
1195
|
} finally {
|
|
@@ -1111,12 +1209,13 @@ async function runRemoteDraftPreview(srcDir) {
|
|
|
1111
1209
|
}
|
|
1112
1210
|
note2(
|
|
1113
1211
|
[
|
|
1114
|
-
`
|
|
1115
|
-
`Store
|
|
1116
|
-
`Preview
|
|
1212
|
+
`Template ${srcDir}`,
|
|
1213
|
+
`Store ${slug} (${storeId})`,
|
|
1214
|
+
`Preview ${lastPreviewUrl ?? "(pending)"}`,
|
|
1117
1215
|
"",
|
|
1118
1216
|
"Live customers still see metadata.templateData.",
|
|
1119
1217
|
"Exit preview in the browser: banner \u2192 Exit, or /?preview=0",
|
|
1218
|
+
`CustomLive errors print here via ${errorServer.url}`,
|
|
1120
1219
|
"Stopping the CLI clears the draft."
|
|
1121
1220
|
].join("\n"),
|
|
1122
1221
|
"Remote draft preview"
|
|
@@ -1144,6 +1243,10 @@ async function runRemoteDraftPreview(srcDir) {
|
|
|
1144
1243
|
watching = false;
|
|
1145
1244
|
debouncedPush.cancel();
|
|
1146
1245
|
killChildren([watch]);
|
|
1246
|
+
try {
|
|
1247
|
+
await errorServer.close();
|
|
1248
|
+
} catch {
|
|
1249
|
+
}
|
|
1147
1250
|
try {
|
|
1148
1251
|
await ff.stores.clearTemplatePreview(storeId);
|
|
1149
1252
|
info("Cleared draft preview on server");
|
|
@@ -1173,6 +1276,8 @@ async function runLocalStorefrontPreview(srcDir, opts) {
|
|
|
1173
1276
|
process.env["FEEEF_STOREFRONT_PATH"] = path8.resolve(opts.storefront);
|
|
1174
1277
|
}
|
|
1175
1278
|
const storefrontRoot = assertStorefrontPath();
|
|
1279
|
+
const errorServer = await startDevErrorServer();
|
|
1280
|
+
info(`Component errors \u2192 ${pc.cyan(errorServer.url)} (terminal)`);
|
|
1176
1281
|
const cfg = loadConfig();
|
|
1177
1282
|
const storeRef = resolveStoreIdFromProject({
|
|
1178
1283
|
themeRoot: srcDir,
|
|
@@ -1199,16 +1304,26 @@ async function runLocalStorefrontPreview(srcDir, opts) {
|
|
|
1199
1304
|
}
|
|
1200
1305
|
}
|
|
1201
1306
|
const s = spinner2();
|
|
1202
|
-
s.start("Building
|
|
1307
|
+
s.start("Building template\u2026");
|
|
1203
1308
|
const { buildTemplate } = await loadBuild();
|
|
1204
1309
|
buildTemplate({ srcDir, outPath, write: true });
|
|
1205
1310
|
s.stop("dist ready");
|
|
1206
1311
|
const previewHost = slug ? `http://${slug}.localhost.feeef.org:${port}` : `http://localhost:${port}`;
|
|
1312
|
+
const previewWithLog = (() => {
|
|
1313
|
+
try {
|
|
1314
|
+
const u = new URL(previewHost);
|
|
1315
|
+
u.searchParams.set("feeef_dev_log", errorServer.url);
|
|
1316
|
+
return u.toString();
|
|
1317
|
+
} catch {
|
|
1318
|
+
return previewHost;
|
|
1319
|
+
}
|
|
1320
|
+
})();
|
|
1207
1321
|
note2(
|
|
1208
1322
|
[
|
|
1209
|
-
`
|
|
1323
|
+
`Template ${srcDir}`,
|
|
1210
1324
|
`Storefront ${storefrontRoot}`,
|
|
1211
|
-
`Preview
|
|
1325
|
+
`Preview ${previewWithLog}`,
|
|
1326
|
+
`Errors ${errorServer.url}`,
|
|
1212
1327
|
"Internal --local mode (not for public docs)."
|
|
1213
1328
|
].join("\n"),
|
|
1214
1329
|
"Local Next preview"
|
|
@@ -1217,6 +1332,7 @@ async function runLocalStorefrontPreview(srcDir, opts) {
|
|
|
1217
1332
|
...process.env,
|
|
1218
1333
|
FEEEF_TEMPLATE_DATA_PATH: outPath,
|
|
1219
1334
|
FEEEF_TEMPLATE_LIBRARY_PATH: libraryPath,
|
|
1335
|
+
FEEEF_DEV_LOG_URL: errorServer.url,
|
|
1220
1336
|
PORT: String(port)
|
|
1221
1337
|
};
|
|
1222
1338
|
if (slug) {
|
|
@@ -1235,7 +1351,7 @@ async function runLocalStorefrontPreview(srcDir, opts) {
|
|
|
1235
1351
|
const children = [watch, next];
|
|
1236
1352
|
const shutdown = (code = 0) => {
|
|
1237
1353
|
killChildren(children);
|
|
1238
|
-
process.exit(code);
|
|
1354
|
+
void errorServer.close().finally(() => process.exit(code));
|
|
1239
1355
|
};
|
|
1240
1356
|
process.on("SIGINT", () => shutdown(0));
|
|
1241
1357
|
process.on("SIGTERM", () => shutdown(0));
|
|
@@ -1243,7 +1359,7 @@ async function runLocalStorefrontPreview(srcDir, opts) {
|
|
|
1243
1359
|
watch.on("exit", (code) => {
|
|
1244
1360
|
if (code && code !== 0) shutdown(code);
|
|
1245
1361
|
});
|
|
1246
|
-
ok(`Open ${pc.cyan(
|
|
1362
|
+
ok(`Open ${pc.cyan(previewWithLog)}`);
|
|
1247
1363
|
outro2("Watching \u2014 Ctrl+C to stop");
|
|
1248
1364
|
await new Promise(() => {
|
|
1249
1365
|
});
|
|
@@ -1324,15 +1440,16 @@ function packageNameFromFolder(name) {
|
|
|
1324
1440
|
return name.toLowerCase();
|
|
1325
1441
|
}
|
|
1326
1442
|
var DEFAULT_PACKAGE_JSON = {
|
|
1327
|
-
name: "feeef-
|
|
1443
|
+
name: "feeef-template",
|
|
1328
1444
|
private: true,
|
|
1329
1445
|
version: "0.1.0",
|
|
1330
|
-
description: "Feeef Lithium
|
|
1446
|
+
description: "Feeef Lithium template",
|
|
1331
1447
|
type: "module",
|
|
1332
1448
|
engines: { node: ">=18" },
|
|
1333
1449
|
scripts: {
|
|
1334
1450
|
build: "feeef template build",
|
|
1335
1451
|
check: "feeef template check",
|
|
1452
|
+
test: "feeef template smoke",
|
|
1336
1453
|
dev: "feeef template dev",
|
|
1337
1454
|
publish: "feeef template publish",
|
|
1338
1455
|
typecheck: "tsc --noEmit"
|
|
@@ -1448,7 +1565,9 @@ function ensureNodePackage(dest, folderName) {
|
|
|
1448
1565
|
pkg.scripts = { ...DEFAULT_PACKAGE_JSON.scripts };
|
|
1449
1566
|
}
|
|
1450
1567
|
const scripts = pkg.scripts;
|
|
1451
|
-
|
|
1568
|
+
for (const [key, value] of Object.entries(DEFAULT_PACKAGE_JSON.scripts)) {
|
|
1569
|
+
if (!scripts[key]) scripts[key] = value;
|
|
1570
|
+
}
|
|
1452
1571
|
const devDeps = pkg.devDependencies && typeof pkg.devDependencies === "object" ? pkg.devDependencies : {};
|
|
1453
1572
|
for (const [name, version] of Object.entries(EDITOR_DEV_DEPS)) {
|
|
1454
1573
|
if (!devDeps[name]) devDeps[name] = version;
|
|
@@ -2028,7 +2147,7 @@ async function initFromPublicTemplate(dest, name, templateId, ff) {
|
|
|
2028
2147
|
);
|
|
2029
2148
|
const { unpackTemplate } = await loadUnpack();
|
|
2030
2149
|
const unpackSpin = spinner2();
|
|
2031
|
-
unpackSpin.start("Unpacking
|
|
2150
|
+
unpackSpin.start("Unpacking template files\u2026");
|
|
2032
2151
|
const stats = unpackTemplate({
|
|
2033
2152
|
inputPath: dataPath,
|
|
2034
2153
|
outDir: dest,
|
|
@@ -2101,7 +2220,7 @@ async function runTemplateInit(nameArg, opts = {}) {
|
|
|
2101
2220
|
options: [
|
|
2102
2221
|
{
|
|
2103
2222
|
value: "blank",
|
|
2104
|
-
label: "Blank
|
|
2223
|
+
label: "Blank template",
|
|
2105
2224
|
hint: resolveBlankKitGitUrl() ? "git clone official / monorepo kit" : "local scaffold"
|
|
2106
2225
|
},
|
|
2107
2226
|
{
|
|
@@ -2117,8 +2236,8 @@ async function runTemplateInit(nameArg, opts = {}) {
|
|
|
2117
2236
|
]
|
|
2118
2237
|
});
|
|
2119
2238
|
const name = nameArg || await text2({
|
|
2120
|
-
message: "
|
|
2121
|
-
placeholder: "my-
|
|
2239
|
+
message: "Template folder name",
|
|
2240
|
+
placeholder: "my-template",
|
|
2122
2241
|
validate: validateFolderName
|
|
2123
2242
|
});
|
|
2124
2243
|
const dest = path12.resolve(process.cwd(), name);
|
|
@@ -2141,7 +2260,7 @@ async function runTemplateInit(nameArg, opts = {}) {
|
|
|
2141
2260
|
info(
|
|
2142
2261
|
"Next:\n cd " + name + "\n feeef signin\n feeef use\n npm run build"
|
|
2143
2262
|
);
|
|
2144
|
-
outro2("
|
|
2263
|
+
outro2("Template scaffold ready");
|
|
2145
2264
|
return;
|
|
2146
2265
|
}
|
|
2147
2266
|
const { ff } = createClient({ requireAuth: false });
|
|
@@ -2159,7 +2278,7 @@ async function runTemplateInit(nameArg, opts = {}) {
|
|
|
2159
2278
|
info(
|
|
2160
2279
|
"Next:\n cd " + name + "\n feeef signin\n feeef use\n npm run build\n npm run dev"
|
|
2161
2280
|
);
|
|
2162
|
-
outro2("
|
|
2281
|
+
outro2("Template cloned and ready");
|
|
2163
2282
|
} catch (e) {
|
|
2164
2283
|
fail(e);
|
|
2165
2284
|
}
|
|
@@ -2242,7 +2361,7 @@ async function runUse(storeArg, opts) {
|
|
|
2242
2361
|
const themeRoot = findThemeRoot(process.cwd());
|
|
2243
2362
|
if (!themeRoot) {
|
|
2244
2363
|
fail(
|
|
2245
|
-
"No feeef.template.json nearby. Run from a
|
|
2364
|
+
"No feeef.template.json nearby. Run from a template folder or feeef template init first."
|
|
2246
2365
|
);
|
|
2247
2366
|
}
|
|
2248
2367
|
const { ff } = createClient();
|
|
@@ -2271,7 +2390,7 @@ async function runUse(storeArg, opts) {
|
|
|
2271
2390
|
if (opts.apiUrl) patch.apiUrl = opts.apiUrl;
|
|
2272
2391
|
const { rcPath } = saveProjectRc(themeRoot, patch);
|
|
2273
2392
|
saveConfig({ defaultStoreId: storeId });
|
|
2274
|
-
ok(`Linked
|
|
2393
|
+
ok(`Linked template \u2192 store ${slug || storeId}`);
|
|
2275
2394
|
info(rcPath);
|
|
2276
2395
|
outro2("Next: feeef template publish --apply");
|
|
2277
2396
|
} catch (e) {
|
|
@@ -2292,7 +2411,7 @@ var SOURCE_TAG_PREFIX = "feeef-source:";
|
|
|
2292
2411
|
async function runTemplatePublish(dirArg, opts) {
|
|
2293
2412
|
intro2("feeef template publish");
|
|
2294
2413
|
try {
|
|
2295
|
-
const { ff, http:
|
|
2414
|
+
const { ff, http: http3, cfg } = createClient();
|
|
2296
2415
|
const srcDir = resolveThemeDir(dirArg);
|
|
2297
2416
|
const meta = loadTemplateMeta(srcDir);
|
|
2298
2417
|
const rc = loadProjectRc(srcDir);
|
|
@@ -2314,7 +2433,7 @@ async function runTemplatePublish(dirArg, opts) {
|
|
|
2314
2433
|
}
|
|
2315
2434
|
const store = await ff.stores.find({ id: storeId });
|
|
2316
2435
|
const title = opts.title || rc.title || meta.name || path13.basename(srcDir);
|
|
2317
|
-
info(`
|
|
2436
|
+
info(`Template ${srcDir}`);
|
|
2318
2437
|
info(`Store ${store.slug || storeId} (${policy})`);
|
|
2319
2438
|
const outPath = path13.join(srcDir, "dist", "data.json");
|
|
2320
2439
|
const s = spinner2();
|
|
@@ -2500,7 +2619,7 @@ async function runTemplatePublish(dirArg, opts) {
|
|
|
2500
2619
|
if (typeof ff.storeTemplates.replaceLocales === "function") {
|
|
2501
2620
|
await ff.storeTemplates.replaceLocales(templateId, locales);
|
|
2502
2621
|
} else {
|
|
2503
|
-
await
|
|
2622
|
+
await http3.put(`/store_templates/${templateId}/locales`, { locales });
|
|
2504
2623
|
}
|
|
2505
2624
|
s.stop(`Locales: ${locales.map((l) => l.locale).join(", ")}`);
|
|
2506
2625
|
}
|
|
@@ -2524,8 +2643,48 @@ async function runTemplatePublish(dirArg, opts) {
|
|
|
2524
2643
|
}
|
|
2525
2644
|
}
|
|
2526
2645
|
|
|
2527
|
-
// src/commands/template/
|
|
2646
|
+
// src/commands/template/smoke.ts
|
|
2528
2647
|
import path14 from "path";
|
|
2648
|
+
async function runTemplateSmoke(dirArg, opts = {}) {
|
|
2649
|
+
intro2("feeef template smoke");
|
|
2650
|
+
try {
|
|
2651
|
+
const srcDir = resolveThemeDir(dirArg);
|
|
2652
|
+
const s = spinner2();
|
|
2653
|
+
s.start(
|
|
2654
|
+
opts.noBuild ? "Smoking custom components (existing dist)\u2026" : "Building + smoking custom components\u2026"
|
|
2655
|
+
);
|
|
2656
|
+
const { smokeTheme } = await loadSmoke();
|
|
2657
|
+
const result = await smokeTheme({
|
|
2658
|
+
srcDir,
|
|
2659
|
+
build: !opts.noBuild
|
|
2660
|
+
});
|
|
2661
|
+
s.stop(
|
|
2662
|
+
`Smoked ${result.total} custom component${result.total === 1 ? "" : "s"}`
|
|
2663
|
+
);
|
|
2664
|
+
for (const f of result.failed) {
|
|
2665
|
+
const label = f.item.sourcePath || f.item.instanceId || f.item.loc || "?";
|
|
2666
|
+
warn(`\u2717 ${label}`);
|
|
2667
|
+
console.error(pc.red(` ${f.error.message}`));
|
|
2668
|
+
}
|
|
2669
|
+
if (result.failed.length) {
|
|
2670
|
+
fail(
|
|
2671
|
+
new Error(
|
|
2672
|
+
`smoke failed \u2014 ${result.failed.length}/${result.total} components`
|
|
2673
|
+
)
|
|
2674
|
+
);
|
|
2675
|
+
return;
|
|
2676
|
+
}
|
|
2677
|
+
ok(
|
|
2678
|
+
`All ${result.passed} custom component${result.passed === 1 ? "" : "s"} rendered`
|
|
2679
|
+
);
|
|
2680
|
+
outro2(path14.relative(process.cwd(), srcDir) || srcDir);
|
|
2681
|
+
} catch (e) {
|
|
2682
|
+
fail(e);
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
// src/commands/template/remove.ts
|
|
2687
|
+
import path15 from "path";
|
|
2529
2688
|
async function runTemplateRemovePage(pageId, opts = {}) {
|
|
2530
2689
|
intro2("feeef template remove page");
|
|
2531
2690
|
try {
|
|
@@ -2544,7 +2703,7 @@ async function runTemplateRemovePage(pageId, opts = {}) {
|
|
|
2544
2703
|
}
|
|
2545
2704
|
}
|
|
2546
2705
|
const { pageId: id, removedPath } = removePage(themeRoot, pageId);
|
|
2547
|
-
ok(`Removed ${
|
|
2706
|
+
ok(`Removed ${path15.relative(themeRoot, removedPath)}`);
|
|
2548
2707
|
outro2(`Page "${id}" deleted`);
|
|
2549
2708
|
} catch (err) {
|
|
2550
2709
|
fail(err instanceof Error ? err.message : String(err));
|
|
@@ -2570,7 +2729,7 @@ async function runTemplateRemoveComponent(pageId, componentId, opts = {}) {
|
|
|
2570
2729
|
const { removedPath } = removeComponent(themeRoot, pageId, componentId, {
|
|
2571
2730
|
...opts.section ? { section: opts.section } : {}
|
|
2572
2731
|
});
|
|
2573
|
-
ok(`Removed ${
|
|
2732
|
+
ok(`Removed ${path15.relative(themeRoot, removedPath)}`);
|
|
2574
2733
|
outro2("Done");
|
|
2575
2734
|
} catch (err) {
|
|
2576
2735
|
fail(err instanceof Error ? err.message : String(err));
|
|
@@ -2579,12 +2738,12 @@ async function runTemplateRemoveComponent(pageId, componentId, opts = {}) {
|
|
|
2579
2738
|
|
|
2580
2739
|
// src/commands/template/watch.ts
|
|
2581
2740
|
import { spawn as spawn2 } from "child_process";
|
|
2582
|
-
import
|
|
2741
|
+
import path16 from "path";
|
|
2583
2742
|
async function runTemplateWatch(dirArg) {
|
|
2584
2743
|
intro2("feeef template watch");
|
|
2585
2744
|
try {
|
|
2586
2745
|
const srcDir = resolveThemeDir(dirArg);
|
|
2587
|
-
const outPath =
|
|
2746
|
+
const outPath = path16.join(srcDir, "dist", "data.json");
|
|
2588
2747
|
const s = spinner2();
|
|
2589
2748
|
s.start("Initial build\u2026");
|
|
2590
2749
|
const { buildTemplate } = await loadBuild();
|
|
@@ -2610,9 +2769,11 @@ async function runTemplateWatch(dirArg) {
|
|
|
2610
2769
|
}
|
|
2611
2770
|
|
|
2612
2771
|
// src/index.ts
|
|
2772
|
+
var require2 = createRequire(import.meta.url);
|
|
2773
|
+
var { version: CLI_VERSION } = require2("../package.json");
|
|
2613
2774
|
function createProgram() {
|
|
2614
2775
|
const program = new Command();
|
|
2615
|
-
program.name("feeef").description("Feeef developer CLI \u2014
|
|
2776
|
+
program.name("feeef").description("Feeef developer CLI \u2014 templates today, cloud tools tomorrow").version(CLI_VERSION).hook("preAction", (thisCommand) => {
|
|
2616
2777
|
if (thisCommand.args.length === 0) return;
|
|
2617
2778
|
banner();
|
|
2618
2779
|
});
|
|
@@ -2640,18 +2801,18 @@ function createProgram() {
|
|
|
2640
2801
|
runConfigSet(key, value);
|
|
2641
2802
|
});
|
|
2642
2803
|
program.command("use").description(
|
|
2643
|
-
"Link
|
|
2644
|
-
).argument("[store]", "Store id or slug (optional \u2014 interactive picker)").option("--api-url <url>", "API override for this
|
|
2804
|
+
"Link template to a store (.feeefrc); omit store to pick from your list"
|
|
2805
|
+
).argument("[store]", "Store id or slug (optional \u2014 interactive picker)").option("--api-url <url>", "API override for this template").action(async (store, opts) => {
|
|
2645
2806
|
await runUse(store, opts);
|
|
2646
2807
|
});
|
|
2647
|
-
program.command("publish").description("Build + publish current
|
|
2808
|
+
program.command("publish").description("Build + publish current template (alias of template publish)").argument("[dir]", "Template directory").option("--public", "Policy public (default private)").option("--unlisted", "Policy unlisted").option("--store <id>", "Owner store id").option("--title <title>", "Marketplace title").option("--yes", "Skip prompts").option("--apply", "Install onto the store").option("--no-apply", "Do not install").action(async (dir, opts) => {
|
|
2648
2809
|
await runTemplatePublish(dir, opts);
|
|
2649
2810
|
});
|
|
2650
2811
|
const template = program.command("template").description(
|
|
2651
|
-
"
|
|
2812
|
+
"Template kit: init, add/remove page|component, build, check, smoke, watch, dev, publish"
|
|
2652
2813
|
);
|
|
2653
2814
|
template.command("init").description(
|
|
2654
|
-
"Scaffold a
|
|
2815
|
+
"Scaffold a template (blank, git kit, or public marketplace)"
|
|
2655
2816
|
).argument("[name]", "Folder name").option("--blank", "Use the blank scaffold (skip picker)").option("--from <templateId>", "Clone a public template by id").option("--git <url>", "Clone a kit repo (depth 1), Adonis/Laravel style").option("-q, --search <query>", "Pre-fill marketplace search").action(async (name, opts) => {
|
|
2656
2817
|
await runTemplateInit(name, {
|
|
2657
2818
|
blank: opts.blank,
|
|
@@ -2660,8 +2821,8 @@ function createProgram() {
|
|
|
2660
2821
|
git: opts.git
|
|
2661
2822
|
});
|
|
2662
2823
|
});
|
|
2663
|
-
const templateAdd = template.command("add").description("Add a page or component to the current
|
|
2664
|
-
templateAdd.command("page").description("Create pages/<pageId>/ with flat components/ + update manifest").argument("<pageId>", "Page id (e.g. home, checkout, thank_you)").option("--dir <path>", "
|
|
2824
|
+
const templateAdd = template.command("add").description("Add a page or component to the current template");
|
|
2825
|
+
templateAdd.command("page").description("Create pages/<pageId>/ with flat components/ + update manifest").argument("<pageId>", "Page id (e.g. home, checkout, thank_you)").option("--dir <path>", "Template directory").action(async (pageId, opts) => {
|
|
2665
2826
|
await runTemplateAddPage(pageId, { dir: opts.dir });
|
|
2666
2827
|
});
|
|
2667
2828
|
templateAdd.command("component").description(
|
|
@@ -2669,42 +2830,47 @@ function createProgram() {
|
|
|
2669
2830
|
).argument("<pageId>", "Page id").argument("<componentId>", "Component file id (e.g. hero)").option("--title <title>", "meta.title (default: Title Case of id)").option(
|
|
2670
2831
|
"--section <id>",
|
|
2671
2832
|
"Legacy: write under sections/<id>/components/ instead of flat"
|
|
2672
|
-
).option("--dir <path>", "
|
|
2833
|
+
).option("--dir <path>", "Template directory").action(async (pageId, componentId, opts) => {
|
|
2673
2834
|
await runTemplateAddComponent(pageId, componentId, {
|
|
2674
2835
|
dir: opts.dir,
|
|
2675
2836
|
title: opts.title,
|
|
2676
2837
|
section: opts.section
|
|
2677
2838
|
});
|
|
2678
2839
|
});
|
|
2679
|
-
const templateRemove = template.command("remove").description("Remove a page or component from the current
|
|
2680
|
-
templateRemove.command("page").description("Delete pages/<pageId>/ and drop it from feeef.template.json").argument("<pageId>", "Page id").option("-y, --yes", "Skip confirmation").option("--dir <path>", "
|
|
2840
|
+
const templateRemove = template.command("remove").description("Remove a page or component from the current template");
|
|
2841
|
+
templateRemove.command("page").description("Delete pages/<pageId>/ and drop it from feeef.template.json").argument("<pageId>", "Page id").option("-y, --yes", "Skip confirmation").option("--dir <path>", "Template directory").action(async (pageId, opts) => {
|
|
2681
2842
|
await runTemplateRemovePage(pageId, { dir: opts.dir, yes: opts.yes });
|
|
2682
2843
|
});
|
|
2683
|
-
templateRemove.command("component").description("Delete a component file/folder under a page").argument("<pageId>", "Page id").argument("<componentId>", "Component id").option("--section <id>", "Legacy: only search sections/<id>/components/").option("-y, --yes", "Skip confirmation").option("--dir <path>", "
|
|
2844
|
+
templateRemove.command("component").description("Delete a component file/folder under a page").argument("<pageId>", "Page id").argument("<componentId>", "Component id").option("--section <id>", "Legacy: only search sections/<id>/components/").option("-y, --yes", "Skip confirmation").option("--dir <path>", "Template directory").action(async (pageId, componentId, opts) => {
|
|
2684
2845
|
await runTemplateRemoveComponent(pageId, componentId, {
|
|
2685
2846
|
dir: opts.dir,
|
|
2686
2847
|
section: opts.section,
|
|
2687
2848
|
yes: opts.yes
|
|
2688
2849
|
});
|
|
2689
2850
|
});
|
|
2690
|
-
template.command("build").description("Compile
|
|
2851
|
+
template.command("build").description("Compile template \u2192 dist/data.json").argument("[dir]", "Template directory").action(async (dir) => {
|
|
2691
2852
|
await runTemplateBuild(dir);
|
|
2692
2853
|
});
|
|
2693
|
-
template.command("check").description("Validate
|
|
2854
|
+
template.command("check").description("Validate template without writing dist").argument("[dir]", "Template directory").action(async (dir) => {
|
|
2694
2855
|
await runTemplateBuild(dir, { checkOnly: true });
|
|
2695
2856
|
});
|
|
2696
|
-
template.command("
|
|
2857
|
+
template.command("smoke").description(
|
|
2858
|
+
"Build + SSR-eval every custom App() (catches broken components)"
|
|
2859
|
+
).argument("[dir]", "Template directory").option("--no-build", "Use existing dist/data.json only").action(async (dir, opts) => {
|
|
2860
|
+
await runTemplateSmoke(dir, { noBuild: Boolean(opts.noBuild) });
|
|
2861
|
+
});
|
|
2862
|
+
template.command("watch").description("Rebuild on file changes").argument("[dir]", "Template directory").action(async (dir) => {
|
|
2697
2863
|
await runTemplateWatch(dir);
|
|
2698
2864
|
});
|
|
2699
2865
|
template.command("dev").description(
|
|
2700
|
-
"Watch + push draft preview to hosted storefront (never overwrites live
|
|
2701
|
-
).argument("[dir]", "
|
|
2866
|
+
"Watch + push draft preview to hosted storefront (never overwrites live template)"
|
|
2867
|
+
).argument("[dir]", "Template directory").option("-p, --port <port>", "Local Next port (only with --local)").option("--storefront <path>", "Lithium storefront root (implies --local)").option(
|
|
2702
2868
|
"--local",
|
|
2703
2869
|
"Employee escape hatch: spawn local Next (requires storefront)"
|
|
2704
2870
|
).action(async (dir, opts) => {
|
|
2705
2871
|
await runTemplateDev(dir, opts);
|
|
2706
2872
|
});
|
|
2707
|
-
template.command("publish").description("Build, upload components + template + locales, optional install").argument("[dir]", "
|
|
2873
|
+
template.command("publish").description("Build, upload components + template + locales, optional install").argument("[dir]", "Template directory").option("--public", "Policy public (default private)").option("--unlisted", "Policy unlisted").option("--store <id>", "Owner store id").option("--title <title>", "Marketplace title").option("--yes", "Skip prompts").option("--apply", "Install onto the store").option("--no-apply", "Do not install").action(async (dir, opts) => {
|
|
2708
2874
|
await runTemplatePublish(dir, opts);
|
|
2709
2875
|
});
|
|
2710
2876
|
const cloud = program.command("cloud").description("Cloud providers (Azure / GCP / AWS) \u2014 coming soon");
|