@feeef.dev/cli 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -5
- package/dist/bin.js +131 -17
- package/dist/bin.js.map +1 -1
- package/dist/index.js +131 -17
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/scaffolds/blank/.cursor/rules/feeef-theme.mdc +9 -7
- package/scaffolds/blank/.cursor/rules/theme-source.mdc +24 -17
- package/scaffolds/blank/.cursor/skills/feeef-theme/SKILL.md +21 -9
- package/scaffolds/blank/.cursor/skills/feeef-theme/reference.md +35 -30
- package/scaffolds/blank/AGENTS.md +7 -4
- package/scaffolds/blank/README.md +1 -1
- package/scaffolds/blank/docs/03-CUSTOM-COMPONENTS.md +68 -39
- package/scaffolds/blank/docs/04-WORKFLOW.md +7 -4
- package/scaffolds/blank/docs/05-ANTI-PATTERNS.md +7 -6
- package/scaffolds/blank/docs/07-SHARED-COMPONENTS.md +45 -77
- package/scaffolds/blank/docs/08-ORDER-FORM.md +1 -1
- package/scaffolds/blank/docs/10-SCHEMA-LIBRARY.md +21 -69
- package/scaffolds/blank/docs/11-PAGES-CHECKOUT-THANKS-PRODUCT.md +37 -22
- package/scaffolds/blank/docs/16-AUTHORING-TS-IMPORTS.md +38 -14
- package/scaffolds/blank/package-lock.json +4 -1918
- package/vendor/template-kit/lib/build.mjs +1 -1
- package/vendor/template-kit/lib/component-meta.mjs +92 -8
- package/vendor/template-kit/lib/unpack.mjs +295 -135
- package/vendor/template-kit/lib/validate.mjs +1 -1
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
|
|
@@ -736,7 +737,13 @@ function nextComponentOrder(parentDir) {
|
|
|
736
737
|
if (fs3.statSync(abs).isFile() && /\.(tsx|jsx)$/.test(name)) {
|
|
737
738
|
sourcePath = abs;
|
|
738
739
|
} else if (fs3.statSync(abs).isDirectory()) {
|
|
739
|
-
|
|
740
|
+
const id = name;
|
|
741
|
+
for (const entry of [
|
|
742
|
+
`${id}.tsx`,
|
|
743
|
+
`${id}.jsx`,
|
|
744
|
+
"component.tsx",
|
|
745
|
+
"component.jsx"
|
|
746
|
+
]) {
|
|
740
747
|
const p2 = path3.join(abs, entry);
|
|
741
748
|
if (fs3.existsSync(p2)) {
|
|
742
749
|
sourcePath = p2;
|
|
@@ -1151,10 +1158,54 @@ async function resolveBoundStore(srcDir) {
|
|
|
1151
1158
|
}
|
|
1152
1159
|
return { storeId: store.id, slug: store.slug };
|
|
1153
1160
|
}
|
|
1161
|
+
function isLoopbackHost(hostname) {
|
|
1162
|
+
const h = hostname.toLowerCase();
|
|
1163
|
+
return h === "localhost" || h === "127.0.0.1" || h === "::1" || h === "0.0.0.0";
|
|
1164
|
+
}
|
|
1165
|
+
function assertRemotePreviewApiMatch(apiBase, previewUrl) {
|
|
1166
|
+
if (process.env["FEEEF_ALLOW_LOCAL_API_REMOTE_PREVIEW"] === "1") return;
|
|
1167
|
+
let apiHost = "";
|
|
1168
|
+
let previewHost = "";
|
|
1169
|
+
try {
|
|
1170
|
+
apiHost = new URL(apiBase).hostname;
|
|
1171
|
+
} catch {
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
try {
|
|
1175
|
+
previewHost = new URL(previewUrl).hostname;
|
|
1176
|
+
} catch {
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
if (isLoopbackHost(apiHost) && !isLoopbackHost(previewHost)) {
|
|
1180
|
+
throw new Error(
|
|
1181
|
+
[
|
|
1182
|
+
"Draft preview mismatch: CLI API is local, but preview URL is hosted.",
|
|
1183
|
+
` API ${apiBase}`,
|
|
1184
|
+
` Preview ${previewUrl}`,
|
|
1185
|
+
"",
|
|
1186
|
+
"Hosted shops load drafts from production API \u2014 your local PUT never reaches them,",
|
|
1187
|
+
"so the page shows the live theme.",
|
|
1188
|
+
"",
|
|
1189
|
+
"Fix:",
|
|
1190
|
+
" feeef config set apiUrl https://api.feeef.org/v1",
|
|
1191
|
+
" feeef signin",
|
|
1192
|
+
" feeef template dev",
|
|
1193
|
+
"",
|
|
1194
|
+
"Or use local Next: feeef template dev --local",
|
|
1195
|
+
"(override: FEEEF_ALLOW_LOCAL_API_REMOTE_PREVIEW=1)"
|
|
1196
|
+
].join("\n")
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1154
1200
|
async function runRemoteDraftPreview(srcDir) {
|
|
1155
1201
|
const outPath = path8.join(srcDir, "dist", "data.json");
|
|
1156
1202
|
const { storeId, slug } = await resolveBoundStore(srcDir);
|
|
1157
|
-
const { ff } = createClient({ requireAuth: true });
|
|
1203
|
+
const { ff, baseURL } = createClient({ requireAuth: true });
|
|
1204
|
+
if (isLoopbackHost(new URL(baseURL).hostname)) {
|
|
1205
|
+
warn(
|
|
1206
|
+
`API is ${baseURL} \u2014 hosted ?preview= will only work if that API is what the shop calls.`
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1158
1209
|
const s = spinner2();
|
|
1159
1210
|
s.start("Building template\u2026");
|
|
1160
1211
|
const { buildTemplate } = await loadBuild();
|
|
@@ -1187,6 +1238,7 @@ async function runRemoteDraftPreview(srcDir) {
|
|
|
1187
1238
|
}
|
|
1188
1239
|
const data = JSON.parse(fs6.readFileSync(outPath, "utf8"));
|
|
1189
1240
|
const result = await ff.stores.putTemplatePreview(storeId, { data });
|
|
1241
|
+
assertRemotePreviewApiMatch(baseURL, result.previewUrl);
|
|
1190
1242
|
lastPreviewUrl = withDevLog(result.previewUrl);
|
|
1191
1243
|
ok(`Draft preview updated \u2192 ${pc.cyan(lastPreviewUrl)}`);
|
|
1192
1244
|
} catch (e) {
|
|
@@ -1367,7 +1419,25 @@ async function runTemplateDev(dirArg, opts) {
|
|
|
1367
1419
|
intro2("feeef template dev");
|
|
1368
1420
|
try {
|
|
1369
1421
|
const srcDir = resolveThemeDir(dirArg);
|
|
1370
|
-
|
|
1422
|
+
let useLocal = Boolean(opts.local || opts.storefront);
|
|
1423
|
+
if (!useLocal) {
|
|
1424
|
+
const cfg = loadConfig();
|
|
1425
|
+
let apiHost = "";
|
|
1426
|
+
try {
|
|
1427
|
+
apiHost = new URL(defaultApiUrl(cfg)).hostname;
|
|
1428
|
+
} catch {
|
|
1429
|
+
}
|
|
1430
|
+
const hasStorefront = Boolean(
|
|
1431
|
+
opts.storefront || cfg.storefrontPath || process.env["FEEEF_STOREFRONT_PATH"]
|
|
1432
|
+
);
|
|
1433
|
+
if (isLoopbackHost(apiHost) && hasStorefront) {
|
|
1434
|
+
warn(
|
|
1435
|
+
"Local API + storefrontPath detected \u2192 using --local (hosted ?preview= would show the live theme)."
|
|
1436
|
+
);
|
|
1437
|
+
useLocal = true;
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
if (useLocal) {
|
|
1371
1441
|
await runLocalStorefrontPreview(srcDir, opts);
|
|
1372
1442
|
return;
|
|
1373
1443
|
}
|
|
@@ -1632,10 +1702,16 @@ function collectPublishableComponents(srcDir) {
|
|
|
1632
1702
|
const entries = [...byId.entries()].map(([id, entryPath]) => ({ id, entryPath })).sort((a, b) => a.id.localeCompare(b.id));
|
|
1633
1703
|
for (const { id: name, entryPath } of entries) {
|
|
1634
1704
|
const isDir = fs9.statSync(entryPath).isDirectory();
|
|
1635
|
-
const
|
|
1636
|
-
const
|
|
1637
|
-
const
|
|
1638
|
-
const
|
|
1705
|
+
const namedJson = isDir ? path11.join(entryPath, `${name}.json`) : null;
|
|
1706
|
+
const legacyJson = isDir ? path11.join(entryPath, "component.json") : null;
|
|
1707
|
+
const metaPath = isDir ? namedJson && fs9.existsSync(namedJson) ? namedJson : legacyJson && fs9.existsSync(legacyJson) ? legacyJson : null : entryPath.endsWith(".json") ? entryPath : null;
|
|
1708
|
+
const namedTsx = isDir ? path11.join(entryPath, `${name}.tsx`) : null;
|
|
1709
|
+
const namedJsx = isDir ? path11.join(entryPath, `${name}.jsx`) : null;
|
|
1710
|
+
const legacyTsx = isDir ? path11.join(entryPath, "component.tsx") : null;
|
|
1711
|
+
const legacyJsx = isDir ? path11.join(entryPath, "component.jsx") : null;
|
|
1712
|
+
const tsxPath = isDir ? namedTsx && fs9.existsSync(namedTsx) ? namedTsx : legacyTsx && fs9.existsSync(legacyTsx) ? legacyTsx : null : entryPath.endsWith(".tsx") ? entryPath : null;
|
|
1713
|
+
const jsxPath = isDir ? namedJsx && fs9.existsSync(namedJsx) ? namedJsx : legacyJsx && fs9.existsSync(legacyJsx) ? legacyJsx : null : entryPath.endsWith(".jsx") ? entryPath : null;
|
|
1714
|
+
const entry = tsxPath ? tsxPath : jsxPath ? jsxPath : null;
|
|
1639
1715
|
let meta = {};
|
|
1640
1716
|
if (metaPath && fs9.existsSync(metaPath)) {
|
|
1641
1717
|
meta = JSON.parse(fs9.readFileSync(metaPath, "utf8"));
|
|
@@ -2102,6 +2178,20 @@ async function initFromPublicTemplate(dest, name, templateId, ff) {
|
|
|
2102
2178
|
download.start("Downloading template\u2026");
|
|
2103
2179
|
const full = await ff.storeTemplates.find({ id: templateId });
|
|
2104
2180
|
download.stop("Template downloaded");
|
|
2181
|
+
const listPrice = Math.max(
|
|
2182
|
+
0,
|
|
2183
|
+
Number(full.price ?? 0) - Number(full.discount ?? 0)
|
|
2184
|
+
);
|
|
2185
|
+
if (listPrice > 0 && !full.data) {
|
|
2186
|
+
fail(
|
|
2187
|
+
"This is a paid template. Purchase + install it from the merchant app \u2014 CLI unpack of paid source is not available."
|
|
2188
|
+
);
|
|
2189
|
+
}
|
|
2190
|
+
if (listPrice > 0) {
|
|
2191
|
+
fail(
|
|
2192
|
+
`Paid template (${listPrice} DZD). Buy it for your store in the merchant app; do not unpack with feeef template init --from.`
|
|
2193
|
+
);
|
|
2194
|
+
}
|
|
2105
2195
|
const data = full.data;
|
|
2106
2196
|
if (!data || typeof data !== "object" || !("pages" in data)) {
|
|
2107
2197
|
fail("Template has no usable data.pages \u2014 cannot unpack");
|
|
@@ -2432,8 +2522,10 @@ async function runTemplatePublish(dirArg, opts) {
|
|
|
2432
2522
|
}
|
|
2433
2523
|
const store = await ff.stores.find({ id: storeId });
|
|
2434
2524
|
const title = opts.title || rc.title || meta.name || path13.basename(srcDir);
|
|
2525
|
+
const price = Math.max(0, Number(opts.price ?? 0) || 0);
|
|
2435
2526
|
info(`Template ${srcDir}`);
|
|
2436
2527
|
info(`Store ${store.slug || storeId} (${policy})`);
|
|
2528
|
+
if (price > 0) info(`Price ${price} DZD (platform cut applies on sale)`);
|
|
2437
2529
|
const outPath = path13.join(srcDir, "dist", "data.json");
|
|
2438
2530
|
const s = spinner2();
|
|
2439
2531
|
s.start("Building\u2026");
|
|
@@ -2587,17 +2679,19 @@ async function runTemplatePublish(dirArg, opts) {
|
|
|
2587
2679
|
if (templateRow) {
|
|
2588
2680
|
templateRow = await ff.storeTemplates.update({
|
|
2589
2681
|
id: templateRow.id,
|
|
2590
|
-
data: { title, schema, data, policy }
|
|
2682
|
+
data: { title, schema, data, policy, price }
|
|
2591
2683
|
});
|
|
2592
2684
|
s.stop(`Updated ${templateRow.id}`);
|
|
2593
2685
|
} else {
|
|
2594
2686
|
templateRow = await ff.storeTemplates.create({
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2687
|
+
data: {
|
|
2688
|
+
storeId,
|
|
2689
|
+
title,
|
|
2690
|
+
schema,
|
|
2691
|
+
data,
|
|
2692
|
+
policy,
|
|
2693
|
+
price
|
|
2694
|
+
}
|
|
2601
2695
|
});
|
|
2602
2696
|
s.stop(`Created ${templateRow.id}`);
|
|
2603
2697
|
}
|
|
@@ -2622,6 +2716,24 @@ async function runTemplatePublish(dirArg, opts) {
|
|
|
2622
2716
|
}
|
|
2623
2717
|
s.stop(`Locales: ${locales.map((l) => l.locale).join(", ")}`);
|
|
2624
2718
|
}
|
|
2719
|
+
if (typeof ff.storeTemplates.createRelease === "function") {
|
|
2720
|
+
s.start("Publishing release\u2026");
|
|
2721
|
+
const release = await ff.storeTemplates.createRelease(templateId, {
|
|
2722
|
+
...opts.version ? { version: opts.version } : {},
|
|
2723
|
+
changelog: opts.changelog ?? null,
|
|
2724
|
+
schema,
|
|
2725
|
+
data
|
|
2726
|
+
});
|
|
2727
|
+
s.stop(`Release ${release.version} (${release.id})`);
|
|
2728
|
+
} else {
|
|
2729
|
+
await http3.post(`/store_templates/${templateId}/releases`, {
|
|
2730
|
+
...opts.version ? { version: opts.version } : {},
|
|
2731
|
+
changelog: opts.changelog ?? null,
|
|
2732
|
+
schema,
|
|
2733
|
+
data
|
|
2734
|
+
});
|
|
2735
|
+
ok("Release published");
|
|
2736
|
+
}
|
|
2625
2737
|
const shouldApply = opts.yes && opts.apply !== false ? true : await askApply(store.slug || storeId, opts.apply);
|
|
2626
2738
|
if (shouldApply) {
|
|
2627
2739
|
s.start("Installing on store\u2026");
|
|
@@ -2768,9 +2880,11 @@ async function runTemplateWatch(dirArg) {
|
|
|
2768
2880
|
}
|
|
2769
2881
|
|
|
2770
2882
|
// src/index.ts
|
|
2883
|
+
var require2 = createRequire(import.meta.url);
|
|
2884
|
+
var { version: CLI_VERSION } = require2("../package.json");
|
|
2771
2885
|
function createProgram() {
|
|
2772
2886
|
const program = new Command();
|
|
2773
|
-
program.name("feeef").description("Feeef developer CLI \u2014 templates today, cloud tools tomorrow").version(
|
|
2887
|
+
program.name("feeef").description("Feeef developer CLI \u2014 templates today, cloud tools tomorrow").version(CLI_VERSION).hook("preAction", (thisCommand) => {
|
|
2774
2888
|
if (thisCommand.args.length === 0) return;
|
|
2775
2889
|
banner();
|
|
2776
2890
|
});
|
|
@@ -2802,7 +2916,7 @@ function createProgram() {
|
|
|
2802
2916
|
).argument("[store]", "Store id or slug (optional \u2014 interactive picker)").option("--api-url <url>", "API override for this template").action(async (store, opts) => {
|
|
2803
2917
|
await runUse(store, opts);
|
|
2804
2918
|
});
|
|
2805
|
-
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) => {
|
|
2919
|
+
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("--price <n>", "List price in DZD (0 = free)").option("--version <semver>", "Release version (default: auto bump)").option("--changelog <text>", "Release changelog for merchants").option("--yes", "Skip prompts").option("--apply", "Install onto the store").option("--no-apply", "Do not install").action(async (dir, opts) => {
|
|
2806
2920
|
await runTemplatePublish(dir, opts);
|
|
2807
2921
|
});
|
|
2808
2922
|
const template = program.command("template").description(
|
|
@@ -2867,7 +2981,7 @@ function createProgram() {
|
|
|
2867
2981
|
).action(async (dir, opts) => {
|
|
2868
2982
|
await runTemplateDev(dir, opts);
|
|
2869
2983
|
});
|
|
2870
|
-
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) => {
|
|
2984
|
+
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("--price <n>", "List price in DZD (0 = free)").option("--version <semver>", "Release version (default: auto bump)").option("--changelog <text>", "Release changelog for merchants").option("--yes", "Skip prompts").option("--apply", "Install onto the store").option("--no-apply", "Do not install").action(async (dir, opts) => {
|
|
2871
2985
|
await runTemplatePublish(dir, opts);
|
|
2872
2986
|
});
|
|
2873
2987
|
const cloud = program.command("cloud").description("Cloud providers (Azure / GCP / AWS) \u2014 coming soon");
|