@zalify/cli 0.11.0 → 0.12.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 +49 -5
- 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
|
});
|
|
@@ -12888,6 +12888,8 @@ async function shopifyUploadImages(storeDir, options = {}) {
|
|
|
12888
12888
|
const manifest = readJson(join6(dir2, "images", "manifest.json"), "images/manifest.json");
|
|
12889
12889
|
console.log(`Uploading images to the Shopify store connected to workspace "${auth.workspaceName}"`);
|
|
12890
12890
|
for (const entry of manifest.images) {
|
|
12891
|
+
if (entry.type === "banner" || !entry.handle)
|
|
12892
|
+
continue;
|
|
12891
12893
|
const filePath = join6(dir2, "images", entry.file);
|
|
12892
12894
|
if (!existsSync6(filePath)) {
|
|
12893
12895
|
console.log(` - ${entry.handle}: ${entry.file} not generated yet, skipped`);
|
|
@@ -13007,12 +13009,26 @@ alone. Validate anytime with \`zalify brand validate\` (defaults to cwd).
|
|
|
13007
13009
|
- \`style\` prefixes packshot/detail prompts; \`modelStyle\` prefixes
|
|
13008
13010
|
\`type: "model"\` prompts. Keep prompts concrete: materials, colors,
|
|
13009
13011
|
camera angle, light.
|
|
13012
|
+
- **Banner / merchandising entries** (\`"type": "banner"\`, no \`handle\`)
|
|
13013
|
+
are theme imagery, not product shots: hero slides, editorial banners.
|
|
13014
|
+
They are generated and pushed to the asset library like any entry, but
|
|
13015
|
+
never attached to a product — theme templates reference them by their
|
|
13016
|
+
\`images/assets.json\` URL. Conventions:
|
|
13017
|
+
- Naming: \`hero-1.png\` + \`hero-1-mobile.png\`, \`hero-2.png\`…,
|
|
13018
|
+
\`editorial-1.png\`.
|
|
13019
|
+
- \`size\`: hero desktop + editorial \`"1536x1024"\`, hero mobile
|
|
13020
|
+
\`"1024x1536"\` (default is \`"1024x1024"\` — never right for banners).
|
|
13021
|
+
- \`bannerStyle\` prefixes banner prompts (falls back to \`style\`, whose
|
|
13022
|
+
"square 1:1" phrasing is wrong for banners — always set it).
|
|
13023
|
+
- NO text, lettering, or logos inside the image; the theme overlays
|
|
13024
|
+
copy. Leave calm negative space on one side for it.
|
|
13010
13025
|
- **Prompt safety (gpt-image-2 rejects [sexual] otherwise)** — for
|
|
13011
13026
|
human-model shots: say "woman"/"man", never "young …"; keep fitted
|
|
13012
13027
|
garments layered or covered (blazer / cardigan / cover-up); crop at
|
|
13013
13028
|
the chin or shoot from behind; prefer wider framing. Swimwear: show
|
|
13014
13029
|
the garment via flat-lay + detail; make the model shot a cover-up
|
|
13015
|
-
scene. Pet/object products: no such constraints.
|
|
13030
|
+
scene. Pet/object products: no such constraints. Applies to banner
|
|
13031
|
+
prompts too.
|
|
13016
13032
|
|
|
13017
13033
|
## Pipeline (after authoring)
|
|
13018
13034
|
|
|
@@ -13110,6 +13126,7 @@ var CATALOG_JSON = (vendor, currency) => JSON.stringify({
|
|
|
13110
13126
|
var MANIFEST_JSON = JSON.stringify({
|
|
13111
13127
|
style: "<shared prefix for packshot/detail prompts: photography style, background, light, 'no text, no watermark. Square 1:1 composition.'>",
|
|
13112
13128
|
modelStyle: "<shared prefix for type:'model' lifestyle prompts — scene, mood, framing. Mind the prompt-safety rules in AUTHORING.md for human models.>",
|
|
13129
|
+
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
13130
|
images: [
|
|
13114
13131
|
{
|
|
13115
13132
|
handle: "example-product",
|
|
@@ -13129,6 +13146,20 @@ var MANIFEST_JSON = JSON.stringify({
|
|
|
13129
13146
|
type: "model",
|
|
13130
13147
|
alt: "The Example Product in use",
|
|
13131
13148
|
prompt: "<lifestyle prompt: who/what uses it, where, light>"
|
|
13149
|
+
},
|
|
13150
|
+
{
|
|
13151
|
+
file: "hero-1.png",
|
|
13152
|
+
type: "banner",
|
|
13153
|
+
size: "1536x1024",
|
|
13154
|
+
alt: "Hero banner: the brand's world in one wide scene",
|
|
13155
|
+
prompt: "<wide hero scene prompt: setting, subject, light — no text, negative space on one side>"
|
|
13156
|
+
},
|
|
13157
|
+
{
|
|
13158
|
+
file: "hero-1-mobile.png",
|
|
13159
|
+
type: "banner",
|
|
13160
|
+
size: "1024x1536",
|
|
13161
|
+
alt: "Hero banner, portrait crop for mobile",
|
|
13162
|
+
prompt: "<same scene, portrait composition — subject centered, negative space above>"
|
|
13132
13163
|
}
|
|
13133
13164
|
]
|
|
13134
13165
|
}, null, 2) + `
|
|
@@ -13155,6 +13186,7 @@ function brandInit(dir2 = ".", opts = {}) {
|
|
|
13155
13186
|
Author in this order: brand.md → catalog.json → images/manifest.json` + `
|
|
13156
13187
|
(rules + worked examples in AUTHORING.md; lint with \`zalify brand validate ${dir2}\`)`);
|
|
13157
13188
|
}
|
|
13189
|
+
var MANIFEST_SIZES = new Set(["1024x1024", "1536x1024", "1024x1536"]);
|
|
13158
13190
|
function brandValidate(dir2 = ".") {
|
|
13159
13191
|
const target = resolve6(dir2);
|
|
13160
13192
|
const problems = [];
|
|
@@ -13266,12 +13298,24 @@ function brandValidate(dir2 = ".") {
|
|
|
13266
13298
|
problems.push(`${where}: duplicate file`);
|
|
13267
13299
|
files.add(entry.file);
|
|
13268
13300
|
}
|
|
13269
|
-
if (entry.
|
|
13301
|
+
if (entry.type === "banner") {
|
|
13302
|
+
if (entry.handle) {
|
|
13303
|
+
warnings.push(`${where}: banner entries take no handle (ignored by attach)`);
|
|
13304
|
+
}
|
|
13305
|
+
} else if (!entry.handle) {
|
|
13306
|
+
problems.push(`${where}: handle required (only type "banner" entries omit it)`);
|
|
13307
|
+
} else if (handles.size && !handles.has(entry.handle)) {
|
|
13270
13308
|
problems.push(`${where}: handle "${entry.handle}" not in catalog.json`);
|
|
13271
13309
|
}
|
|
13310
|
+
if (entry.size && !MANIFEST_SIZES.has(entry.size)) {
|
|
13311
|
+
problems.push(`${where}: size must be one of ${[...MANIFEST_SIZES].join(", ")}`);
|
|
13312
|
+
}
|
|
13272
13313
|
if (entry.type === "model" && (!manifest.modelStyle || manifest.modelStyle.startsWith("<"))) {
|
|
13273
13314
|
warnings.push("images/manifest.json: modelStyle prefix not filled in (model shots present)");
|
|
13274
13315
|
}
|
|
13316
|
+
if (entry.type === "banner" && (!manifest.bannerStyle || manifest.bannerStyle.startsWith("<"))) {
|
|
13317
|
+
warnings.push("images/manifest.json: bannerStyle prefix not filled in (banner entries present)");
|
|
13318
|
+
}
|
|
13275
13319
|
}
|
|
13276
13320
|
}
|
|
13277
13321
|
for (const w of [...new Set(warnings)])
|