@zalify/cli 0.12.1 → 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.
Files changed (2) hide show
  1. package/dist/cli.js +195 -1
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -12836,6 +12836,36 @@ 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
+ }
12839
12869
  const collectionIds = [];
12840
12870
  for (const c of catalog.collections ?? []) {
12841
12871
  const existing = await gql(auth, `query($q: String!) { collections(first: 1, query: $q) { nodes { id handle } } }`, { q: `handle:${c.handle}` });
@@ -12872,6 +12902,40 @@ async function shopifyImport(storeDir, options = {}) {
12872
12902
  console.log(` ✓ collection ${c.handle}`);
12873
12903
  }
12874
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
+ }
12938
+ }
12875
12939
  try {
12876
12940
  const pubs = await gql(auth, `{ publications(first: 10) { nodes { id name } } }`);
12877
12941
  const online = pubs.publications.nodes.find((n) => n.name === "Online Store");
@@ -13006,6 +13070,20 @@ alone. Validate anytime with \`zalify brand validate\` (defaults to cwd).
13006
13070
  - Prices are strings with 2 decimals, in the store currency. Honest
13007
13071
  markdowns only (15–25% off typical).
13008
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.
13009
13087
 
13010
13088
  ## images/manifest.json rules
13011
13089
 
@@ -13127,7 +13205,83 @@ var CATALOG_JSON = (vendor, currency) => JSON.stringify({
13127
13205
  descriptionHtml: "<p>Real markdowns on favorites.</p>",
13128
13206
  rule: { tag: "collection:sale" }
13129
13207
  }
13130
- ]
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
+ }
13131
13285
  }, null, 2) + `
13132
13286
  `;
13133
13287
  var MANIFEST_JSON = JSON.stringify({
@@ -13284,6 +13438,46 @@ function brandValidate(dir2 = ".") {
13284
13438
  }
13285
13439
  }
13286
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
+ }
13287
13481
  }
13288
13482
  const manifest = readJson2("images/manifest.json");
13289
13483
  if (manifest) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalify/cli",
3
- "version": "0.12.1",
3
+ "version": "0.13.0",
4
4
  "description": "Zalify CLI - command-line interface for Zalify",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",