@zalify/cli 0.10.0 → 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 +246 -139
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -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,23 +12958,23 @@ 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);
|
|
12911
12962
|
}
|
|
12912
12963
|
|
|
12913
|
-
// src/
|
|
12914
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as
|
|
12964
|
+
// src/brand.ts
|
|
12965
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "node:fs";
|
|
12915
12966
|
import { basename as basename4, join as join7, resolve as resolve6 } from "node:path";
|
|
12916
|
-
var AUTHORING_MD = `#
|
|
12967
|
+
var AUTHORING_MD = `# Brand authoring guide
|
|
12917
12968
|
|
|
12918
12969
|
The rules for filling in this folder. An author — human or AI agent —
|
|
12919
|
-
should be able to produce a complete, import-ready
|
|
12920
|
-
alone. Validate anytime with \`zalify
|
|
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).
|
|
12921
12972
|
|
|
12922
12973
|
## Files
|
|
12923
12974
|
|
|
12924
12975
|
| File | Role |
|
|
12925
12976
|
| ---- | ---- |
|
|
12926
|
-
| store.json |
|
|
12977
|
+
| store.json | the Shopify store this brand connects to (name, slug, vertical, currency; storeDomain filled in once the store exists) |
|
|
12927
12978
|
| brand.md | brand voice guide — tone, audience, naming, price band. Written FIRST; everything else follows it |
|
|
12928
12979
|
| catalog.json | canonical catalog (products + collections) — the single source the pipeline imports |
|
|
12929
12980
|
| images/manifest.json | one entry per shot + shared style/modelStyle art-direction prefixes |
|
|
@@ -12931,7 +12982,7 @@ alone. Validate anytime with \`zalify store validate .\`.
|
|
|
12931
12982
|
|
|
12932
12983
|
## catalog.json rules
|
|
12933
12984
|
|
|
12934
|
-
- 10–14 products is the sweet spot for a demo
|
|
12985
|
+
- 10–14 products is the sweet spot for a demo brand; every product needs
|
|
12935
12986
|
\`handle\` (unique, kebab-case, no SKU-speak), \`title\`, \`descriptionHtml\`
|
|
12936
12987
|
(brand voice; simple HTML: p/ul/li/em only), \`type\`, \`tags\`, \`variants\`.
|
|
12937
12988
|
- **Collections are smart collections driven by tags**: a product joins
|
|
@@ -12966,13 +13017,13 @@ alone. Validate anytime with \`zalify store validate .\`.
|
|
|
12966
13017
|
## Pipeline (after authoring)
|
|
12967
13018
|
|
|
12968
13019
|
\`\`\`sh
|
|
12969
|
-
zalify
|
|
13020
|
+
zalify brand validate # lint this folder (cwd)
|
|
12970
13021
|
# create the Shopify dev store + Zalify workspace, then:
|
|
12971
13022
|
zalify workspace set <workspace-slug>
|
|
12972
|
-
zalify
|
|
12973
|
-
zalify
|
|
12974
|
-
zalify images generate
|
|
12975
|
-
zalify
|
|
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
|
|
12976
13027
|
\`\`\`
|
|
12977
13028
|
`;
|
|
12978
13029
|
var BRAND_MD = (name) => `# ${name} — brand voice guide
|
|
@@ -13082,7 +13133,7 @@ var MANIFEST_JSON = JSON.stringify({
|
|
|
13082
13133
|
]
|
|
13083
13134
|
}, null, 2) + `
|
|
13084
13135
|
`;
|
|
13085
|
-
function
|
|
13136
|
+
function brandInit(dir2 = ".", opts = {}) {
|
|
13086
13137
|
const target = resolve6(dir2);
|
|
13087
13138
|
const slug = basename4(target).toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/^-+|-+$/g, "");
|
|
13088
13139
|
if (existsSync7(target) && readdirSync3(target).length > 0) {
|
|
@@ -13091,20 +13142,20 @@ function storeInit(dir2, opts) {
|
|
|
13091
13142
|
const name = opts.name ?? slug.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
13092
13143
|
const currency = (opts.currency ?? "USD").toUpperCase();
|
|
13093
13144
|
mkdirSync3(join7(target, "images"), { recursive: true });
|
|
13094
|
-
|
|
13095
|
-
|
|
13096
|
-
|
|
13097
|
-
|
|
13098
|
-
|
|
13099
|
-
console.log(`Scaffolded
|
|
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}:`);
|
|
13100
13151
|
for (const f of ["AUTHORING.md", "brand.md", "store.json", "catalog.json", "images/manifest.json"]) {
|
|
13101
13152
|
console.log(` + ${f}`);
|
|
13102
13153
|
}
|
|
13103
13154
|
console.log(`
|
|
13104
13155
|
Author in this order: brand.md → catalog.json → images/manifest.json` + `
|
|
13105
|
-
(rules + worked examples in AUTHORING.md; lint with \`zalify
|
|
13156
|
+
(rules + worked examples in AUTHORING.md; lint with \`zalify brand validate ${dir2}\`)`);
|
|
13106
13157
|
}
|
|
13107
|
-
function
|
|
13158
|
+
function brandValidate(dir2 = ".") {
|
|
13108
13159
|
const target = resolve6(dir2);
|
|
13109
13160
|
const problems = [];
|
|
13110
13161
|
const warnings = [];
|
|
@@ -13228,7 +13279,7 @@ function storeValidate(dir2) {
|
|
|
13228
13279
|
if (problems.length) {
|
|
13229
13280
|
for (const p of problems)
|
|
13230
13281
|
console.log(` ✗ ${p}`);
|
|
13231
|
-
throw new Error(`${problems.length} problem(s) — fix and re-run \`zalify
|
|
13282
|
+
throw new Error(`${problems.length} problem(s) — fix and re-run \`zalify brand validate\``);
|
|
13232
13283
|
}
|
|
13233
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)` : ""));
|
|
13234
13285
|
}
|
|
@@ -13244,7 +13295,78 @@ try {
|
|
|
13244
13295
|
});
|
|
13245
13296
|
} catch {}
|
|
13246
13297
|
var program2 = new Command;
|
|
13247
|
-
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));
|
|
13248
13370
|
program2.command("version").description("Print Zalify CLI version and check for updates").action(async () => {
|
|
13249
13371
|
console.log(`Zalify CLI version: ${pkg.version}`);
|
|
13250
13372
|
const isNewer = (a, b) => {
|
|
@@ -13261,69 +13383,54 @@ program2.command("version").description("Print Zalify CLI version and check for
|
|
|
13261
13383
|
const update = await notifier.fetchInfo();
|
|
13262
13384
|
if (update && isNewer(update.latest, update.current)) {
|
|
13263
13385
|
console.log(`
|
|
13264
|
-
A new version (${update.latest}) is available. Run "
|
|
13386
|
+
A new version (${update.latest}) is available. Run "zalify self-update" to update.`);
|
|
13265
13387
|
}
|
|
13266
13388
|
} catch {}
|
|
13267
13389
|
});
|
|
13268
|
-
program2.command("
|
|
13269
|
-
|
|
13270
|
-
|
|
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);
|
|
13271
13394
|
});
|
|
13272
|
-
|
|
13273
|
-
|
|
13274
|
-
|
|
13395
|
+
legacyStore.command("validate <dir>").action((dir2) => {
|
|
13396
|
+
moved("brand validate");
|
|
13397
|
+
brandValidate(dir2);
|
|
13275
13398
|
});
|
|
13276
|
-
var
|
|
13277
|
-
|
|
13278
|
-
|
|
13279
|
-
});
|
|
13280
|
-
workspace.command("set <slug-or-id>").description("Switch the active workspace (local, no re-login)").action(async (slugOrId) => {
|
|
13281
|
-
await workspaceSet(slugOrId);
|
|
13282
|
-
});
|
|
13283
|
-
var images = program2.command("images").description("Generate images on Zalify infrastructure");
|
|
13284
|
-
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) => {
|
|
13285
|
-
await imagesGenerate(storeDir);
|
|
13286
|
-
});
|
|
13287
|
-
var shopify = program2.command("shopify").description("Push catalogs and images to the workspace's connected Shopify store");
|
|
13288
|
-
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");
|
|
13289
13402
|
await shopifyImport(storeDir, options);
|
|
13290
13403
|
});
|
|
13291
|
-
|
|
13404
|
+
legacyShopify.command("upload-images <store-dir>").action(async (storeDir) => {
|
|
13405
|
+
moved("brand images attach");
|
|
13292
13406
|
await shopifyUploadImages(storeDir);
|
|
13293
13407
|
});
|
|
13294
|
-
var
|
|
13295
|
-
|
|
13296
|
-
|
|
13297
|
-
|
|
13298
|
-
store.command("validate <dir>").description("Lint the store-data folder against the authoring rules (schema, collection tags, sale pricing, image manifest)").action((dir2) => {
|
|
13299
|
-
storeValidate(dir2);
|
|
13300
|
-
});
|
|
13301
|
-
var storefront = program2.command("storefront").description("Provision and manage fully-hosted standalone storefronts (repo + Vercel via Zalify)");
|
|
13302
|
-
storefront.command("create <dir>").description("One-command launch: Zalify provisions the GitHub repo + Vercel project (env, domain, storefront token), then scaffolds locally and pushes").option("-t, --theme <name>", "theme", "nextjs").option("--editor", "include the Zalify canvas-editor (z1) wiring").option("--to <version>", "template version (default: latest)").option("--no-install", "skip dependency install").option("--no-git", "skip git init/push").action(async (dir2, options) => {
|
|
13303
|
-
await storefrontCreate(dir2, options);
|
|
13304
|
-
});
|
|
13305
|
-
storefront.command("list", { isDefault: true }).description("List the workspace's provisioned storefronts").action(async () => {
|
|
13306
|
-
await storefrontList();
|
|
13307
|
-
});
|
|
13308
|
-
storefront.command("revalidate <slug-or-store-dir>").description("Flush the storefront site's data cache (secret stays server-side)").action(async (slugOrDir) => {
|
|
13309
|
-
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);
|
|
13310
13412
|
});
|
|
13311
|
-
var
|
|
13312
|
-
|
|
13313
|
-
|
|
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);
|
|
13314
13417
|
});
|
|
13315
|
-
|
|
13316
|
-
|
|
13418
|
+
legacyAssets.command("pull <store-dir>").action(async (storeDir) => {
|
|
13419
|
+
moved("brand images pull");
|
|
13420
|
+
await assetsPull(storeDir);
|
|
13317
13421
|
});
|
|
13318
|
-
|
|
13319
|
-
|
|
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);
|
|
13320
13426
|
});
|
|
13321
|
-
|
|
13322
|
-
|
|
13323
|
-
await
|
|
13427
|
+
legacyStorefront.command("list", { isDefault: true }).action(async () => {
|
|
13428
|
+
moved("site list");
|
|
13429
|
+
await siteList();
|
|
13324
13430
|
});
|
|
13325
|
-
|
|
13326
|
-
|
|
13431
|
+
legacyStorefront.command("revalidate <slug-or-store-dir>").action(async (slugOrDir) => {
|
|
13432
|
+
moved("site refresh");
|
|
13433
|
+
await siteRefresh(slugOrDir);
|
|
13327
13434
|
});
|
|
13328
13435
|
program2.parseAsync().catch((err) => {
|
|
13329
13436
|
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|