@zalify/cli 0.9.1 → 0.11.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/cli.js +549 -112
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -11293,7 +11293,7 @@ function updateNotifier(options) {
|
|
|
11293
11293
|
|
|
11294
11294
|
// src/cli.ts
|
|
11295
11295
|
import { createRequire as createRequire2 } from "node:module";
|
|
11296
|
-
import { dirname as dirname2, join as
|
|
11296
|
+
import { dirname as dirname2, join as join8 } from "node:path";
|
|
11297
11297
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
11298
11298
|
|
|
11299
11299
|
// src/auth.ts
|
|
@@ -12064,9 +12064,9 @@ async function imagesGenerate(storeDir) {
|
|
|
12064
12064
|
import { readFileSync as readFileSync6, existsSync as existsSync6 } from "node:fs";
|
|
12065
12065
|
import { basename as basename3, join as join6, resolve as resolve5 } from "node:path";
|
|
12066
12066
|
|
|
12067
|
-
// src/
|
|
12068
|
-
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
12069
|
-
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
|
|
12067
|
+
// src/site.ts
|
|
12068
|
+
import { spawn as spawn3, spawnSync as spawnSync3 } from "node:child_process";
|
|
12069
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
12070
12070
|
import { basename as basename2, join as join5, resolve as resolve4 } from "node:path";
|
|
12071
12071
|
|
|
12072
12072
|
// src/theme.ts
|
|
@@ -12493,11 +12493,11 @@ function mergePackageJson(projectDir, oldDir, newDir, dryRun) {
|
|
|
12493
12493
|
return depsChanged;
|
|
12494
12494
|
}
|
|
12495
12495
|
|
|
12496
|
-
// src/
|
|
12496
|
+
// src/site.ts
|
|
12497
12497
|
function apiError2(status, json, appUrl) {
|
|
12498
12498
|
if (status === 402) {
|
|
12499
12499
|
return new Error(`${json.error ?? "Upgrade required"}
|
|
12500
|
-
` + `
|
|
12500
|
+
` + ` Site hosting is a paid feature — upgrade the workspace in ${appUrl}`);
|
|
12501
12501
|
}
|
|
12502
12502
|
if (json.code === "NO_CONNECTION") {
|
|
12503
12503
|
return new Error(`${json.error}
|
|
@@ -12508,40 +12508,44 @@ function apiError2(status, json, appUrl) {
|
|
|
12508
12508
|
Pick a different directory/slug name.`);
|
|
12509
12509
|
}
|
|
12510
12510
|
if (status === 404) {
|
|
12511
|
-
return new Error("The
|
|
12511
|
+
return new Error("The sites API is not deployed yet (404). Update app.zalify.com first.");
|
|
12512
12512
|
}
|
|
12513
12513
|
if (status === 503) {
|
|
12514
|
-
return new Error(json.error ?? "
|
|
12514
|
+
return new Error(json.error ?? "Site provisioning is not configured on the server.");
|
|
12515
12515
|
}
|
|
12516
|
-
return new Error(`
|
|
12516
|
+
return new Error(`sites API ${status}: ${JSON.stringify(json)}`);
|
|
12517
12517
|
}
|
|
12518
|
-
async function
|
|
12519
|
-
const
|
|
12520
|
-
method: "
|
|
12521
|
-
|
|
12522
|
-
|
|
12523
|
-
|
|
12518
|
+
async function request(auth, method, path9, body) {
|
|
12519
|
+
const attempt = async (p) => {
|
|
12520
|
+
const url = method === "GET" ? `${auth.appUrl}${p}${p.includes("?") ? "&" : "?"}workspaceId=${auth.workspaceId}` : `${auth.appUrl}${p}`;
|
|
12521
|
+
return fetch(url, {
|
|
12522
|
+
method,
|
|
12523
|
+
headers: {
|
|
12524
|
+
Authorization: `Bearer ${auth.key}`,
|
|
12525
|
+
...method === "POST" ? { "Content-Type": "application/json" } : {}
|
|
12526
|
+
},
|
|
12527
|
+
...method === "POST" ? { body: JSON.stringify({ workspaceId: auth.workspaceId, ...body }) } : {}
|
|
12528
|
+
});
|
|
12529
|
+
};
|
|
12530
|
+
let res = await attempt(path9);
|
|
12531
|
+
if (res.status === 404 && path9.startsWith("/api/sites")) {
|
|
12532
|
+
res = await attempt(path9.replace("/api/sites", "/api/storefronts"));
|
|
12533
|
+
}
|
|
12524
12534
|
const json = await res.json().catch(() => ({}));
|
|
12525
12535
|
if (!res.ok)
|
|
12526
12536
|
throw apiError2(res.status, json, auth.appUrl);
|
|
12527
12537
|
return json;
|
|
12528
12538
|
}
|
|
12529
|
-
async function
|
|
12530
|
-
const
|
|
12531
|
-
|
|
12532
|
-
headers: { Authorization: `Bearer ${auth.key}` }
|
|
12533
|
-
});
|
|
12534
|
-
const json = await res.json().catch(() => ({}));
|
|
12535
|
-
if (!res.ok)
|
|
12536
|
-
throw apiError2(res.status, json, auth.appUrl);
|
|
12537
|
-
return json;
|
|
12539
|
+
async function listSites(auth) {
|
|
12540
|
+
const json = await request(auth, "GET", "/api/sites");
|
|
12541
|
+
return json.sites ?? json.storefronts ?? [];
|
|
12538
12542
|
}
|
|
12539
12543
|
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
12540
12544
|
async function pollUntilReady(auth, id) {
|
|
12541
12545
|
let lastMessage = "";
|
|
12542
12546
|
const deadline = Date.now() + 10 * 60000;
|
|
12543
12547
|
while (Date.now() < deadline) {
|
|
12544
|
-
const view = await
|
|
12548
|
+
const view = await request(auth, "GET", `/api/sites/${id}`);
|
|
12545
12549
|
const message = view.statusMessage ?? view.status;
|
|
12546
12550
|
if (message !== lastMessage) {
|
|
12547
12551
|
console.log(` … ${message}`);
|
|
@@ -12556,16 +12560,19 @@ async function pollUntilReady(auth, id) {
|
|
|
12556
12560
|
}
|
|
12557
12561
|
throw new Error("Provisioning timed out after 10 minutes — check the Temporal UI.");
|
|
12558
12562
|
}
|
|
12559
|
-
async function
|
|
12563
|
+
async function siteCreate(dir2, opts) {
|
|
12560
12564
|
const auth = requireActive();
|
|
12561
|
-
const slug = basename2(resolve4(dir2)).toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/^-+|-+$/g, "") || "
|
|
12562
|
-
console.log(`Provisioning
|
|
12563
|
-
const
|
|
12565
|
+
const slug = basename2(resolve4(dir2)).toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/^-+|-+$/g, "") || "site";
|
|
12566
|
+
console.log(`Provisioning site "${slug}" (workspace "${auth.workspaceName}")`);
|
|
12567
|
+
const created = await request(auth, "POST", "/api/sites", {
|
|
12564
12568
|
slug,
|
|
12565
12569
|
theme: opts.theme ?? "nextjs",
|
|
12566
12570
|
...opts.to ? { themeVersion: opts.to } : {}
|
|
12567
12571
|
});
|
|
12568
|
-
const
|
|
12572
|
+
const siteId = created.siteId ?? created.storefrontId;
|
|
12573
|
+
if (!siteId)
|
|
12574
|
+
throw new Error("provisioning API returned no site id");
|
|
12575
|
+
const view = await pollUntilReady(auth, siteId);
|
|
12569
12576
|
console.log(` ✓ provisioned: ${view.githubRepo} → https://${view.domain}`);
|
|
12570
12577
|
await themeCreate(dir2, {
|
|
12571
12578
|
template: view.theme,
|
|
@@ -12576,9 +12583,13 @@ async function storefrontCreate(dir2, opts) {
|
|
|
12576
12583
|
install: opts.install,
|
|
12577
12584
|
git: opts.git
|
|
12578
12585
|
});
|
|
12586
|
+
writeFileSync6(join5(resolve4(dir2), ".zalify", "site.json"), JSON.stringify({ id: siteId, slug: view.slug, domain: view.domain, repo: view.githubRepo }, null, 2) + `
|
|
12587
|
+
`);
|
|
12579
12588
|
if (opts.git !== false && view.githubRepo) {
|
|
12580
|
-
const { token, repo } = await
|
|
12589
|
+
const { token, repo } = await request(auth, "POST", `/api/sites/${siteId}/push-token`, {});
|
|
12581
12590
|
const run = (args) => spawnSync3("git", args, { cwd: resolve4(dir2), stdio: "ignore" });
|
|
12591
|
+
run(["add", ".zalify/site.json"]);
|
|
12592
|
+
run(["commit", "-m", "Link site record", "--no-verify"]);
|
|
12582
12593
|
run(["remote", "add", "origin", `https://github.com/${repo}.git`]);
|
|
12583
12594
|
const push = spawnSync3("git", ["push", `https://x-access-token:${token}@github.com/${repo}.git`, "main"], { cwd: resolve4(dir2), stdio: "inherit" });
|
|
12584
12595
|
if (push.status !== 0) {
|
|
@@ -12587,7 +12598,7 @@ async function storefrontCreate(dir2, opts) {
|
|
|
12587
12598
|
run(["update-ref", "refs/remotes/origin/main", "HEAD"]);
|
|
12588
12599
|
run(["config", "branch.main.remote", "origin"]);
|
|
12589
12600
|
run(["config", "branch.main.merge", "refs/heads/main"]);
|
|
12590
|
-
console.log(` ✓ pushed to github.com/${repo} —
|
|
12601
|
+
console.log(` ✓ pushed to github.com/${repo} — deploys on push`);
|
|
12591
12602
|
}
|
|
12592
12603
|
if (view.domain) {
|
|
12593
12604
|
console.log(` Waiting for https://${view.domain} …`);
|
|
@@ -12607,49 +12618,89 @@ Live: https://${view.domain}`);
|
|
|
12607
12618
|
console.log(`
|
|
12608
12619
|
Next steps:
|
|
12609
12620
|
` + ` - author your theme in ${dir2}/theme/ (see theme/README.md), commit + push
|
|
12610
|
-
` + ` - zalify
|
|
12611
|
-
` + ` - zalify
|
|
12621
|
+
` + ` - zalify brand push <brand-dir> --prune # catalog (auto-refreshes this site)
|
|
12622
|
+
` + ` - zalify site open ${slug} # see it live
|
|
12623
|
+
` + ` - zalify theme status ${dir2} # drift check anytime`);
|
|
12624
|
+
}
|
|
12625
|
+
async function resolveSite(auth, slugOrDir) {
|
|
12626
|
+
const fromDir = (dir2) => {
|
|
12627
|
+
const link = join5(dir2, ".zalify", "site.json");
|
|
12628
|
+
if (existsSync5(link)) {
|
|
12629
|
+
const parsed = JSON.parse(readFileSync5(link, "utf8"));
|
|
12630
|
+
if (parsed.slug)
|
|
12631
|
+
return parsed.slug;
|
|
12632
|
+
}
|
|
12633
|
+
const storeJson = join5(dir2, "store.json");
|
|
12634
|
+
if (existsSync5(storeJson)) {
|
|
12635
|
+
const parsed = JSON.parse(readFileSync5(storeJson, "utf8"));
|
|
12636
|
+
if (parsed.slug)
|
|
12637
|
+
return parsed.slug;
|
|
12638
|
+
}
|
|
12639
|
+
return null;
|
|
12640
|
+
};
|
|
12641
|
+
let slug = null;
|
|
12642
|
+
if (slugOrDir && existsSync5(resolve4(slugOrDir)))
|
|
12643
|
+
slug = fromDir(resolve4(slugOrDir));
|
|
12644
|
+
if (!slug && slugOrDir)
|
|
12645
|
+
slug = slugOrDir;
|
|
12646
|
+
if (!slug)
|
|
12647
|
+
slug = fromDir(process.cwd());
|
|
12648
|
+
const sites = await listSites(auth);
|
|
12649
|
+
if (slug) {
|
|
12650
|
+
const match = sites.find((s) => s.slug === slug);
|
|
12651
|
+
if (match)
|
|
12652
|
+
return match;
|
|
12653
|
+
throw new Error(`No site "${slug}" in workspace "${auth.workspaceName}".
|
|
12654
|
+
` + ` Sites here: ${sites.map((s) => s.slug).join(", ") || "(none)"}`);
|
|
12655
|
+
}
|
|
12656
|
+
if (sites.length === 1)
|
|
12657
|
+
return sites[0];
|
|
12658
|
+
throw new Error(sites.length === 0 ? `No sites in workspace "${auth.workspaceName}" — create one with \`zalify site create <dir>\`.` : `Multiple sites in this workspace — name one: ${sites.map((s) => s.slug).join(", ")}`);
|
|
12612
12659
|
}
|
|
12613
|
-
async function
|
|
12660
|
+
async function siteList() {
|
|
12614
12661
|
const auth = requireActive();
|
|
12615
|
-
const
|
|
12616
|
-
if (!
|
|
12617
|
-
console.log(`No
|
|
12662
|
+
const sites = await listSites(auth);
|
|
12663
|
+
if (!sites.length) {
|
|
12664
|
+
console.log(`No sites in workspace "${auth.workspaceName}".`);
|
|
12618
12665
|
return;
|
|
12619
12666
|
}
|
|
12620
|
-
for (const s of
|
|
12621
|
-
const
|
|
12622
|
-
console.log(`${s.status === "READY" ? "✓" : s.status === "FAILED" ? "✗" : "…"} ${s.slug} ${
|
|
12667
|
+
for (const s of sites) {
|
|
12668
|
+
const url = s.domain ? `https://${s.domain}` : "-";
|
|
12669
|
+
console.log(`${s.status === "READY" ? "✓" : s.status === "FAILED" ? "✗" : "…"} ${s.slug} ${url} (${s.status.toLowerCase()}${s.githubRepo ? `, ${s.githubRepo}` : ""})`);
|
|
12623
12670
|
}
|
|
12624
12671
|
}
|
|
12625
|
-
function
|
|
12626
|
-
const
|
|
12627
|
-
|
|
12628
|
-
|
|
12629
|
-
|
|
12630
|
-
return parsed.slug;
|
|
12631
|
-
}
|
|
12632
|
-
return basename2(slugOrDir);
|
|
12672
|
+
async function siteRefresh(slugOrDir) {
|
|
12673
|
+
const auth = requireActive();
|
|
12674
|
+
const site = await resolveSite(auth, slugOrDir);
|
|
12675
|
+
await request(auth, "POST", `/api/sites/${site.id}/revalidate`, {});
|
|
12676
|
+
console.log(`✓ refreshed https://${site.domain}`);
|
|
12633
12677
|
}
|
|
12634
|
-
async function
|
|
12678
|
+
async function siteOpen(slugOrDir) {
|
|
12635
12679
|
const auth = requireActive();
|
|
12636
|
-
const
|
|
12637
|
-
|
|
12638
|
-
|
|
12639
|
-
|
|
12640
|
-
|
|
12641
|
-
|
|
12642
|
-
console.log(
|
|
12643
|
-
}
|
|
12644
|
-
async function
|
|
12680
|
+
const site = await resolveSite(auth, slugOrDir);
|
|
12681
|
+
if (!site.domain)
|
|
12682
|
+
throw new Error(`Site "${site.slug}" has no domain yet.`);
|
|
12683
|
+
const url = `https://${site.domain}`;
|
|
12684
|
+
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
12685
|
+
spawn3(opener, [url], { detached: true, stdio: "ignore" }).unref();
|
|
12686
|
+
console.log(url);
|
|
12687
|
+
}
|
|
12688
|
+
async function maybeRefreshSites(auth, brandDir, siteSlugOverride) {
|
|
12645
12689
|
try {
|
|
12646
|
-
|
|
12647
|
-
|
|
12648
|
-
|
|
12649
|
-
|
|
12690
|
+
let slug = siteSlugOverride ?? null;
|
|
12691
|
+
if (!slug) {
|
|
12692
|
+
const storeJson = join5(resolve4(brandDir), "store.json");
|
|
12693
|
+
if (existsSync5(storeJson)) {
|
|
12694
|
+
slug = JSON.parse(readFileSync5(storeJson, "utf8")).slug ?? null;
|
|
12695
|
+
}
|
|
12696
|
+
}
|
|
12697
|
+
if (!slug)
|
|
12650
12698
|
return;
|
|
12651
|
-
await
|
|
12652
|
-
|
|
12699
|
+
const sites = (await listSites(auth)).filter((s) => s.slug === slug && s.status === "READY");
|
|
12700
|
+
for (const site of sites) {
|
|
12701
|
+
await request(auth, "POST", `/api/sites/${site.id}/revalidate`, {});
|
|
12702
|
+
console.log(`✓ site refreshed (https://${site.domain})`);
|
|
12703
|
+
}
|
|
12653
12704
|
} catch {}
|
|
12654
12705
|
}
|
|
12655
12706
|
|
|
@@ -12829,9 +12880,9 @@ async function shopifyImport(storeDir, options = {}) {
|
|
|
12829
12880
|
console.warn(` ! could not publish to Online Store (write_publications scope missing?): ${e instanceof Error ? e.message : String(e)}`);
|
|
12830
12881
|
}
|
|
12831
12882
|
console.log("Done.");
|
|
12832
|
-
await
|
|
12883
|
+
await maybeRefreshSites(auth, dir2, options.site);
|
|
12833
12884
|
}
|
|
12834
|
-
async function shopifyUploadImages(storeDir) {
|
|
12885
|
+
async function shopifyUploadImages(storeDir, options = {}) {
|
|
12835
12886
|
const auth = requireActive();
|
|
12836
12887
|
const dir2 = resolve5(storeDir);
|
|
12837
12888
|
const manifest = readJson(join6(dir2, "images", "manifest.json"), "images/manifest.json");
|
|
@@ -12907,13 +12958,336 @@ async function shopifyUploadImages(storeDir) {
|
|
|
12907
12958
|
console.log(` ✓ ${entry.file}`);
|
|
12908
12959
|
}
|
|
12909
12960
|
console.log("Done.");
|
|
12910
|
-
await
|
|
12961
|
+
await maybeRefreshSites(auth, dir2, options.site);
|
|
12962
|
+
}
|
|
12963
|
+
|
|
12964
|
+
// src/brand.ts
|
|
12965
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "node:fs";
|
|
12966
|
+
import { basename as basename4, join as join7, resolve as resolve6 } from "node:path";
|
|
12967
|
+
var AUTHORING_MD = `# Brand authoring guide
|
|
12968
|
+
|
|
12969
|
+
The rules for filling in this folder. An author — human or AI agent —
|
|
12970
|
+
should be able to produce a complete, import-ready brand from this file
|
|
12971
|
+
alone. Validate anytime with \`zalify brand validate\` (defaults to cwd).
|
|
12972
|
+
|
|
12973
|
+
## Files
|
|
12974
|
+
|
|
12975
|
+
| File | Role |
|
|
12976
|
+
| ---- | ---- |
|
|
12977
|
+
| store.json | the Shopify store this brand connects to (name, slug, vertical, currency; storeDomain filled in once the store exists) |
|
|
12978
|
+
| brand.md | brand voice guide — tone, audience, naming, price band. Written FIRST; everything else follows it |
|
|
12979
|
+
| catalog.json | canonical catalog (products + collections) — the single source the pipeline imports |
|
|
12980
|
+
| images/manifest.json | one entry per shot + shared style/modelStyle art-direction prefixes |
|
|
12981
|
+
| images/*.png | generated masters (NOT committed — they live in the Zalify asset library; images/assets.json is the committed index) |
|
|
12982
|
+
|
|
12983
|
+
## catalog.json rules
|
|
12984
|
+
|
|
12985
|
+
- 10–14 products is the sweet spot for a demo brand; every product needs
|
|
12986
|
+
\`handle\` (unique, kebab-case, no SKU-speak), \`title\`, \`descriptionHtml\`
|
|
12987
|
+
(brand voice; simple HTML: p/ul/li/em only), \`type\`, \`tags\`, \`variants\`.
|
|
12988
|
+
- **Collections are smart collections driven by tags**: a product joins
|
|
12989
|
+
collection \`x\` by carrying tag \`collection:x\`, and the collection's
|
|
12990
|
+
\`rule\` is \`{ "tag": "collection:x" }\`. Every product belongs to at
|
|
12991
|
+
least one collection.
|
|
12992
|
+
- **Mandatory collections**: \`best-sellers\` and \`sale\`. Sale products
|
|
12993
|
+
carry a real \`compareAtPrice\` AND the \`collection:sale\` tag.
|
|
12994
|
+
- \`options\` is 0–3 option names; each variant's \`options\` array matches
|
|
12995
|
+
that length and order. Single-variant products omit \`options\` and give
|
|
12996
|
+
one variant with \`"options": []\`.
|
|
12997
|
+
- Prices are strings with 2 decimals, in the store currency. Honest
|
|
12998
|
+
markdowns only (15–25% off typical).
|
|
12999
|
+
- Colors and variants get evocative names per brand.md — never "Gray 02".
|
|
13000
|
+
|
|
13001
|
+
## images/manifest.json rules
|
|
13002
|
+
|
|
13003
|
+
- Three shots per hero product: packshot (\`<handle>.png\`), macro detail
|
|
13004
|
+
(\`<handle>-detail.png\`), lifestyle/model (\`<handle>-model.png\`,
|
|
13005
|
+
\`"type": "model"\`). ~33–36 entries total. Every \`handle\` must exist in
|
|
13006
|
+
catalog.json; every entry needs \`alt\` text.
|
|
13007
|
+
- \`style\` prefixes packshot/detail prompts; \`modelStyle\` prefixes
|
|
13008
|
+
\`type: "model"\` prompts. Keep prompts concrete: materials, colors,
|
|
13009
|
+
camera angle, light.
|
|
13010
|
+
- **Prompt safety (gpt-image-2 rejects [sexual] otherwise)** — for
|
|
13011
|
+
human-model shots: say "woman"/"man", never "young …"; keep fitted
|
|
13012
|
+
garments layered or covered (blazer / cardigan / cover-up); crop at
|
|
13013
|
+
the chin or shoot from behind; prefer wider framing. Swimwear: show
|
|
13014
|
+
the garment via flat-lay + detail; make the model shot a cover-up
|
|
13015
|
+
scene. Pet/object products: no such constraints.
|
|
13016
|
+
|
|
13017
|
+
## Pipeline (after authoring)
|
|
13018
|
+
|
|
13019
|
+
\`\`\`sh
|
|
13020
|
+
zalify brand validate # lint this folder (cwd)
|
|
13021
|
+
# create the Shopify dev store + Zalify workspace, then:
|
|
13022
|
+
zalify workspace set <workspace-slug>
|
|
13023
|
+
zalify site create <slug> # hosted site: repo + deploy + domain + token
|
|
13024
|
+
zalify brand push --prune # catalog → your Shopify store
|
|
13025
|
+
zalify brand images generate # create the manifest's shots
|
|
13026
|
+
zalify brand images attach # attach them to the products
|
|
13027
|
+
\`\`\`
|
|
13028
|
+
`;
|
|
13029
|
+
var BRAND_MD = (name) => `# ${name} — brand voice guide
|
|
13030
|
+
|
|
13031
|
+
## Who we are
|
|
13032
|
+
|
|
13033
|
+
<2–3 sentences: what the brand makes, for whom, and the feeling it sells.>
|
|
13034
|
+
|
|
13035
|
+
## Audience
|
|
13036
|
+
|
|
13037
|
+
<Who buys: age band, habits, what they care about. Name the customer the
|
|
13038
|
+
way the brand would ("pet parents", not "owners").>
|
|
13039
|
+
|
|
13040
|
+
## Tone
|
|
13041
|
+
|
|
13042
|
+
- <3–5 bullets: adjectives with teeth, plus what the brand NEVER sounds like.>
|
|
13043
|
+
- <How to address the reader (second person? playful? expert-calm?).>
|
|
13044
|
+
|
|
13045
|
+
## Naming conventions
|
|
13046
|
+
|
|
13047
|
+
- <Pattern for product names, with 2–3 examples.>
|
|
13048
|
+
- <How colors/variants are named (evocative, never "Gray 02").>
|
|
13049
|
+
|
|
13050
|
+
## Price band
|
|
13051
|
+
|
|
13052
|
+
- <Floor–ceiling and what anchors each end. Markdown policy.>
|
|
13053
|
+
|
|
13054
|
+
## Quick sniff test
|
|
13055
|
+
|
|
13056
|
+
<One sentence: how to tell a line of copy is (or isn't) this brand.>
|
|
13057
|
+
`;
|
|
13058
|
+
var STORE_JSON = (name, slug, vertical, currency) => JSON.stringify({
|
|
13059
|
+
name,
|
|
13060
|
+
slug,
|
|
13061
|
+
vertical,
|
|
13062
|
+
currency,
|
|
13063
|
+
storeDomain: null,
|
|
13064
|
+
notes: "Standalone-copy deployment — fill storeDomain when the Shopify dev store exists; no siteUrl on purpose."
|
|
13065
|
+
}, null, 2) + `
|
|
13066
|
+
`;
|
|
13067
|
+
var CATALOG_JSON = (vendor, currency) => JSON.stringify({
|
|
13068
|
+
vendor,
|
|
13069
|
+
currency,
|
|
13070
|
+
products: [
|
|
13071
|
+
{
|
|
13072
|
+
handle: "example-product",
|
|
13073
|
+
title: "The Example Product",
|
|
13074
|
+
descriptionHtml: "<p>Two short paragraphs in the brand voice: lead with the moment the customer uses it, then the practical facts.</p><ul><li>Material / composition</li><li>Care instructions</li><li>Size guidance</li></ul>",
|
|
13075
|
+
type: "Category",
|
|
13076
|
+
tags: ["example", "collection:best-sellers"],
|
|
13077
|
+
options: ["Color"],
|
|
13078
|
+
variants: [
|
|
13079
|
+
{
|
|
13080
|
+
options: ["Oat Cream"],
|
|
13081
|
+
sku: "EXM-001-OAT",
|
|
13082
|
+
price: "29.00",
|
|
13083
|
+
compareAtPrice: null,
|
|
13084
|
+
grams: 500,
|
|
13085
|
+
inventory: 50
|
|
13086
|
+
}
|
|
13087
|
+
],
|
|
13088
|
+
seo: {
|
|
13089
|
+
title: "The Example Product — Brand",
|
|
13090
|
+
description: "One-sentence meta description in the brand voice."
|
|
13091
|
+
}
|
|
13092
|
+
}
|
|
13093
|
+
],
|
|
13094
|
+
collections: [
|
|
13095
|
+
{
|
|
13096
|
+
handle: "best-sellers",
|
|
13097
|
+
title: "Best Sellers",
|
|
13098
|
+
descriptionHtml: "<p>The ones everyone comes back for.</p>",
|
|
13099
|
+
rule: { tag: "collection:best-sellers" }
|
|
13100
|
+
},
|
|
13101
|
+
{
|
|
13102
|
+
handle: "sale",
|
|
13103
|
+
title: "Sale",
|
|
13104
|
+
descriptionHtml: "<p>Real markdowns on favorites.</p>",
|
|
13105
|
+
rule: { tag: "collection:sale" }
|
|
13106
|
+
}
|
|
13107
|
+
]
|
|
13108
|
+
}, null, 2) + `
|
|
13109
|
+
`;
|
|
13110
|
+
var MANIFEST_JSON = JSON.stringify({
|
|
13111
|
+
style: "<shared prefix for packshot/detail prompts: photography style, background, light, 'no text, no watermark. Square 1:1 composition.'>",
|
|
13112
|
+
modelStyle: "<shared prefix for type:'model' lifestyle prompts — scene, mood, framing. Mind the prompt-safety rules in AUTHORING.md for human models.>",
|
|
13113
|
+
images: [
|
|
13114
|
+
{
|
|
13115
|
+
handle: "example-product",
|
|
13116
|
+
file: "example-product.png",
|
|
13117
|
+
alt: "The Example Product in Oat Cream, front three-quarter view",
|
|
13118
|
+
prompt: "<concrete packshot prompt: the product, materials, colors, camera angle>"
|
|
13119
|
+
},
|
|
13120
|
+
{
|
|
13121
|
+
handle: "example-product",
|
|
13122
|
+
file: "example-product-detail.png",
|
|
13123
|
+
alt: "Macro detail of the Example Product's texture",
|
|
13124
|
+
prompt: "<extreme macro close-up prompt: texture, seam, hardware>"
|
|
13125
|
+
},
|
|
13126
|
+
{
|
|
13127
|
+
handle: "example-product",
|
|
13128
|
+
file: "example-product-model.png",
|
|
13129
|
+
type: "model",
|
|
13130
|
+
alt: "The Example Product in use",
|
|
13131
|
+
prompt: "<lifestyle prompt: who/what uses it, where, light>"
|
|
13132
|
+
}
|
|
13133
|
+
]
|
|
13134
|
+
}, null, 2) + `
|
|
13135
|
+
`;
|
|
13136
|
+
function brandInit(dir2 = ".", opts = {}) {
|
|
13137
|
+
const target = resolve6(dir2);
|
|
13138
|
+
const slug = basename4(target).toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/^-+|-+$/g, "");
|
|
13139
|
+
if (existsSync7(target) && readdirSync3(target).length > 0) {
|
|
13140
|
+
throw new Error(`${dir2} already exists and is not empty`);
|
|
13141
|
+
}
|
|
13142
|
+
const name = opts.name ?? slug.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
13143
|
+
const currency = (opts.currency ?? "USD").toUpperCase();
|
|
13144
|
+
mkdirSync3(join7(target, "images"), { recursive: true });
|
|
13145
|
+
writeFileSync7(join7(target, "AUTHORING.md"), AUTHORING_MD);
|
|
13146
|
+
writeFileSync7(join7(target, "brand.md"), BRAND_MD(name));
|
|
13147
|
+
writeFileSync7(join7(target, "store.json"), STORE_JSON(name, slug, opts.vertical ?? "<vertical>", currency));
|
|
13148
|
+
writeFileSync7(join7(target, "catalog.json"), CATALOG_JSON(name, currency));
|
|
13149
|
+
writeFileSync7(join7(target, "images", "manifest.json"), MANIFEST_JSON);
|
|
13150
|
+
console.log(`Scaffolded brand data in ${dir2}:`);
|
|
13151
|
+
for (const f of ["AUTHORING.md", "brand.md", "store.json", "catalog.json", "images/manifest.json"]) {
|
|
13152
|
+
console.log(` + ${f}`);
|
|
13153
|
+
}
|
|
13154
|
+
console.log(`
|
|
13155
|
+
Author in this order: brand.md → catalog.json → images/manifest.json` + `
|
|
13156
|
+
(rules + worked examples in AUTHORING.md; lint with \`zalify brand validate ${dir2}\`)`);
|
|
13157
|
+
}
|
|
13158
|
+
function brandValidate(dir2 = ".") {
|
|
13159
|
+
const target = resolve6(dir2);
|
|
13160
|
+
const problems = [];
|
|
13161
|
+
const warnings = [];
|
|
13162
|
+
const readJson2 = (rel) => {
|
|
13163
|
+
const path9 = join7(target, rel);
|
|
13164
|
+
if (!existsSync7(path9)) {
|
|
13165
|
+
problems.push(`missing ${rel}`);
|
|
13166
|
+
return null;
|
|
13167
|
+
}
|
|
13168
|
+
try {
|
|
13169
|
+
return JSON.parse(readFileSync7(path9, "utf8"));
|
|
13170
|
+
} catch (err) {
|
|
13171
|
+
problems.push(`${rel}: invalid JSON — ${err instanceof Error ? err.message : err}`);
|
|
13172
|
+
return null;
|
|
13173
|
+
}
|
|
13174
|
+
};
|
|
13175
|
+
if (!existsSync7(join7(target, "brand.md")))
|
|
13176
|
+
problems.push("missing brand.md");
|
|
13177
|
+
const store = readJson2("store.json");
|
|
13178
|
+
if (store && (!store.name || !store.slug))
|
|
13179
|
+
problems.push("store.json: name and slug are required");
|
|
13180
|
+
const catalog = readJson2("catalog.json");
|
|
13181
|
+
const handles = new Set;
|
|
13182
|
+
if (catalog) {
|
|
13183
|
+
const products = catalog.products ?? [];
|
|
13184
|
+
if (!products.length)
|
|
13185
|
+
problems.push("catalog.json: no products");
|
|
13186
|
+
if (products.some((p) => p.handle === "example-product")) {
|
|
13187
|
+
warnings.push("catalog.json still contains the example-product stub");
|
|
13188
|
+
}
|
|
13189
|
+
const collectionTags = new Set((catalog.collections ?? []).map((c) => c.rule?.tag).filter(Boolean));
|
|
13190
|
+
for (const c of ["best-sellers", "sale"]) {
|
|
13191
|
+
if (!(catalog.collections ?? []).some((col) => col.handle === c)) {
|
|
13192
|
+
problems.push(`catalog.json: mandatory collection "${c}" missing`);
|
|
13193
|
+
}
|
|
13194
|
+
}
|
|
13195
|
+
for (const p of products) {
|
|
13196
|
+
const where = `product "${p.handle ?? p.title ?? "?"}"`;
|
|
13197
|
+
if (!p.handle || !/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(p.handle)) {
|
|
13198
|
+
problems.push(`${where}: handle must be kebab-case`);
|
|
13199
|
+
} else if (handles.has(p.handle)) {
|
|
13200
|
+
problems.push(`${where}: duplicate handle`);
|
|
13201
|
+
} else {
|
|
13202
|
+
handles.add(p.handle);
|
|
13203
|
+
}
|
|
13204
|
+
if (!p.title || !p.descriptionHtml || !p.type) {
|
|
13205
|
+
problems.push(`${where}: title, descriptionHtml, and type are required`);
|
|
13206
|
+
}
|
|
13207
|
+
const tags = p.tags ?? [];
|
|
13208
|
+
const colTags = tags.filter((t) => t.startsWith("collection:"));
|
|
13209
|
+
if (!colTags.length)
|
|
13210
|
+
problems.push(`${where}: no collection:<handle> tag`);
|
|
13211
|
+
for (const t of colTags) {
|
|
13212
|
+
if (!collectionTags.has(t)) {
|
|
13213
|
+
problems.push(`${where}: tag "${t}" matches no collection rule`);
|
|
13214
|
+
}
|
|
13215
|
+
}
|
|
13216
|
+
const optionCount = p.options?.length ?? 0;
|
|
13217
|
+
if (optionCount > 3)
|
|
13218
|
+
problems.push(`${where}: more than 3 options`);
|
|
13219
|
+
if (!p.variants?.length) {
|
|
13220
|
+
problems.push(`${where}: at least one variant required`);
|
|
13221
|
+
} else {
|
|
13222
|
+
const hasSale = tags.includes("collection:sale");
|
|
13223
|
+
let anyCompareAt = false;
|
|
13224
|
+
for (const v of p.variants) {
|
|
13225
|
+
if ((v.options?.length ?? 0) !== optionCount) {
|
|
13226
|
+
problems.push(`${where}: variant options length must match options (${optionCount})`);
|
|
13227
|
+
}
|
|
13228
|
+
if (!v.price || !/^\d+\.\d{2}$/.test(v.price)) {
|
|
13229
|
+
problems.push(`${where}: price must be a string with 2 decimals`);
|
|
13230
|
+
}
|
|
13231
|
+
if (v.compareAtPrice != null) {
|
|
13232
|
+
anyCompareAt = true;
|
|
13233
|
+
if (!/^\d+\.\d{2}$/.test(v.compareAtPrice)) {
|
|
13234
|
+
problems.push(`${where}: compareAtPrice must be a string with 2 decimals`);
|
|
13235
|
+
} else if (v.price && Number(v.compareAtPrice) <= Number(v.price)) {
|
|
13236
|
+
problems.push(`${where}: compareAtPrice must exceed price`);
|
|
13237
|
+
}
|
|
13238
|
+
}
|
|
13239
|
+
}
|
|
13240
|
+
if (hasSale && !anyCompareAt) {
|
|
13241
|
+
problems.push(`${where}: in collection:sale but no variant has compareAtPrice`);
|
|
13242
|
+
}
|
|
13243
|
+
if (anyCompareAt && !hasSale) {
|
|
13244
|
+
warnings.push(`${where}: has compareAtPrice but not tagged collection:sale`);
|
|
13245
|
+
}
|
|
13246
|
+
}
|
|
13247
|
+
}
|
|
13248
|
+
}
|
|
13249
|
+
const manifest = readJson2("images/manifest.json");
|
|
13250
|
+
if (manifest) {
|
|
13251
|
+
const images = manifest.images ?? [];
|
|
13252
|
+
if (!images.length)
|
|
13253
|
+
problems.push("images/manifest.json: no images");
|
|
13254
|
+
if (!manifest.style || manifest.style.startsWith("<")) {
|
|
13255
|
+
warnings.push("images/manifest.json: style prefix not filled in");
|
|
13256
|
+
}
|
|
13257
|
+
const files = new Set;
|
|
13258
|
+
for (const entry of images) {
|
|
13259
|
+
const where = `image "${entry.file ?? "?"}"`;
|
|
13260
|
+
if (!entry.file || !entry.prompt)
|
|
13261
|
+
problems.push(`${where}: file and prompt required`);
|
|
13262
|
+
if (!entry.alt)
|
|
13263
|
+
problems.push(`${where}: alt text required`);
|
|
13264
|
+
if (entry.file) {
|
|
13265
|
+
if (files.has(entry.file))
|
|
13266
|
+
problems.push(`${where}: duplicate file`);
|
|
13267
|
+
files.add(entry.file);
|
|
13268
|
+
}
|
|
13269
|
+
if (entry.handle && handles.size && !handles.has(entry.handle)) {
|
|
13270
|
+
problems.push(`${where}: handle "${entry.handle}" not in catalog.json`);
|
|
13271
|
+
}
|
|
13272
|
+
if (entry.type === "model" && (!manifest.modelStyle || manifest.modelStyle.startsWith("<"))) {
|
|
13273
|
+
warnings.push("images/manifest.json: modelStyle prefix not filled in (model shots present)");
|
|
13274
|
+
}
|
|
13275
|
+
}
|
|
13276
|
+
}
|
|
13277
|
+
for (const w of [...new Set(warnings)])
|
|
13278
|
+
console.log(` ! ${w}`);
|
|
13279
|
+
if (problems.length) {
|
|
13280
|
+
for (const p of problems)
|
|
13281
|
+
console.log(` ✗ ${p}`);
|
|
13282
|
+
throw new Error(`${problems.length} problem(s) — fix and re-run \`zalify brand validate\``);
|
|
13283
|
+
}
|
|
13284
|
+
console.log(`✓ valid: ${handles.size} product(s), ${catalog?.collections?.length ?? 0} collection(s), ${manifest?.images?.length ?? 0} image entries` + (warnings.length ? ` — ${warnings.length} warning(s)` : ""));
|
|
12911
13285
|
}
|
|
12912
13286
|
|
|
12913
13287
|
// src/cli.ts
|
|
12914
13288
|
var __dirname4 = dirname2(fileURLToPath3(import.meta.url));
|
|
12915
13289
|
var require2 = createRequire2(import.meta.url);
|
|
12916
|
-
var pkg = require2(
|
|
13290
|
+
var pkg = require2(join8(__dirname4, "..", "package.json"));
|
|
12917
13291
|
try {
|
|
12918
13292
|
updateNotifier({ pkg }).notify({
|
|
12919
13293
|
isGlobal: true,
|
|
@@ -12921,7 +13295,78 @@ try {
|
|
|
12921
13295
|
});
|
|
12922
13296
|
} catch {}
|
|
12923
13297
|
var program2 = new Command;
|
|
12924
|
-
program2.name("zalify").description("Zalify CLI
|
|
13298
|
+
program2.name("zalify").description("Zalify CLI — author a brand, launch its site, keep the theme current").version(pkg.version, "-v, --version", "output the current version");
|
|
13299
|
+
program2.addHelpText("after", `
|
|
13300
|
+
The golden path (new brand, start to live):
|
|
13301
|
+
1. zalify brand init stores/acme author the data (no login needed)
|
|
13302
|
+
2. zalify login && zalify workspace set <workspace>
|
|
13303
|
+
3. zalify site create acme repo + hosting + domain + deploy
|
|
13304
|
+
4. cd stores/acme && zalify brand push --prune
|
|
13305
|
+
5. zalify brand images generate && zalify brand images attach
|
|
13306
|
+
6. zalify theme status | upgrade stay current, keep your edits
|
|
13307
|
+
`);
|
|
13308
|
+
var moved = (to) => console.log(`(moved — this is now \`zalify ${to}\`)`);
|
|
13309
|
+
program2.command("login").description("Authenticate via the browser (one login covers all your workspaces)").option("--app-url <url>", "Zalify app origin (default: https://app.zalify.com)").action(async (options) => {
|
|
13310
|
+
await login(options);
|
|
13311
|
+
});
|
|
13312
|
+
program2.command("logout").description("Delete the stored CLI credentials").action(() => logout());
|
|
13313
|
+
program2.command("whoami").description("Verify the stored key and show workspace, plan, and key details").action(async () => {
|
|
13314
|
+
await whoami();
|
|
13315
|
+
});
|
|
13316
|
+
var workspace = program2.command("workspace").description("Active workspace — which Shopify store your commands target");
|
|
13317
|
+
workspace.command("list", { isDefault: true }).description("List authorized workspaces (* = active)").action(async () => {
|
|
13318
|
+
await workspaceList();
|
|
13319
|
+
});
|
|
13320
|
+
workspace.command("set <slug-or-id>").description("Switch the active workspace (local, no re-login)").action(async (slugOrId) => {
|
|
13321
|
+
await workspaceSet(slugOrId);
|
|
13322
|
+
});
|
|
13323
|
+
var brand = program2.command("brand").description("Brand data — author the catalog, voice, and imagery; push it to your Shopify store");
|
|
13324
|
+
brand.command("init [dir]").description("Scaffold a brand folder (catalog, voice guide, image manifest + AUTHORING.md rules); no login needed").option("--name <name>", "brand name (default: derived from the directory)").option("--vertical <vertical>", "e.g. womens-fast-fashion, pet-supplies").option("--currency <code>", "ISO currency", "USD").action((dir2, options) => {
|
|
13325
|
+
brandInit(dir2, options);
|
|
13326
|
+
});
|
|
13327
|
+
brand.command("validate [dir]").description("Lint the brand folder against the authoring rules").action((dir2) => {
|
|
13328
|
+
brandValidate(dir2);
|
|
13329
|
+
});
|
|
13330
|
+
brand.command("push [dir]").description("Push catalog.json to your connected Shopify store (products, smart collections, publish; idempotent)").option("--prune", "delete store products (by catalog vendor) absent from catalog.json").option("--site <slug>", "refresh only this site afterwards (default: every site matching the brand)").action(async (dir2, options) => {
|
|
13331
|
+
await shopifyImport(dir2 ?? ".", options);
|
|
13332
|
+
});
|
|
13333
|
+
var brandImages = brand.command("images").description("The brand's imagery pipeline: generate → attach → push/pull masters");
|
|
13334
|
+
brandImages.command("generate [dir]").description("Generate the manifest's missing images on Zalify infrastructure (resumable)").action(async (dir2) => {
|
|
13335
|
+
await imagesGenerate(dir2 ?? ".");
|
|
13336
|
+
});
|
|
13337
|
+
brandImages.command("attach [dir]").description("Attach generated images to their Shopify products per the manifest (skips already-attached)").option("--site <slug>", "refresh only this site afterwards").action(async (dir2, options) => {
|
|
13338
|
+
await shopifyUploadImages(dir2 ?? ".", options);
|
|
13339
|
+
});
|
|
13340
|
+
brandImages.command("push [dir]").description("Upload image masters to the Zalify asset library (sha256 dedup, writes assets.json)").action(async (dir2) => {
|
|
13341
|
+
await assetsPush(dir2 ?? ".");
|
|
13342
|
+
});
|
|
13343
|
+
brandImages.command("pull [dir]").description("Download masters listed in images/assets.json (fresh-clone restore)").action(async (dir2) => {
|
|
13344
|
+
await assetsPull(dir2 ?? ".");
|
|
13345
|
+
});
|
|
13346
|
+
var site = program2.command("site").description("Hosted sites — Zalify provisions the repo, deployment, and domain");
|
|
13347
|
+
site.command("create <dir>").description("One-command launch: provision repo + deployment + domain + storefront token, scaffold locally, push, go live").option("-t, --theme <name>", "theme", "nextjs").option("--editor", "include the Zalify canvas-editor (z1) wiring").option("--to <version>", "theme version (default: latest)").option("--no-install", "skip dependency install").option("--no-git", "skip git init/push").action(async (dir2, options) => {
|
|
13348
|
+
await siteCreate(dir2, options);
|
|
13349
|
+
});
|
|
13350
|
+
site.command("list", { isDefault: true }).description("List the workspace's hosted sites").action(async () => {
|
|
13351
|
+
await siteList();
|
|
13352
|
+
});
|
|
13353
|
+
site.command("refresh [slug-or-dir]").description("Refresh the live site's data cache (defaults to the site linked to cwd)").action(async (slugOrDir) => {
|
|
13354
|
+
await siteRefresh(slugOrDir);
|
|
13355
|
+
});
|
|
13356
|
+
site.command("open [slug-or-dir]").description("Open the live site in your browser (defaults to the site linked to cwd)").action(async (slugOrDir) => {
|
|
13357
|
+
await siteOpen(slugOrDir);
|
|
13358
|
+
});
|
|
13359
|
+
var theme = program2.command("theme").description("Theme source code — scaffold a copy and upgrade it without losing your edits");
|
|
13360
|
+
theme.command("create <dir>").description("Scaffold only — no Zalify hosting (self-host path; `site create` is the fully-hosted launch)").option("-t, --template <name>", "liquid | hydrogen | nextjs", "nextjs").option("--editor", "include the Zalify canvas-editor (z1) wiring").option("--store-domain <domain>", "your-store.myshopify.com (default: mock.shop demo data)").option("--storefront-token <token>", "public Storefront API access token").option("--to <version>", "theme version (default: latest)").option("--no-install", "skip dependency install").option("--no-git", "skip git init + initial commit").option("--tarball <path>", "use a local @zalify/theme-templates .tgz instead of npm").action(async (dir2, options) => {
|
|
13361
|
+
await themeCreate(dir2, options);
|
|
13362
|
+
});
|
|
13363
|
+
theme.command("status [dir]").description("Which theme files have you edited? (checksums vs .zalify/theme.json)").action((dir2) => {
|
|
13364
|
+
themeStatus(dir2);
|
|
13365
|
+
});
|
|
13366
|
+
theme.command("upgrade [dir]").description("Move to a newer theme version: untouched files overwritten, edited files 3-way-merged, theme/ + .env never touched").option("--to <version>", "target theme version (default: latest)").option("--dry-run", "classify and report without writing anything").option("--install", "run install when dependencies changed").option("--tarball <path>", "target version as a local .tgz (testing)").option("--base-tarball <path>", "base version as a local .tgz (testing)").action(async (dir2, options) => {
|
|
13367
|
+
await themeUpgrade(dir2, options);
|
|
13368
|
+
});
|
|
13369
|
+
program2.command("self-update").description("Update the CLI to the latest version (auto-detects pnpm/bun/npm)").action(() => selfUpdate(pkg.version));
|
|
12925
13370
|
program2.command("version").description("Print Zalify CLI version and check for updates").action(async () => {
|
|
12926
13371
|
console.log(`Zalify CLI version: ${pkg.version}`);
|
|
12927
13372
|
const isNewer = (a, b) => {
|
|
@@ -12938,62 +13383,54 @@ program2.command("version").description("Print Zalify CLI version and check for
|
|
|
12938
13383
|
const update = await notifier.fetchInfo();
|
|
12939
13384
|
if (update && isNewer(update.latest, update.current)) {
|
|
12940
13385
|
console.log(`
|
|
12941
|
-
A new version (${update.latest}) is available. Run "
|
|
13386
|
+
A new version (${update.latest}) is available. Run "zalify self-update" to update.`);
|
|
12942
13387
|
}
|
|
12943
13388
|
} catch {}
|
|
12944
13389
|
});
|
|
12945
|
-
program2.command("
|
|
12946
|
-
|
|
12947
|
-
|
|
13390
|
+
var legacyStore = program2.command("store", { hidden: true });
|
|
13391
|
+
legacyStore.command("init <dir>").option("--name <name>").option("--vertical <vertical>").option("--currency <code>").action((dir2, options) => {
|
|
13392
|
+
moved("brand init");
|
|
13393
|
+
brandInit(dir2, options);
|
|
12948
13394
|
});
|
|
12949
|
-
|
|
12950
|
-
|
|
12951
|
-
|
|
13395
|
+
legacyStore.command("validate <dir>").action((dir2) => {
|
|
13396
|
+
moved("brand validate");
|
|
13397
|
+
brandValidate(dir2);
|
|
12952
13398
|
});
|
|
12953
|
-
var
|
|
12954
|
-
|
|
12955
|
-
|
|
12956
|
-
});
|
|
12957
|
-
workspace.command("set <slug-or-id>").description("Switch the active workspace (local, no re-login)").action(async (slugOrId) => {
|
|
12958
|
-
await workspaceSet(slugOrId);
|
|
12959
|
-
});
|
|
12960
|
-
var images = program2.command("images").description("Generate images on Zalify infrastructure");
|
|
12961
|
-
images.command("generate <store-dir>").description("Generate missing manifest images server-side (results land in the asset library and are downloaded locally)").action(async (storeDir) => {
|
|
12962
|
-
await imagesGenerate(storeDir);
|
|
12963
|
-
});
|
|
12964
|
-
var shopify = program2.command("shopify").description("Push catalogs and images to the workspace's connected Shopify store");
|
|
12965
|
-
shopify.command("import <store-dir>").description("Import <store-dir>/catalog.json into the connected Shopify store (products, smart collections, Online Store publish; idempotent by handle)").option("--prune", "delete store products (by catalog vendor) absent from catalog.json").action(async (storeDir, options) => {
|
|
13399
|
+
var legacyShopify = program2.command("shopify", { hidden: true });
|
|
13400
|
+
legacyShopify.command("import <store-dir>").option("--prune").action(async (storeDir, options) => {
|
|
13401
|
+
moved("brand push");
|
|
12966
13402
|
await shopifyImport(storeDir, options);
|
|
12967
13403
|
});
|
|
12968
|
-
|
|
13404
|
+
legacyShopify.command("upload-images <store-dir>").action(async (storeDir) => {
|
|
13405
|
+
moved("brand images attach");
|
|
12969
13406
|
await shopifyUploadImages(storeDir);
|
|
12970
13407
|
});
|
|
12971
|
-
var
|
|
12972
|
-
|
|
12973
|
-
|
|
12974
|
-
|
|
12975
|
-
storefront.command("list", { isDefault: true }).description("List the workspace's provisioned storefronts").action(async () => {
|
|
12976
|
-
await storefrontList();
|
|
12977
|
-
});
|
|
12978
|
-
storefront.command("revalidate <slug-or-store-dir>").description("Flush the storefront site's data cache (secret stays server-side)").action(async (slugOrDir) => {
|
|
12979
|
-
await storefrontRevalidate(slugOrDir);
|
|
13408
|
+
var legacyImages = program2.command("images", { hidden: true });
|
|
13409
|
+
legacyImages.command("generate <store-dir>").action(async (storeDir) => {
|
|
13410
|
+
moved("brand images generate");
|
|
13411
|
+
await imagesGenerate(storeDir);
|
|
12980
13412
|
});
|
|
12981
|
-
var
|
|
12982
|
-
|
|
12983
|
-
|
|
13413
|
+
var legacyAssets = program2.command("assets", { hidden: true });
|
|
13414
|
+
legacyAssets.command("push <store-dir>").action(async (storeDir) => {
|
|
13415
|
+
moved("brand images push");
|
|
13416
|
+
await assetsPush(storeDir);
|
|
12984
13417
|
});
|
|
12985
|
-
|
|
12986
|
-
|
|
13418
|
+
legacyAssets.command("pull <store-dir>").action(async (storeDir) => {
|
|
13419
|
+
moved("brand images pull");
|
|
13420
|
+
await assetsPull(storeDir);
|
|
12987
13421
|
});
|
|
12988
|
-
|
|
12989
|
-
|
|
13422
|
+
var legacyStorefront = program2.command("storefront", { hidden: true });
|
|
13423
|
+
legacyStorefront.command("create <dir>").option("-t, --theme <name>", "", "nextjs").option("--editor").option("--to <version>").option("--no-install").option("--no-git").action(async (dir2, options) => {
|
|
13424
|
+
moved("site create");
|
|
13425
|
+
await siteCreate(dir2, options);
|
|
12990
13426
|
});
|
|
12991
|
-
|
|
12992
|
-
|
|
12993
|
-
await
|
|
13427
|
+
legacyStorefront.command("list", { isDefault: true }).action(async () => {
|
|
13428
|
+
moved("site list");
|
|
13429
|
+
await siteList();
|
|
12994
13430
|
});
|
|
12995
|
-
|
|
12996
|
-
|
|
13431
|
+
legacyStorefront.command("revalidate <slug-or-store-dir>").action(async (slugOrDir) => {
|
|
13432
|
+
moved("site refresh");
|
|
13433
|
+
await siteRefresh(slugOrDir);
|
|
12997
13434
|
});
|
|
12998
13435
|
program2.parseAsync().catch((err) => {
|
|
12999
13436
|
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|