@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.
- package/dist/cli.js +60 -9
- 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
|
-
|
|
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.
|
|
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)])
|