@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/bin.js
CHANGED
|
@@ -94,6 +94,13 @@ async function loadBuild() {
|
|
|
94
94
|
}
|
|
95
95
|
return import(pathToFileURL(buildPath).href);
|
|
96
96
|
}
|
|
97
|
+
async function loadSmoke() {
|
|
98
|
+
const smokePath = path2.join(templateKitRoot(), "lib", "smoke.mjs");
|
|
99
|
+
if (!fs2.existsSync(smokePath)) {
|
|
100
|
+
throw new Error(`template-kit smoke missing at ${smokePath}`);
|
|
101
|
+
}
|
|
102
|
+
return import(pathToFileURL(smokePath).href);
|
|
103
|
+
}
|
|
97
104
|
async function loadUnpack() {
|
|
98
105
|
const unpackPath = path2.join(templateKitRoot(), "lib", "unpack.mjs");
|
|
99
106
|
if (!fs2.existsSync(unpackPath)) {
|
|
@@ -120,6 +127,7 @@ function loadEnvFiles() {
|
|
|
120
127
|
}
|
|
121
128
|
|
|
122
129
|
// src/index.ts
|
|
130
|
+
import { createRequire } from "module";
|
|
123
131
|
import { Command } from "commander";
|
|
124
132
|
|
|
125
133
|
// src/client.ts
|
|
@@ -145,22 +153,22 @@ function createClient(opts = {}) {
|
|
|
145
153
|
if (requireAuth && !token) {
|
|
146
154
|
throw new Error("Not signed in. Run: feeef signin");
|
|
147
155
|
}
|
|
148
|
-
const
|
|
156
|
+
const http3 = createHttp(baseURL, token);
|
|
149
157
|
const ff = new FeeeF({
|
|
150
158
|
apiKey: token || "",
|
|
151
159
|
baseURL,
|
|
152
|
-
client:
|
|
160
|
+
client: http3,
|
|
153
161
|
cache: false
|
|
154
162
|
});
|
|
155
|
-
return { ff, http:
|
|
163
|
+
return { ff, http: http3, cfg, baseURL, token };
|
|
156
164
|
}
|
|
157
165
|
async function signinWithPassword(input) {
|
|
158
166
|
const baseURL = (input.apiUrl || defaultApiUrl()).replace(/\/$/, "");
|
|
159
|
-
const
|
|
167
|
+
const http3 = createHttp(baseURL, void 0);
|
|
160
168
|
const ff = new FeeeF({
|
|
161
169
|
apiKey: "",
|
|
162
170
|
baseURL,
|
|
163
|
-
client:
|
|
171
|
+
client: http3,
|
|
164
172
|
cache: false
|
|
165
173
|
});
|
|
166
174
|
const auth = await ff.users.signin({
|
|
@@ -660,14 +668,14 @@ function resolveThemeDir(dirArg) {
|
|
|
660
668
|
if (dirArg) {
|
|
661
669
|
const abs = path4.resolve(dirArg);
|
|
662
670
|
if (!fs4.existsSync(abs)) {
|
|
663
|
-
throw new Error(`
|
|
671
|
+
throw new Error(`Template directory not found: ${abs}`);
|
|
664
672
|
}
|
|
665
673
|
return findThemeRoot(abs) || abs;
|
|
666
674
|
}
|
|
667
675
|
const fromCwd = findThemeRoot(process.cwd());
|
|
668
676
|
if (fromCwd) return fromCwd;
|
|
669
677
|
throw new Error(
|
|
670
|
-
"No
|
|
678
|
+
"No template found. Run inside a template folder (feeef.template.json) or: feeef template init"
|
|
671
679
|
);
|
|
672
680
|
}
|
|
673
681
|
|
|
@@ -977,7 +985,7 @@ async function runTemplateBuild(dirArg, opts = {}) {
|
|
|
977
985
|
const srcDir = resolveThemeDir(dirArg);
|
|
978
986
|
const outPath = path7.join(srcDir, "dist", "data.json");
|
|
979
987
|
const s = spinner2();
|
|
980
|
-
s.start(opts.checkOnly ? "Checking
|
|
988
|
+
s.start(opts.checkOnly ? "Checking template\u2026" : "Building template\u2026");
|
|
981
989
|
const { buildTemplate } = await loadBuild();
|
|
982
990
|
const result = buildTemplate({
|
|
983
991
|
srcDir,
|
|
@@ -1030,7 +1038,7 @@ function assertStorefrontPath() {
|
|
|
1030
1038
|
throw new Error(
|
|
1031
1039
|
[
|
|
1032
1040
|
"Lithium storefront not found.",
|
|
1033
|
-
"Set it once (
|
|
1041
|
+
"Set it once (template repos do not need storefront source inside them):",
|
|
1034
1042
|
" feeef config set storefrontPath /path/to/storefront",
|
|
1035
1043
|
"or:",
|
|
1036
1044
|
" export FEEEF_STOREFRONT_PATH=/path/to/storefront"
|
|
@@ -1040,6 +1048,85 @@ function assertStorefrontPath() {
|
|
|
1040
1048
|
return root;
|
|
1041
1049
|
}
|
|
1042
1050
|
|
|
1051
|
+
// src/dev-error-server.ts
|
|
1052
|
+
import http2 from "http";
|
|
1053
|
+
|
|
1054
|
+
// src/dev-error-format.ts
|
|
1055
|
+
function formatFeeefLiveErrorLine(body) {
|
|
1056
|
+
const debug = body.debug || {};
|
|
1057
|
+
const kind = String(body.kind || "error");
|
|
1058
|
+
const message = String(body.message || "unknown");
|
|
1059
|
+
const lines = [
|
|
1060
|
+
`[feeef:custom-live] ${kind}`,
|
|
1061
|
+
debug.sourcePath ? ` path: ${debug.sourcePath}` : null,
|
|
1062
|
+
debug.instanceId ? ` instanceId: ${debug.instanceId}` : null,
|
|
1063
|
+
debug.title ? ` title: ${debug.title}` : null,
|
|
1064
|
+
debug.type ? ` type: ${debug.type}` : null,
|
|
1065
|
+
` message: ${message}`,
|
|
1066
|
+
body.props && typeof body.props === "object" ? ` props: ${JSON.stringify(body.props)}` : null,
|
|
1067
|
+
body.href ? ` href: ${body.href}` : null
|
|
1068
|
+
].filter(Boolean);
|
|
1069
|
+
return lines.join("\n");
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// src/dev-error-server.ts
|
|
1073
|
+
async function startDevErrorServer(opts) {
|
|
1074
|
+
const preferred = opts?.preferredPort ?? (Number(process.env["FEEEF_DEV_ERROR_PORT"]) || 0);
|
|
1075
|
+
const server = http2.createServer((req, res) => {
|
|
1076
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1077
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
1078
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
1079
|
+
if (req.method === "OPTIONS") {
|
|
1080
|
+
res.writeHead(204);
|
|
1081
|
+
res.end();
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
if (req.method === "GET" && (req.url === "/" || req.url === "/health")) {
|
|
1085
|
+
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
1086
|
+
res.end("feeef-dev-error-ok");
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
if (req.method === "POST" && req.url?.startsWith("/feeef-error")) {
|
|
1090
|
+
const chunks = [];
|
|
1091
|
+
req.on("data", (c) => chunks.push(c));
|
|
1092
|
+
req.on("end", () => {
|
|
1093
|
+
try {
|
|
1094
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
1095
|
+
const body = JSON.parse(raw);
|
|
1096
|
+
const line = formatFeeefLiveErrorLine(body);
|
|
1097
|
+
console.error("\n" + line + "\n");
|
|
1098
|
+
res.writeHead(204);
|
|
1099
|
+
res.end();
|
|
1100
|
+
} catch (e) {
|
|
1101
|
+
console.error("[feeef:custom-live] bad error payload", e);
|
|
1102
|
+
res.writeHead(400);
|
|
1103
|
+
res.end("bad json");
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
res.writeHead(404);
|
|
1109
|
+
res.end("not found");
|
|
1110
|
+
});
|
|
1111
|
+
await new Promise((resolve, reject) => {
|
|
1112
|
+
server.once("error", reject);
|
|
1113
|
+
server.listen(preferred, "127.0.0.1", () => resolve());
|
|
1114
|
+
});
|
|
1115
|
+
const addr = server.address();
|
|
1116
|
+
if (!addr || typeof addr === "string") {
|
|
1117
|
+
throw new Error("Failed to bind feeef dev error server");
|
|
1118
|
+
}
|
|
1119
|
+
const port = addr.port;
|
|
1120
|
+
const url = `http://127.0.0.1:${port}`;
|
|
1121
|
+
return {
|
|
1122
|
+
port,
|
|
1123
|
+
url,
|
|
1124
|
+
close: () => new Promise((resolve) => {
|
|
1125
|
+
server.close(() => resolve());
|
|
1126
|
+
})
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1043
1130
|
// src/commands/template/dev.ts
|
|
1044
1131
|
function killChildren(children) {
|
|
1045
1132
|
for (const c of children) {
|
|
@@ -1073,7 +1160,7 @@ async function resolveBoundStore(srcDir) {
|
|
|
1073
1160
|
});
|
|
1074
1161
|
if (!storeRef) {
|
|
1075
1162
|
throw new Error(
|
|
1076
|
-
"No store linked. Run: feeef use <slug>\n(Remote preview pushes a draft to your store \u2014 live
|
|
1163
|
+
"No store linked. Run: feeef use <slug>\n(Remote preview pushes a draft to your store \u2014 live template stays unchanged.)"
|
|
1077
1164
|
);
|
|
1078
1165
|
}
|
|
1079
1166
|
const { ff } = createClient({ requireAuth: true });
|
|
@@ -1090,10 +1177,21 @@ async function runRemoteDraftPreview(srcDir) {
|
|
|
1090
1177
|
const { storeId, slug } = await resolveBoundStore(srcDir);
|
|
1091
1178
|
const { ff } = createClient({ requireAuth: true });
|
|
1092
1179
|
const s = spinner2();
|
|
1093
|
-
s.start("Building
|
|
1180
|
+
s.start("Building template\u2026");
|
|
1094
1181
|
const { buildTemplate } = await loadBuild();
|
|
1095
1182
|
buildTemplate({ srcDir, outPath, write: true });
|
|
1096
1183
|
s.stop("dist ready");
|
|
1184
|
+
const errorServer = await startDevErrorServer();
|
|
1185
|
+
info(`Component errors \u2192 ${pc.cyan(errorServer.url)} (terminal)`);
|
|
1186
|
+
const withDevLog = (previewUrl) => {
|
|
1187
|
+
try {
|
|
1188
|
+
const u = new URL(previewUrl);
|
|
1189
|
+
u.searchParams.set("feeef_dev_log", errorServer.url);
|
|
1190
|
+
return u.toString();
|
|
1191
|
+
} catch {
|
|
1192
|
+
return previewUrl;
|
|
1193
|
+
}
|
|
1194
|
+
};
|
|
1097
1195
|
let lastPreviewUrl;
|
|
1098
1196
|
let pushInFlight = false;
|
|
1099
1197
|
let pushQueued = false;
|
|
@@ -1110,8 +1208,8 @@ async function runRemoteDraftPreview(srcDir) {
|
|
|
1110
1208
|
}
|
|
1111
1209
|
const data = JSON.parse(fs7.readFileSync(outPath, "utf8"));
|
|
1112
1210
|
const result = await ff.stores.putTemplatePreview(storeId, { data });
|
|
1113
|
-
lastPreviewUrl = result.previewUrl;
|
|
1114
|
-
ok(`Draft preview updated \u2192 ${pc.cyan(
|
|
1211
|
+
lastPreviewUrl = withDevLog(result.previewUrl);
|
|
1212
|
+
ok(`Draft preview updated \u2192 ${pc.cyan(lastPreviewUrl)}`);
|
|
1115
1213
|
} catch (e) {
|
|
1116
1214
|
fail(e);
|
|
1117
1215
|
} finally {
|
|
@@ -1131,12 +1229,13 @@ async function runRemoteDraftPreview(srcDir) {
|
|
|
1131
1229
|
}
|
|
1132
1230
|
note2(
|
|
1133
1231
|
[
|
|
1134
|
-
`
|
|
1135
|
-
`Store
|
|
1136
|
-
`Preview
|
|
1232
|
+
`Template ${srcDir}`,
|
|
1233
|
+
`Store ${slug} (${storeId})`,
|
|
1234
|
+
`Preview ${lastPreviewUrl ?? "(pending)"}`,
|
|
1137
1235
|
"",
|
|
1138
1236
|
"Live customers still see metadata.templateData.",
|
|
1139
1237
|
"Exit preview in the browser: banner \u2192 Exit, or /?preview=0",
|
|
1238
|
+
`CustomLive errors print here via ${errorServer.url}`,
|
|
1140
1239
|
"Stopping the CLI clears the draft."
|
|
1141
1240
|
].join("\n"),
|
|
1142
1241
|
"Remote draft preview"
|
|
@@ -1164,6 +1263,10 @@ async function runRemoteDraftPreview(srcDir) {
|
|
|
1164
1263
|
watching = false;
|
|
1165
1264
|
debouncedPush.cancel();
|
|
1166
1265
|
killChildren([watch]);
|
|
1266
|
+
try {
|
|
1267
|
+
await errorServer.close();
|
|
1268
|
+
} catch {
|
|
1269
|
+
}
|
|
1167
1270
|
try {
|
|
1168
1271
|
await ff.stores.clearTemplatePreview(storeId);
|
|
1169
1272
|
info("Cleared draft preview on server");
|
|
@@ -1193,6 +1296,8 @@ async function runLocalStorefrontPreview(srcDir, opts) {
|
|
|
1193
1296
|
process.env["FEEEF_STOREFRONT_PATH"] = path9.resolve(opts.storefront);
|
|
1194
1297
|
}
|
|
1195
1298
|
const storefrontRoot = assertStorefrontPath();
|
|
1299
|
+
const errorServer = await startDevErrorServer();
|
|
1300
|
+
info(`Component errors \u2192 ${pc.cyan(errorServer.url)} (terminal)`);
|
|
1196
1301
|
const cfg = loadConfig();
|
|
1197
1302
|
const storeRef = resolveStoreIdFromProject({
|
|
1198
1303
|
themeRoot: srcDir,
|
|
@@ -1219,16 +1324,26 @@ async function runLocalStorefrontPreview(srcDir, opts) {
|
|
|
1219
1324
|
}
|
|
1220
1325
|
}
|
|
1221
1326
|
const s = spinner2();
|
|
1222
|
-
s.start("Building
|
|
1327
|
+
s.start("Building template\u2026");
|
|
1223
1328
|
const { buildTemplate } = await loadBuild();
|
|
1224
1329
|
buildTemplate({ srcDir, outPath, write: true });
|
|
1225
1330
|
s.stop("dist ready");
|
|
1226
1331
|
const previewHost = slug ? `http://${slug}.localhost.feeef.org:${port}` : `http://localhost:${port}`;
|
|
1332
|
+
const previewWithLog = (() => {
|
|
1333
|
+
try {
|
|
1334
|
+
const u = new URL(previewHost);
|
|
1335
|
+
u.searchParams.set("feeef_dev_log", errorServer.url);
|
|
1336
|
+
return u.toString();
|
|
1337
|
+
} catch {
|
|
1338
|
+
return previewHost;
|
|
1339
|
+
}
|
|
1340
|
+
})();
|
|
1227
1341
|
note2(
|
|
1228
1342
|
[
|
|
1229
|
-
`
|
|
1343
|
+
`Template ${srcDir}`,
|
|
1230
1344
|
`Storefront ${storefrontRoot}`,
|
|
1231
|
-
`Preview
|
|
1345
|
+
`Preview ${previewWithLog}`,
|
|
1346
|
+
`Errors ${errorServer.url}`,
|
|
1232
1347
|
"Internal --local mode (not for public docs)."
|
|
1233
1348
|
].join("\n"),
|
|
1234
1349
|
"Local Next preview"
|
|
@@ -1237,6 +1352,7 @@ async function runLocalStorefrontPreview(srcDir, opts) {
|
|
|
1237
1352
|
...process.env,
|
|
1238
1353
|
FEEEF_TEMPLATE_DATA_PATH: outPath,
|
|
1239
1354
|
FEEEF_TEMPLATE_LIBRARY_PATH: libraryPath,
|
|
1355
|
+
FEEEF_DEV_LOG_URL: errorServer.url,
|
|
1240
1356
|
PORT: String(port)
|
|
1241
1357
|
};
|
|
1242
1358
|
if (slug) {
|
|
@@ -1255,7 +1371,7 @@ async function runLocalStorefrontPreview(srcDir, opts) {
|
|
|
1255
1371
|
const children = [watch, next];
|
|
1256
1372
|
const shutdown = (code = 0) => {
|
|
1257
1373
|
killChildren(children);
|
|
1258
|
-
process.exit(code);
|
|
1374
|
+
void errorServer.close().finally(() => process.exit(code));
|
|
1259
1375
|
};
|
|
1260
1376
|
process.on("SIGINT", () => shutdown(0));
|
|
1261
1377
|
process.on("SIGTERM", () => shutdown(0));
|
|
@@ -1263,7 +1379,7 @@ async function runLocalStorefrontPreview(srcDir, opts) {
|
|
|
1263
1379
|
watch.on("exit", (code) => {
|
|
1264
1380
|
if (code && code !== 0) shutdown(code);
|
|
1265
1381
|
});
|
|
1266
|
-
ok(`Open ${pc.cyan(
|
|
1382
|
+
ok(`Open ${pc.cyan(previewWithLog)}`);
|
|
1267
1383
|
outro2("Watching \u2014 Ctrl+C to stop");
|
|
1268
1384
|
await new Promise(() => {
|
|
1269
1385
|
});
|
|
@@ -1344,15 +1460,16 @@ function packageNameFromFolder(name) {
|
|
|
1344
1460
|
return name.toLowerCase();
|
|
1345
1461
|
}
|
|
1346
1462
|
var DEFAULT_PACKAGE_JSON = {
|
|
1347
|
-
name: "feeef-
|
|
1463
|
+
name: "feeef-template",
|
|
1348
1464
|
private: true,
|
|
1349
1465
|
version: "0.1.0",
|
|
1350
|
-
description: "Feeef Lithium
|
|
1466
|
+
description: "Feeef Lithium template",
|
|
1351
1467
|
type: "module",
|
|
1352
1468
|
engines: { node: ">=18" },
|
|
1353
1469
|
scripts: {
|
|
1354
1470
|
build: "feeef template build",
|
|
1355
1471
|
check: "feeef template check",
|
|
1472
|
+
test: "feeef template smoke",
|
|
1356
1473
|
dev: "feeef template dev",
|
|
1357
1474
|
publish: "feeef template publish",
|
|
1358
1475
|
typecheck: "tsc --noEmit"
|
|
@@ -1468,7 +1585,9 @@ function ensureNodePackage(dest, folderName) {
|
|
|
1468
1585
|
pkg.scripts = { ...DEFAULT_PACKAGE_JSON.scripts };
|
|
1469
1586
|
}
|
|
1470
1587
|
const scripts = pkg.scripts;
|
|
1471
|
-
|
|
1588
|
+
for (const [key, value] of Object.entries(DEFAULT_PACKAGE_JSON.scripts)) {
|
|
1589
|
+
if (!scripts[key]) scripts[key] = value;
|
|
1590
|
+
}
|
|
1472
1591
|
const devDeps = pkg.devDependencies && typeof pkg.devDependencies === "object" ? pkg.devDependencies : {};
|
|
1473
1592
|
for (const [name, version] of Object.entries(EDITOR_DEV_DEPS)) {
|
|
1474
1593
|
if (!devDeps[name]) devDeps[name] = version;
|
|
@@ -2048,7 +2167,7 @@ async function initFromPublicTemplate(dest, name, templateId, ff) {
|
|
|
2048
2167
|
);
|
|
2049
2168
|
const { unpackTemplate } = await loadUnpack();
|
|
2050
2169
|
const unpackSpin = spinner2();
|
|
2051
|
-
unpackSpin.start("Unpacking
|
|
2170
|
+
unpackSpin.start("Unpacking template files\u2026");
|
|
2052
2171
|
const stats = unpackTemplate({
|
|
2053
2172
|
inputPath: dataPath,
|
|
2054
2173
|
outDir: dest,
|
|
@@ -2121,7 +2240,7 @@ async function runTemplateInit(nameArg, opts = {}) {
|
|
|
2121
2240
|
options: [
|
|
2122
2241
|
{
|
|
2123
2242
|
value: "blank",
|
|
2124
|
-
label: "Blank
|
|
2243
|
+
label: "Blank template",
|
|
2125
2244
|
hint: resolveBlankKitGitUrl() ? "git clone official / monorepo kit" : "local scaffold"
|
|
2126
2245
|
},
|
|
2127
2246
|
{
|
|
@@ -2137,8 +2256,8 @@ async function runTemplateInit(nameArg, opts = {}) {
|
|
|
2137
2256
|
]
|
|
2138
2257
|
});
|
|
2139
2258
|
const name = nameArg || await text2({
|
|
2140
|
-
message: "
|
|
2141
|
-
placeholder: "my-
|
|
2259
|
+
message: "Template folder name",
|
|
2260
|
+
placeholder: "my-template",
|
|
2142
2261
|
validate: validateFolderName
|
|
2143
2262
|
});
|
|
2144
2263
|
const dest = path13.resolve(process.cwd(), name);
|
|
@@ -2161,7 +2280,7 @@ async function runTemplateInit(nameArg, opts = {}) {
|
|
|
2161
2280
|
info(
|
|
2162
2281
|
"Next:\n cd " + name + "\n feeef signin\n feeef use\n npm run build"
|
|
2163
2282
|
);
|
|
2164
|
-
outro2("
|
|
2283
|
+
outro2("Template scaffold ready");
|
|
2165
2284
|
return;
|
|
2166
2285
|
}
|
|
2167
2286
|
const { ff } = createClient({ requireAuth: false });
|
|
@@ -2179,7 +2298,7 @@ async function runTemplateInit(nameArg, opts = {}) {
|
|
|
2179
2298
|
info(
|
|
2180
2299
|
"Next:\n cd " + name + "\n feeef signin\n feeef use\n npm run build\n npm run dev"
|
|
2181
2300
|
);
|
|
2182
|
-
outro2("
|
|
2301
|
+
outro2("Template cloned and ready");
|
|
2183
2302
|
} catch (e) {
|
|
2184
2303
|
fail(e);
|
|
2185
2304
|
}
|
|
@@ -2262,7 +2381,7 @@ async function runUse(storeArg, opts) {
|
|
|
2262
2381
|
const themeRoot = findThemeRoot(process.cwd());
|
|
2263
2382
|
if (!themeRoot) {
|
|
2264
2383
|
fail(
|
|
2265
|
-
"No feeef.template.json nearby. Run from a
|
|
2384
|
+
"No feeef.template.json nearby. Run from a template folder or feeef template init first."
|
|
2266
2385
|
);
|
|
2267
2386
|
}
|
|
2268
2387
|
const { ff } = createClient();
|
|
@@ -2291,7 +2410,7 @@ async function runUse(storeArg, opts) {
|
|
|
2291
2410
|
if (opts.apiUrl) patch.apiUrl = opts.apiUrl;
|
|
2292
2411
|
const { rcPath } = saveProjectRc(themeRoot, patch);
|
|
2293
2412
|
saveConfig({ defaultStoreId: storeId });
|
|
2294
|
-
ok(`Linked
|
|
2413
|
+
ok(`Linked template \u2192 store ${slug || storeId}`);
|
|
2295
2414
|
info(rcPath);
|
|
2296
2415
|
outro2("Next: feeef template publish --apply");
|
|
2297
2416
|
} catch (e) {
|
|
@@ -2312,7 +2431,7 @@ var SOURCE_TAG_PREFIX = "feeef-source:";
|
|
|
2312
2431
|
async function runTemplatePublish(dirArg, opts) {
|
|
2313
2432
|
intro2("feeef template publish");
|
|
2314
2433
|
try {
|
|
2315
|
-
const { ff, http:
|
|
2434
|
+
const { ff, http: http3, cfg } = createClient();
|
|
2316
2435
|
const srcDir = resolveThemeDir(dirArg);
|
|
2317
2436
|
const meta = loadTemplateMeta(srcDir);
|
|
2318
2437
|
const rc = loadProjectRc(srcDir);
|
|
@@ -2334,7 +2453,7 @@ async function runTemplatePublish(dirArg, opts) {
|
|
|
2334
2453
|
}
|
|
2335
2454
|
const store = await ff.stores.find({ id: storeId });
|
|
2336
2455
|
const title = opts.title || rc.title || meta.name || path14.basename(srcDir);
|
|
2337
|
-
info(`
|
|
2456
|
+
info(`Template ${srcDir}`);
|
|
2338
2457
|
info(`Store ${store.slug || storeId} (${policy})`);
|
|
2339
2458
|
const outPath = path14.join(srcDir, "dist", "data.json");
|
|
2340
2459
|
const s = spinner2();
|
|
@@ -2520,7 +2639,7 @@ async function runTemplatePublish(dirArg, opts) {
|
|
|
2520
2639
|
if (typeof ff.storeTemplates.replaceLocales === "function") {
|
|
2521
2640
|
await ff.storeTemplates.replaceLocales(templateId, locales);
|
|
2522
2641
|
} else {
|
|
2523
|
-
await
|
|
2642
|
+
await http3.put(`/store_templates/${templateId}/locales`, { locales });
|
|
2524
2643
|
}
|
|
2525
2644
|
s.stop(`Locales: ${locales.map((l) => l.locale).join(", ")}`);
|
|
2526
2645
|
}
|
|
@@ -2544,8 +2663,48 @@ async function runTemplatePublish(dirArg, opts) {
|
|
|
2544
2663
|
}
|
|
2545
2664
|
}
|
|
2546
2665
|
|
|
2547
|
-
// src/commands/template/
|
|
2666
|
+
// src/commands/template/smoke.ts
|
|
2548
2667
|
import path15 from "path";
|
|
2668
|
+
async function runTemplateSmoke(dirArg, opts = {}) {
|
|
2669
|
+
intro2("feeef template smoke");
|
|
2670
|
+
try {
|
|
2671
|
+
const srcDir = resolveThemeDir(dirArg);
|
|
2672
|
+
const s = spinner2();
|
|
2673
|
+
s.start(
|
|
2674
|
+
opts.noBuild ? "Smoking custom components (existing dist)\u2026" : "Building + smoking custom components\u2026"
|
|
2675
|
+
);
|
|
2676
|
+
const { smokeTheme } = await loadSmoke();
|
|
2677
|
+
const result = await smokeTheme({
|
|
2678
|
+
srcDir,
|
|
2679
|
+
build: !opts.noBuild
|
|
2680
|
+
});
|
|
2681
|
+
s.stop(
|
|
2682
|
+
`Smoked ${result.total} custom component${result.total === 1 ? "" : "s"}`
|
|
2683
|
+
);
|
|
2684
|
+
for (const f of result.failed) {
|
|
2685
|
+
const label = f.item.sourcePath || f.item.instanceId || f.item.loc || "?";
|
|
2686
|
+
warn(`\u2717 ${label}`);
|
|
2687
|
+
console.error(pc.red(` ${f.error.message}`));
|
|
2688
|
+
}
|
|
2689
|
+
if (result.failed.length) {
|
|
2690
|
+
fail(
|
|
2691
|
+
new Error(
|
|
2692
|
+
`smoke failed \u2014 ${result.failed.length}/${result.total} components`
|
|
2693
|
+
)
|
|
2694
|
+
);
|
|
2695
|
+
return;
|
|
2696
|
+
}
|
|
2697
|
+
ok(
|
|
2698
|
+
`All ${result.passed} custom component${result.passed === 1 ? "" : "s"} rendered`
|
|
2699
|
+
);
|
|
2700
|
+
outro2(path15.relative(process.cwd(), srcDir) || srcDir);
|
|
2701
|
+
} catch (e) {
|
|
2702
|
+
fail(e);
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
// src/commands/template/remove.ts
|
|
2707
|
+
import path16 from "path";
|
|
2549
2708
|
async function runTemplateRemovePage(pageId, opts = {}) {
|
|
2550
2709
|
intro2("feeef template remove page");
|
|
2551
2710
|
try {
|
|
@@ -2564,7 +2723,7 @@ async function runTemplateRemovePage(pageId, opts = {}) {
|
|
|
2564
2723
|
}
|
|
2565
2724
|
}
|
|
2566
2725
|
const { pageId: id, removedPath } = removePage(themeRoot, pageId);
|
|
2567
|
-
ok(`Removed ${
|
|
2726
|
+
ok(`Removed ${path16.relative(themeRoot, removedPath)}`);
|
|
2568
2727
|
outro2(`Page "${id}" deleted`);
|
|
2569
2728
|
} catch (err) {
|
|
2570
2729
|
fail(err instanceof Error ? err.message : String(err));
|
|
@@ -2590,7 +2749,7 @@ async function runTemplateRemoveComponent(pageId, componentId, opts = {}) {
|
|
|
2590
2749
|
const { removedPath } = removeComponent(themeRoot, pageId, componentId, {
|
|
2591
2750
|
...opts.section ? { section: opts.section } : {}
|
|
2592
2751
|
});
|
|
2593
|
-
ok(`Removed ${
|
|
2752
|
+
ok(`Removed ${path16.relative(themeRoot, removedPath)}`);
|
|
2594
2753
|
outro2("Done");
|
|
2595
2754
|
} catch (err) {
|
|
2596
2755
|
fail(err instanceof Error ? err.message : String(err));
|
|
@@ -2599,12 +2758,12 @@ async function runTemplateRemoveComponent(pageId, componentId, opts = {}) {
|
|
|
2599
2758
|
|
|
2600
2759
|
// src/commands/template/watch.ts
|
|
2601
2760
|
import { spawn as spawn2 } from "child_process";
|
|
2602
|
-
import
|
|
2761
|
+
import path17 from "path";
|
|
2603
2762
|
async function runTemplateWatch(dirArg) {
|
|
2604
2763
|
intro2("feeef template watch");
|
|
2605
2764
|
try {
|
|
2606
2765
|
const srcDir = resolveThemeDir(dirArg);
|
|
2607
|
-
const outPath =
|
|
2766
|
+
const outPath = path17.join(srcDir, "dist", "data.json");
|
|
2608
2767
|
const s = spinner2();
|
|
2609
2768
|
s.start("Initial build\u2026");
|
|
2610
2769
|
const { buildTemplate } = await loadBuild();
|
|
@@ -2630,9 +2789,11 @@ async function runTemplateWatch(dirArg) {
|
|
|
2630
2789
|
}
|
|
2631
2790
|
|
|
2632
2791
|
// src/index.ts
|
|
2792
|
+
var require2 = createRequire(import.meta.url);
|
|
2793
|
+
var { version: CLI_VERSION } = require2("../package.json");
|
|
2633
2794
|
function createProgram() {
|
|
2634
2795
|
const program = new Command();
|
|
2635
|
-
program.name("feeef").description("Feeef developer CLI \u2014
|
|
2796
|
+
program.name("feeef").description("Feeef developer CLI \u2014 templates today, cloud tools tomorrow").version(CLI_VERSION).hook("preAction", (thisCommand) => {
|
|
2636
2797
|
if (thisCommand.args.length === 0) return;
|
|
2637
2798
|
banner();
|
|
2638
2799
|
});
|
|
@@ -2660,18 +2821,18 @@ function createProgram() {
|
|
|
2660
2821
|
runConfigSet(key, value);
|
|
2661
2822
|
});
|
|
2662
2823
|
program.command("use").description(
|
|
2663
|
-
"Link
|
|
2664
|
-
).argument("[store]", "Store id or slug (optional \u2014 interactive picker)").option("--api-url <url>", "API override for this
|
|
2824
|
+
"Link template to a store (.feeefrc); omit store to pick from your list"
|
|
2825
|
+
).argument("[store]", "Store id or slug (optional \u2014 interactive picker)").option("--api-url <url>", "API override for this template").action(async (store, opts) => {
|
|
2665
2826
|
await runUse(store, opts);
|
|
2666
2827
|
});
|
|
2667
|
-
program.command("publish").description("Build + publish current
|
|
2828
|
+
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) => {
|
|
2668
2829
|
await runTemplatePublish(dir, opts);
|
|
2669
2830
|
});
|
|
2670
2831
|
const template = program.command("template").description(
|
|
2671
|
-
"
|
|
2832
|
+
"Template kit: init, add/remove page|component, build, check, smoke, watch, dev, publish"
|
|
2672
2833
|
);
|
|
2673
2834
|
template.command("init").description(
|
|
2674
|
-
"Scaffold a
|
|
2835
|
+
"Scaffold a template (blank, git kit, or public marketplace)"
|
|
2675
2836
|
).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) => {
|
|
2676
2837
|
await runTemplateInit(name, {
|
|
2677
2838
|
blank: opts.blank,
|
|
@@ -2680,8 +2841,8 @@ function createProgram() {
|
|
|
2680
2841
|
git: opts.git
|
|
2681
2842
|
});
|
|
2682
2843
|
});
|
|
2683
|
-
const templateAdd = template.command("add").description("Add a page or component to the current
|
|
2684
|
-
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>", "
|
|
2844
|
+
const templateAdd = template.command("add").description("Add a page or component to the current template");
|
|
2845
|
+
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) => {
|
|
2685
2846
|
await runTemplateAddPage(pageId, { dir: opts.dir });
|
|
2686
2847
|
});
|
|
2687
2848
|
templateAdd.command("component").description(
|
|
@@ -2689,42 +2850,47 @@ function createProgram() {
|
|
|
2689
2850
|
).argument("<pageId>", "Page id").argument("<componentId>", "Component file id (e.g. hero)").option("--title <title>", "meta.title (default: Title Case of id)").option(
|
|
2690
2851
|
"--section <id>",
|
|
2691
2852
|
"Legacy: write under sections/<id>/components/ instead of flat"
|
|
2692
|
-
).option("--dir <path>", "
|
|
2853
|
+
).option("--dir <path>", "Template directory").action(async (pageId, componentId, opts) => {
|
|
2693
2854
|
await runTemplateAddComponent(pageId, componentId, {
|
|
2694
2855
|
dir: opts.dir,
|
|
2695
2856
|
title: opts.title,
|
|
2696
2857
|
section: opts.section
|
|
2697
2858
|
});
|
|
2698
2859
|
});
|
|
2699
|
-
const templateRemove = template.command("remove").description("Remove a page or component from the current
|
|
2700
|
-
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>", "
|
|
2860
|
+
const templateRemove = template.command("remove").description("Remove a page or component from the current template");
|
|
2861
|
+
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) => {
|
|
2701
2862
|
await runTemplateRemovePage(pageId, { dir: opts.dir, yes: opts.yes });
|
|
2702
2863
|
});
|
|
2703
|
-
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>", "
|
|
2864
|
+
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) => {
|
|
2704
2865
|
await runTemplateRemoveComponent(pageId, componentId, {
|
|
2705
2866
|
dir: opts.dir,
|
|
2706
2867
|
section: opts.section,
|
|
2707
2868
|
yes: opts.yes
|
|
2708
2869
|
});
|
|
2709
2870
|
});
|
|
2710
|
-
template.command("build").description("Compile
|
|
2871
|
+
template.command("build").description("Compile template \u2192 dist/data.json").argument("[dir]", "Template directory").action(async (dir) => {
|
|
2711
2872
|
await runTemplateBuild(dir);
|
|
2712
2873
|
});
|
|
2713
|
-
template.command("check").description("Validate
|
|
2874
|
+
template.command("check").description("Validate template without writing dist").argument("[dir]", "Template directory").action(async (dir) => {
|
|
2714
2875
|
await runTemplateBuild(dir, { checkOnly: true });
|
|
2715
2876
|
});
|
|
2716
|
-
template.command("
|
|
2877
|
+
template.command("smoke").description(
|
|
2878
|
+
"Build + SSR-eval every custom App() (catches broken components)"
|
|
2879
|
+
).argument("[dir]", "Template directory").option("--no-build", "Use existing dist/data.json only").action(async (dir, opts) => {
|
|
2880
|
+
await runTemplateSmoke(dir, { noBuild: Boolean(opts.noBuild) });
|
|
2881
|
+
});
|
|
2882
|
+
template.command("watch").description("Rebuild on file changes").argument("[dir]", "Template directory").action(async (dir) => {
|
|
2717
2883
|
await runTemplateWatch(dir);
|
|
2718
2884
|
});
|
|
2719
2885
|
template.command("dev").description(
|
|
2720
|
-
"Watch + push draft preview to hosted storefront (never overwrites live
|
|
2721
|
-
).argument("[dir]", "
|
|
2886
|
+
"Watch + push draft preview to hosted storefront (never overwrites live template)"
|
|
2887
|
+
).argument("[dir]", "Template directory").option("-p, --port <port>", "Local Next port (only with --local)").option("--storefront <path>", "Lithium storefront root (implies --local)").option(
|
|
2722
2888
|
"--local",
|
|
2723
2889
|
"Employee escape hatch: spawn local Next (requires storefront)"
|
|
2724
2890
|
).action(async (dir, opts) => {
|
|
2725
2891
|
await runTemplateDev(dir, opts);
|
|
2726
2892
|
});
|
|
2727
|
-
template.command("publish").description("Build, upload components + template + locales, optional install").argument("[dir]", "
|
|
2893
|
+
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) => {
|
|
2728
2894
|
await runTemplatePublish(dir, opts);
|
|
2729
2895
|
});
|
|
2730
2896
|
const cloud = program.command("cloud").description("Cloud providers (Azure / GCP / AWS) \u2014 coming soon");
|