@zalify/cli 0.12.0 → 0.13.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 +206 -5
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -12836,9 +12836,42 @@ async function shopifyImport(storeDir, options = {}) {
|
|
|
12836
12836
|
console.log(` ✓ pruned ${node.handle}`);
|
|
12837
12837
|
}
|
|
12838
12838
|
}
|
|
12839
|
+
for (const pg of catalog.pages ?? []) {
|
|
12840
|
+
const existing = await gql(auth, `query($q: String!) { pages(first: 1, query: $q) { nodes { id handle } } }`, { q: `handle:${pg.handle}` });
|
|
12841
|
+
const node = existing.pages.nodes[0];
|
|
12842
|
+
if (node?.handle === pg.handle) {
|
|
12843
|
+
const res = await gql(auth, `mutation($id: ID!, $page: PageUpdateInput!) {
|
|
12844
|
+
pageUpdate(id: $id, page: $page) { userErrors { field message } }
|
|
12845
|
+
}`, { id: node.id, page: { title: pg.title, body: pg.bodyHtml } });
|
|
12846
|
+
const errs = res.pageUpdate.userErrors;
|
|
12847
|
+
if (errs.length)
|
|
12848
|
+
console.error(` ✗ page ${pg.handle}: ${joinErrors(errs)}`);
|
|
12849
|
+
else
|
|
12850
|
+
console.log(` ✓ page ${pg.handle} updated`);
|
|
12851
|
+
} else {
|
|
12852
|
+
const res = await gql(auth, `mutation($page: PageCreateInput!) {
|
|
12853
|
+
pageCreate(page: $page) { userErrors { field message } }
|
|
12854
|
+
}`, {
|
|
12855
|
+
page: {
|
|
12856
|
+
title: pg.title,
|
|
12857
|
+
handle: pg.handle,
|
|
12858
|
+
body: pg.bodyHtml,
|
|
12859
|
+
isPublished: true
|
|
12860
|
+
}
|
|
12861
|
+
});
|
|
12862
|
+
const errs = res.pageCreate.userErrors;
|
|
12863
|
+
if (errs.length)
|
|
12864
|
+
console.error(` ✗ page ${pg.handle}: ${joinErrors(errs)}`);
|
|
12865
|
+
else
|
|
12866
|
+
console.log(` ✓ page ${pg.handle} created`);
|
|
12867
|
+
}
|
|
12868
|
+
}
|
|
12869
|
+
const collectionIds = [];
|
|
12839
12870
|
for (const c of catalog.collections ?? []) {
|
|
12840
12871
|
const existing = await gql(auth, `query($q: String!) { collections(first: 1, query: $q) { nodes { id handle } } }`, { q: `handle:${c.handle}` });
|
|
12841
|
-
|
|
12872
|
+
const existingNode = existing.collections.nodes[0];
|
|
12873
|
+
if (existingNode?.handle === c.handle) {
|
|
12874
|
+
collectionIds.push(existingNode.id);
|
|
12842
12875
|
console.log(` - collection ${c.handle} exists, skipped`);
|
|
12843
12876
|
continue;
|
|
12844
12877
|
}
|
|
@@ -12862,19 +12895,57 @@ async function shopifyImport(storeDir, options = {}) {
|
|
|
12862
12895
|
const errs = data.collectionCreate.userErrors;
|
|
12863
12896
|
if (errs.length)
|
|
12864
12897
|
console.error(` ✗ collection ${c.handle}: ${joinErrors(errs)}`);
|
|
12865
|
-
else
|
|
12898
|
+
else {
|
|
12899
|
+
if (data.collectionCreate.collection) {
|
|
12900
|
+
collectionIds.push(data.collectionCreate.collection.id);
|
|
12901
|
+
}
|
|
12866
12902
|
console.log(` ✓ collection ${c.handle}`);
|
|
12903
|
+
}
|
|
12904
|
+
}
|
|
12905
|
+
const menuEntries = Object.entries(catalog.menus ?? {});
|
|
12906
|
+
if (menuEntries.length) {
|
|
12907
|
+
const toMenuItems = (items) => items.map((i) => ({
|
|
12908
|
+
title: i.title,
|
|
12909
|
+
type: "HTTP",
|
|
12910
|
+
url: i.url,
|
|
12911
|
+
items: i.items ? toMenuItems(i.items) : []
|
|
12912
|
+
}));
|
|
12913
|
+
const titleize = (handle) => handle.replaceAll("-", " ").replace(/^./, (c) => c.toUpperCase());
|
|
12914
|
+
const list = await gql(auth, `{ menus(first: 50) { nodes { id handle title } } }`);
|
|
12915
|
+
for (const [handle, items] of menuEntries) {
|
|
12916
|
+
const existing = list.menus.nodes.find((n) => n.handle === handle);
|
|
12917
|
+
const input = toMenuItems(items);
|
|
12918
|
+
if (existing) {
|
|
12919
|
+
const res = await gql(auth, `mutation($id: ID!, $title: String!, $items: [MenuItemUpdateInput!]!) {
|
|
12920
|
+
menuUpdate(id: $id, title: $title, items: $items) { userErrors { field message } }
|
|
12921
|
+
}`, { id: existing.id, title: existing.title, items: input });
|
|
12922
|
+
const errs = res.menuUpdate.userErrors;
|
|
12923
|
+
if (errs.length)
|
|
12924
|
+
console.error(` ✗ menu ${handle}: ${joinErrors(errs)}`);
|
|
12925
|
+
else
|
|
12926
|
+
console.log(` ✓ menu ${handle} updated (${items.length} top-level items)`);
|
|
12927
|
+
} else {
|
|
12928
|
+
const res = await gql(auth, `mutation($title: String!, $handle: String!, $items: [MenuItemCreateInput!]!) {
|
|
12929
|
+
menuCreate(title: $title, handle: $handle, items: $items) { userErrors { field message } }
|
|
12930
|
+
}`, { title: titleize(handle), handle, items: input });
|
|
12931
|
+
const errs = res.menuCreate.userErrors;
|
|
12932
|
+
if (errs.length)
|
|
12933
|
+
console.error(` ✗ menu ${handle}: ${joinErrors(errs)}`);
|
|
12934
|
+
else
|
|
12935
|
+
console.log(` ✓ menu ${handle} created (${items.length} top-level items)`);
|
|
12936
|
+
}
|
|
12937
|
+
}
|
|
12867
12938
|
}
|
|
12868
12939
|
try {
|
|
12869
12940
|
const pubs = await gql(auth, `{ publications(first: 10) { nodes { id name } } }`);
|
|
12870
12941
|
const online = pubs.publications.nodes.find((n) => n.name === "Online Store");
|
|
12871
12942
|
if (online) {
|
|
12872
|
-
for (const id of productIds) {
|
|
12943
|
+
for (const id of [...productIds, ...collectionIds]) {
|
|
12873
12944
|
await gql(auth, `mutation($id: ID!, $input: [PublicationInput!]!) {
|
|
12874
12945
|
publishablePublish(id: $id, input: $input) { userErrors { field message } }
|
|
12875
12946
|
}`, { id, input: [{ publicationId: online.id }] });
|
|
12876
12947
|
}
|
|
12877
|
-
console.log(` ✓ published ${productIds.length} products to Online Store`);
|
|
12948
|
+
console.log(` ✓ published ${productIds.length} products and ${collectionIds.length} collections to Online Store`);
|
|
12878
12949
|
}
|
|
12879
12950
|
} catch (e) {
|
|
12880
12951
|
console.warn(` ! could not publish to Online Store (write_publications scope missing?): ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -12999,6 +13070,20 @@ alone. Validate anytime with \`zalify brand validate\` (defaults to cwd).
|
|
|
12999
13070
|
- Prices are strings with 2 decimals, in the store currency. Honest
|
|
13000
13071
|
markdowns only (15–25% off typical).
|
|
13001
13072
|
- Colors and variants get evocative names per brand.md — never "Gray 02".
|
|
13073
|
+
- **Pages** (\`pages\`): the standard store pages, upserted to Shopify by
|
|
13074
|
+
\`brand push\` and linked from the footer menu. Keep the scaffold's six
|
|
13075
|
+
handles (about, shipping-delivery, returns-exchanges, faq,
|
|
13076
|
+
privacy-policy, terms-of-service); write \`bodyHtml\` in the brand
|
|
13077
|
+
voice (simple HTML: h2/p/ul/li). The \`contact\` page already exists in
|
|
13078
|
+
every Shopify store — link it, don't create it.
|
|
13079
|
+
- **Menus** (\`menus\`): \`main-menu\` (header nav) and \`footer\`, upserted
|
|
13080
|
+
by \`brand push\`; the catalog is the source of truth (a push replaces
|
|
13081
|
+
the menu's items). Items are \`{ title, url, items? }\`, max 3 levels.
|
|
13082
|
+
A top-level main-menu item with children opens the theme's mega
|
|
13083
|
+
panel; footer top-level items with children become footer columns
|
|
13084
|
+
(Shop / Help / Company is the standard trio). URLs are relative
|
|
13085
|
+
(\`/collections/<handle>\`, \`/pages/<handle>\`) and must point at
|
|
13086
|
+
handles that exist in this file.
|
|
13002
13087
|
|
|
13003
13088
|
## images/manifest.json rules
|
|
13004
13089
|
|
|
@@ -13120,7 +13205,83 @@ var CATALOG_JSON = (vendor, currency) => JSON.stringify({
|
|
|
13120
13205
|
descriptionHtml: "<p>Real markdowns on favorites.</p>",
|
|
13121
13206
|
rule: { tag: "collection:sale" }
|
|
13122
13207
|
}
|
|
13123
|
-
]
|
|
13208
|
+
],
|
|
13209
|
+
pages: [
|
|
13210
|
+
{
|
|
13211
|
+
handle: "about",
|
|
13212
|
+
title: "About",
|
|
13213
|
+
bodyHtml: "<p><Two-three paragraphs in the brand voice: who we are, what we make, why it matters.></p>"
|
|
13214
|
+
},
|
|
13215
|
+
{
|
|
13216
|
+
handle: "shipping-delivery",
|
|
13217
|
+
title: "Shipping & Delivery",
|
|
13218
|
+
bodyHtml: "<p><Shipping promise in the brand voice: threshold for free shipping, carriers, timing, tracking.></p>"
|
|
13219
|
+
},
|
|
13220
|
+
{
|
|
13221
|
+
handle: "returns-exchanges",
|
|
13222
|
+
title: "Returns & Exchanges",
|
|
13223
|
+
bodyHtml: "<p><Return window, condition rules, how to start a return, refund timing — friendly and concrete.></p>"
|
|
13224
|
+
},
|
|
13225
|
+
{
|
|
13226
|
+
handle: "faq",
|
|
13227
|
+
title: "FAQ",
|
|
13228
|
+
bodyHtml: "<h2>Question one?</h2><p>Answer.</p><h2>Question two?</h2><p>Answer. 5-8 real questions a shopper would ask.</p>"
|
|
13229
|
+
},
|
|
13230
|
+
{
|
|
13231
|
+
handle: "privacy-policy",
|
|
13232
|
+
title: "Privacy Policy",
|
|
13233
|
+
bodyHtml: "<p><Plain-language privacy policy: what's collected, why, cookies, contact email.></p>"
|
|
13234
|
+
},
|
|
13235
|
+
{
|
|
13236
|
+
handle: "terms-of-service",
|
|
13237
|
+
title: "Terms of Service",
|
|
13238
|
+
bodyHtml: "<p><Plain-language terms: orders, pricing, liability, governing law.></p>"
|
|
13239
|
+
}
|
|
13240
|
+
],
|
|
13241
|
+
menus: {
|
|
13242
|
+
"main-menu": [
|
|
13243
|
+
{ title: "New in", url: "/collections/all" },
|
|
13244
|
+
{
|
|
13245
|
+
title: "Shop",
|
|
13246
|
+
url: "/collections/all",
|
|
13247
|
+
items: [
|
|
13248
|
+
{ title: "Best sellers", url: "/collections/best-sellers" },
|
|
13249
|
+
{ title: "Sale", url: "/collections/sale" }
|
|
13250
|
+
]
|
|
13251
|
+
},
|
|
13252
|
+
{ title: "Sale", url: "/collections/sale" }
|
|
13253
|
+
],
|
|
13254
|
+
footer: [
|
|
13255
|
+
{
|
|
13256
|
+
title: "Shop",
|
|
13257
|
+
url: "/collections/all",
|
|
13258
|
+
items: [
|
|
13259
|
+
{ title: "Shop all", url: "/collections/all" },
|
|
13260
|
+
{ title: "Best sellers", url: "/collections/best-sellers" },
|
|
13261
|
+
{ title: "Sale", url: "/collections/sale" }
|
|
13262
|
+
]
|
|
13263
|
+
},
|
|
13264
|
+
{
|
|
13265
|
+
title: "Help",
|
|
13266
|
+
url: "/pages/shipping-delivery",
|
|
13267
|
+
items: [
|
|
13268
|
+
{ title: "Shipping & Delivery", url: "/pages/shipping-delivery" },
|
|
13269
|
+
{ title: "Returns & Exchanges", url: "/pages/returns-exchanges" },
|
|
13270
|
+
{ title: "FAQ", url: "/pages/faq" },
|
|
13271
|
+
{ title: "Contact", url: "/pages/contact" }
|
|
13272
|
+
]
|
|
13273
|
+
},
|
|
13274
|
+
{
|
|
13275
|
+
title: "Company",
|
|
13276
|
+
url: "/pages/about",
|
|
13277
|
+
items: [
|
|
13278
|
+
{ title: "About", url: "/pages/about" },
|
|
13279
|
+
{ title: "Privacy Policy", url: "/pages/privacy-policy" },
|
|
13280
|
+
{ title: "Terms of Service", url: "/pages/terms-of-service" }
|
|
13281
|
+
]
|
|
13282
|
+
}
|
|
13283
|
+
]
|
|
13284
|
+
}
|
|
13124
13285
|
}, null, 2) + `
|
|
13125
13286
|
`;
|
|
13126
13287
|
var MANIFEST_JSON = JSON.stringify({
|
|
@@ -13277,6 +13438,46 @@ function brandValidate(dir2 = ".") {
|
|
|
13277
13438
|
}
|
|
13278
13439
|
}
|
|
13279
13440
|
}
|
|
13441
|
+
const pageHandles = new Set;
|
|
13442
|
+
for (const pg of catalog.pages ?? []) {
|
|
13443
|
+
const where = `page "${pg.handle ?? pg.title ?? "?"}"`;
|
|
13444
|
+
if (!pg.handle || !pg.title || !pg.bodyHtml) {
|
|
13445
|
+
problems.push(`${where}: handle, title, and bodyHtml are required`);
|
|
13446
|
+
}
|
|
13447
|
+
if (pg.handle) {
|
|
13448
|
+
if (pageHandles.has(pg.handle))
|
|
13449
|
+
problems.push(`${where}: duplicate handle`);
|
|
13450
|
+
pageHandles.add(pg.handle);
|
|
13451
|
+
}
|
|
13452
|
+
if (pg.bodyHtml?.includes("<p><")) {
|
|
13453
|
+
warnings.push(`${where}: bodyHtml still contains a scaffold placeholder`);
|
|
13454
|
+
}
|
|
13455
|
+
}
|
|
13456
|
+
const collectionHandles = new Set((catalog.collections ?? []).map((c) => c.handle).filter(Boolean));
|
|
13457
|
+
const checkMenu = (nodes, menu, depth) => {
|
|
13458
|
+
if (depth > 3) {
|
|
13459
|
+
problems.push(`menu "${menu}": nested deeper than 3 levels`);
|
|
13460
|
+
return;
|
|
13461
|
+
}
|
|
13462
|
+
for (const node of nodes) {
|
|
13463
|
+
const where = `menu "${menu}" item "${node.title ?? "?"}"`;
|
|
13464
|
+
if (!node.title || !node.url)
|
|
13465
|
+
problems.push(`${where}: title and url required`);
|
|
13466
|
+
const col = node.url?.match(/^\/collections\/([a-z0-9-]+)$/)?.[1];
|
|
13467
|
+
if (col && col !== "all" && !collectionHandles.has(col)) {
|
|
13468
|
+
problems.push(`${where}: collection "${col}" not in catalog.json`);
|
|
13469
|
+
}
|
|
13470
|
+
const pageRef = node.url?.match(/^\/pages\/([a-z0-9-]+)$/)?.[1];
|
|
13471
|
+
if (pageRef && pageRef !== "contact" && !pageHandles.has(pageRef)) {
|
|
13472
|
+
problems.push(`${where}: page "${pageRef}" not in catalog.json pages`);
|
|
13473
|
+
}
|
|
13474
|
+
if (node.items?.length)
|
|
13475
|
+
checkMenu(node.items, menu, depth + 1);
|
|
13476
|
+
}
|
|
13477
|
+
};
|
|
13478
|
+
for (const [menuHandle, nodes] of Object.entries(catalog.menus ?? {})) {
|
|
13479
|
+
checkMenu(nodes ?? [], menuHandle, 1);
|
|
13480
|
+
}
|
|
13280
13481
|
}
|
|
13281
13482
|
const manifest = readJson2("images/manifest.json");
|
|
13282
13483
|
if (manifest) {
|