@zalify/cli 0.11.0 → 0.12.1

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 +60 -9
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -11860,7 +11860,7 @@ var POLL_INTERVAL_MS = 5000;
11860
11860
  var POLL_TIMEOUT_MS = 30 * 60 * 1000;
11861
11861
  var STREAM_BATCH = 20;
11862
11862
  function buildPrompt(manifest, entry) {
11863
- const style = entry.type === "model" ? manifest.modelStyle ?? manifest.style : manifest.style;
11863
+ const style = entry.type === "model" ? manifest.modelStyle ?? manifest.style : entry.type === "banner" ? manifest.bannerStyle ?? manifest.style : manifest.style;
11864
11864
  return `${style}
11865
11865
 
11866
11866
  ${entry.prompt}`;
@@ -11903,7 +11903,7 @@ async function submitJob(auth, manifest, entries) {
11903
11903
  workspaceId: auth.workspaceId,
11904
11904
  jobs: entries.map((e) => ({
11905
11905
  prompt: buildPrompt(manifest, e),
11906
- size: "1024x1024",
11906
+ size: e.size ?? "1024x1024",
11907
11907
  count: 1
11908
11908
  }))
11909
11909
  })
@@ -11970,7 +11970,7 @@ async function streamingGenerate(auth, manifest, missing, imagesDir, indexPath,
11970
11970
  jobs: batch.map((e) => ({
11971
11971
  prompt: buildPrompt(manifest, e),
11972
11972
  count: 1,
11973
- size: "1024x1024"
11973
+ size: e.size ?? "1024x1024"
11974
11974
  }))
11975
11975
  })
11976
11976
  });
@@ -12836,9 +12836,12 @@ async function shopifyImport(storeDir, options = {}) {
12836
12836
  console.log(` ✓ pruned ${node.handle}`);
12837
12837
  }
12838
12838
  }
12839
+ const collectionIds = [];
12839
12840
  for (const c of catalog.collections ?? []) {
12840
12841
  const existing = await gql(auth, `query($q: String!) { collections(first: 1, query: $q) { nodes { id handle } } }`, { q: `handle:${c.handle}` });
12841
- if (existing.collections.nodes[0]?.handle === c.handle) {
12842
+ const existingNode = existing.collections.nodes[0];
12843
+ if (existingNode?.handle === c.handle) {
12844
+ collectionIds.push(existingNode.id);
12842
12845
  console.log(` - collection ${c.handle} exists, skipped`);
12843
12846
  continue;
12844
12847
  }
@@ -12862,19 +12865,23 @@ async function shopifyImport(storeDir, options = {}) {
12862
12865
  const errs = data.collectionCreate.userErrors;
12863
12866
  if (errs.length)
12864
12867
  console.error(` ✗ collection ${c.handle}: ${joinErrors(errs)}`);
12865
- else
12868
+ else {
12869
+ if (data.collectionCreate.collection) {
12870
+ collectionIds.push(data.collectionCreate.collection.id);
12871
+ }
12866
12872
  console.log(` ✓ collection ${c.handle}`);
12873
+ }
12867
12874
  }
12868
12875
  try {
12869
12876
  const pubs = await gql(auth, `{ publications(first: 10) { nodes { id name } } }`);
12870
12877
  const online = pubs.publications.nodes.find((n) => n.name === "Online Store");
12871
12878
  if (online) {
12872
- for (const id of productIds) {
12879
+ for (const id of [...productIds, ...collectionIds]) {
12873
12880
  await gql(auth, `mutation($id: ID!, $input: [PublicationInput!]!) {
12874
12881
  publishablePublish(id: $id, input: $input) { userErrors { field message } }
12875
12882
  }`, { id, input: [{ publicationId: online.id }] });
12876
12883
  }
12877
- console.log(` ✓ published ${productIds.length} products to Online Store`);
12884
+ console.log(` ✓ published ${productIds.length} products and ${collectionIds.length} collections to Online Store`);
12878
12885
  }
12879
12886
  } catch (e) {
12880
12887
  console.warn(` ! could not publish to Online Store (write_publications scope missing?): ${e instanceof Error ? e.message : String(e)}`);
@@ -12888,6 +12895,8 @@ async function shopifyUploadImages(storeDir, options = {}) {
12888
12895
  const manifest = readJson(join6(dir2, "images", "manifest.json"), "images/manifest.json");
12889
12896
  console.log(`Uploading images to the Shopify store connected to workspace "${auth.workspaceName}"`);
12890
12897
  for (const entry of manifest.images) {
12898
+ if (entry.type === "banner" || !entry.handle)
12899
+ continue;
12891
12900
  const filePath = join6(dir2, "images", entry.file);
12892
12901
  if (!existsSync6(filePath)) {
12893
12902
  console.log(` - ${entry.handle}: ${entry.file} not generated yet, skipped`);
@@ -13007,12 +13016,26 @@ alone. Validate anytime with \`zalify brand validate\` (defaults to cwd).
13007
13016
  - \`style\` prefixes packshot/detail prompts; \`modelStyle\` prefixes
13008
13017
  \`type: "model"\` prompts. Keep prompts concrete: materials, colors,
13009
13018
  camera angle, light.
13019
+ - **Banner / merchandising entries** (\`"type": "banner"\`, no \`handle\`)
13020
+ are theme imagery, not product shots: hero slides, editorial banners.
13021
+ They are generated and pushed to the asset library like any entry, but
13022
+ never attached to a product — theme templates reference them by their
13023
+ \`images/assets.json\` URL. Conventions:
13024
+ - Naming: \`hero-1.png\` + \`hero-1-mobile.png\`, \`hero-2.png\`…,
13025
+ \`editorial-1.png\`.
13026
+ - \`size\`: hero desktop + editorial \`"1536x1024"\`, hero mobile
13027
+ \`"1024x1536"\` (default is \`"1024x1024"\` — never right for banners).
13028
+ - \`bannerStyle\` prefixes banner prompts (falls back to \`style\`, whose
13029
+ "square 1:1" phrasing is wrong for banners — always set it).
13030
+ - NO text, lettering, or logos inside the image; the theme overlays
13031
+ copy. Leave calm negative space on one side for it.
13010
13032
  - **Prompt safety (gpt-image-2 rejects [sexual] otherwise)** — for
13011
13033
  human-model shots: say "woman"/"man", never "young …"; keep fitted
13012
13034
  garments layered or covered (blazer / cardigan / cover-up); crop at
13013
13035
  the chin or shoot from behind; prefer wider framing. Swimwear: show
13014
13036
  the garment via flat-lay + detail; make the model shot a cover-up
13015
- scene. Pet/object products: no such constraints.
13037
+ scene. Pet/object products: no such constraints. Applies to banner
13038
+ prompts too.
13016
13039
 
13017
13040
  ## Pipeline (after authoring)
13018
13041
 
@@ -13110,6 +13133,7 @@ var CATALOG_JSON = (vendor, currency) => JSON.stringify({
13110
13133
  var MANIFEST_JSON = JSON.stringify({
13111
13134
  style: "<shared prefix for packshot/detail prompts: photography style, background, light, 'no text, no watermark. Square 1:1 composition.'>",
13112
13135
  modelStyle: "<shared prefix for type:'model' lifestyle prompts — scene, mood, framing. Mind the prompt-safety rules in AUTHORING.md for human models.>",
13136
+ bannerStyle: "<shared prefix for type:'banner' theme imagery — wide cinematic scene, no text or logos, calm negative space on one side for overlaid copy.>",
13113
13137
  images: [
13114
13138
  {
13115
13139
  handle: "example-product",
@@ -13129,6 +13153,20 @@ var MANIFEST_JSON = JSON.stringify({
13129
13153
  type: "model",
13130
13154
  alt: "The Example Product in use",
13131
13155
  prompt: "<lifestyle prompt: who/what uses it, where, light>"
13156
+ },
13157
+ {
13158
+ file: "hero-1.png",
13159
+ type: "banner",
13160
+ size: "1536x1024",
13161
+ alt: "Hero banner: the brand's world in one wide scene",
13162
+ prompt: "<wide hero scene prompt: setting, subject, light — no text, negative space on one side>"
13163
+ },
13164
+ {
13165
+ file: "hero-1-mobile.png",
13166
+ type: "banner",
13167
+ size: "1024x1536",
13168
+ alt: "Hero banner, portrait crop for mobile",
13169
+ prompt: "<same scene, portrait composition — subject centered, negative space above>"
13132
13170
  }
13133
13171
  ]
13134
13172
  }, null, 2) + `
@@ -13155,6 +13193,7 @@ function brandInit(dir2 = ".", opts = {}) {
13155
13193
  Author in this order: brand.md → catalog.json → images/manifest.json` + `
13156
13194
  (rules + worked examples in AUTHORING.md; lint with \`zalify brand validate ${dir2}\`)`);
13157
13195
  }
13196
+ var MANIFEST_SIZES = new Set(["1024x1024", "1536x1024", "1024x1536"]);
13158
13197
  function brandValidate(dir2 = ".") {
13159
13198
  const target = resolve6(dir2);
13160
13199
  const problems = [];
@@ -13266,12 +13305,24 @@ function brandValidate(dir2 = ".") {
13266
13305
  problems.push(`${where}: duplicate file`);
13267
13306
  files.add(entry.file);
13268
13307
  }
13269
- if (entry.handle && handles.size && !handles.has(entry.handle)) {
13308
+ if (entry.type === "banner") {
13309
+ if (entry.handle) {
13310
+ warnings.push(`${where}: banner entries take no handle (ignored by attach)`);
13311
+ }
13312
+ } else if (!entry.handle) {
13313
+ problems.push(`${where}: handle required (only type "banner" entries omit it)`);
13314
+ } else if (handles.size && !handles.has(entry.handle)) {
13270
13315
  problems.push(`${where}: handle "${entry.handle}" not in catalog.json`);
13271
13316
  }
13317
+ if (entry.size && !MANIFEST_SIZES.has(entry.size)) {
13318
+ problems.push(`${where}: size must be one of ${[...MANIFEST_SIZES].join(", ")}`);
13319
+ }
13272
13320
  if (entry.type === "model" && (!manifest.modelStyle || manifest.modelStyle.startsWith("<"))) {
13273
13321
  warnings.push("images/manifest.json: modelStyle prefix not filled in (model shots present)");
13274
13322
  }
13323
+ if (entry.type === "banner" && (!manifest.bannerStyle || manifest.bannerStyle.startsWith("<"))) {
13324
+ warnings.push("images/manifest.json: bannerStyle prefix not filled in (banner entries present)");
13325
+ }
13275
13326
  }
13276
13327
  }
13277
13328
  for (const w of [...new Set(warnings)])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalify/cli",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "description": "Zalify CLI - command-line interface for Zalify",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",