@wix/agent-skills 1.30.0 → 1.31.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/package.json +1 -1
- package/skills/wix-app/SKILL.md +7 -8
- package/skills/wix-app/references/DASHBOARD_PAGE.md +1 -1
- package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +11 -50
- package/skills/wix-app/references/dashboard-page/DATA_SOURCES.md +14 -10
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE.md +5 -8
- package/skills/wix-app/references/dashboard-page/TABLE_STATE.md +5 -4
- package/skills/wix-headless-kit/SKILL.md +20 -12
- package/skills/wix-headless-kit/guides/api-run.md +178 -0
- package/skills/wix-headless-kit/guides/existing-site.md +3 -2
- package/skills/wix-headless-kit/guides/reference-mode.md +9 -1
- package/skills/wix-headless-kit/install/AGENTS.md +7 -5
- package/skills/wix-headless-kit/install/attach.mjs +3 -3
- package/skills/wix-headless-kit/install/context.mjs +13 -3
- package/skills/wix-headless-kit/install/deploy.mjs +3 -3
- package/skills/wix-headless-kit/install/pins.json +1 -1
- package/skills/wix-headless-kit/install/setup.mjs +2 -2
- package/skills/wix-headless-kit/install/templates.mjs +2 -1
- package/skills/wix-headless-templates/blog/seed/SEED.md +1 -1
- package/skills/wix-headless-templates/blog/seed/seed-blog.mjs +2 -2
- package/skills/wix-headless-templates/bookings/seed/SEED.md +1 -1
- package/skills/wix-headless-templates/bookings/seed/seed-bookings.mjs +2 -1
- package/skills/wix-headless-templates/donations/seed/SEED.md +1 -1
- package/skills/wix-headless-templates/donations/seed/seed-donations.mjs +3 -2
- package/skills/wix-headless-templates/events/seed/SEED.md +1 -1
- package/skills/wix-headless-templates/events/seed/seed-events.mjs +3 -2
- package/skills/wix-headless-templates/portfolio/seed/SEED.md +4 -2
- package/skills/wix-headless-templates/portfolio/seed/seed-portfolio.mjs +6 -6
- package/skills/wix-headless-templates/rentals/seed/SEED.md +1 -1
- package/skills/wix-headless-templates/rentals/seed/seed-rentals.mjs +2 -1
- package/skills/wix-headless-templates/restaurants/seed/SEED.md +1 -1
- package/skills/wix-headless-templates/restaurants/seed/seed-restaurants.mjs +4 -3
- package/skills/wix-headless-templates/shared/seed/images.mjs +63 -7
- package/skills/wix-headless-templates/storefront/seed/SEED.md +17 -5
- package/skills/wix-headless-templates/storefront/seed/seed-store.mjs +49 -16
- package/skills/wix-manage/SKILL.md +25 -15
- package/skills/wix-manage/references/automations/activation-status.md +52 -0
- package/skills/wix-manage/references/automations/api-catalog.md +81 -0
- package/skills/wix-manage/references/automations/build-and-manage-automations.md +34 -0
- package/skills/wix-manage/references/bookings/create-and-update-booking-services.md +277 -363
- package/skills/wix-manage/references/custom-apps/use-app-skills-and-app-tools.md +3 -1
- package/skills/wix-manage/references/sites/send-feedback-to-wix.md +92 -0
- package/skills/wix-manage/references/sites/upload-static-site.md +258 -279
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +7 -1
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +7 -1
- package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +7 -1
- package/skills/wix-headless-kit/guides/no-machine.md +0 -115
- package/skills/wix-manage/references/bookings/create-appointment-service.md +0 -243
- package/skills/wix-manage/references/bookings/create-booking-service-from-prompt.md +0 -31
- package/skills/wix-manage/references/bookings/create-class-service.md +0 -222
- package/skills/wix-manage/references/bookings/create-course-service.md +0 -251
|
@@ -20,12 +20,14 @@ call is memory, and an empty or error reply to it says nothing about the site.
|
|
|
20
20
|
Installed at `.agents/skills/` (by the Wix plugin, or by
|
|
21
21
|
`npx skills add wix/skills --skill {{SKILL}} --skill wix-docs --skill wix-manage`).
|
|
22
22
|
|
|
23
|
-
- `{{SKILL}}` — the code in this app and how to extend it. The shipped code
|
|
23
|
+
- `{{SKILL}}` — the code in this app and how to extend it. The shipped code is the
|
|
24
|
+
`wix-headless-templates` skill: `node .agents/skills/{{SKILL}}/install/templates.mjs` prints where it
|
|
25
|
+
is (`.agents/skills/wix-headless-templates/` when installed beside this skill, else
|
|
24
26
|
`.agents/skills/{{SKILL}}/templates/`, fetched once from the skill's repository and kept with this
|
|
25
|
-
project at the commit it was built from
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
adds one; `node
|
|
27
|
+
project at the commit it was built from, `templates/.source`) and fetches it when neither exists;
|
|
28
|
+
`<templates>` below is that folder. Each business solution has a playbook at
|
|
29
|
+
`<templates>/<solution>/INSTRUCTIONS.md`; `node .agents/skills/{{SKILL}}/install/deploy.mjs <solution> --stack {{STACK}}`
|
|
30
|
+
adds one; `node <templates>/<solution>/seed/read-site.mjs` reads what the
|
|
29
31
|
site holds for it, with the documentation URL of every call it made. A code change ends with a release; the live URL shows it, the dev server does not count.
|
|
30
32
|
`node .agents/skills/{{SKILL}}/install/check.mjs` says whether the skill or its templates have a newer
|
|
31
33
|
version and prints the update commands; an update touches the skill folder or `templates/`, never `src/`.
|
|
@@ -304,7 +304,7 @@ emit("agent_configs", writeAgentsMd(projectDir, { skill: basename(SKILL_ROOT), s
|
|
|
304
304
|
if (mode !== "link" && (stack !== "astro" || mode === "config-only")) {
|
|
305
305
|
emit("ready", { projectDir, siteId, appId, baseUrl, hosting, frontend, stack, dashboardUrl: `https://manage.wix.com/dashboard/${siteId}`,
|
|
306
306
|
next: `scaffold the ${stack} project in this folder per SKILL.md, then deploy.mjs <vertical…> --stack ${stack}${planPath ? ` --plan ${planPath}` : ""} (the client id is read from wix.config.json); ` +
|
|
307
|
-
(planPath ? `then seed with the plan: node
|
|
307
|
+
(planPath ? `then seed with the plan: node ${join(TEMPLATES, verticals[0], "seed", `seed-${verticals[0]}.mjs`)} ${planPath}` : `no plan given, so nothing seeds — the site owns its content`) +
|
|
308
308
|
(hosting === "self" ? `; you host it: origins on the OAuth app allow-list now: ${origins.join(", ") || "none — add them before the first checkout test"}` : "") });
|
|
309
309
|
process.exit(0);
|
|
310
310
|
}
|
|
@@ -349,10 +349,10 @@ if (planPath) {
|
|
|
349
349
|
emit("seeding_started", { vertical: verticals[0], ...seed, ...(verticals.length > 1 ? { note: `the plan seeds ${verticals[0]}; the other verticals' seeds run afterwards, each with its own plan` } : {}) });
|
|
350
350
|
} else {
|
|
351
351
|
if (siteOrigin === "init") {
|
|
352
|
-
emit("note", { step: "seed", detail: `this site was made for this run (init) and is empty: draft a plan per
|
|
352
|
+
emit("note", { step: "seed", detail: `this site was made for this run (init) and is empty: draft a plan per ${join(TEMPLATES, verticals[0], "seed", "SEED.md")} — the brief's content, else demo content — and run node <SKILL_ROOT>/templates/${verticals[0]}/seed/seed-<vertical>.mjs plan.json from the project root` });
|
|
353
353
|
}
|
|
354
354
|
if (verticals.includes("members")) {
|
|
355
|
-
emit("note", { step: "seed", detail:
|
|
355
|
+
emit("note", { step: "seed", detail: `members: the Members Area app (the profile layer, no content) is installed by ${join(TEMPLATES, "members", "seed", "seed-members.mjs")} — run it unless the site already has the app` });
|
|
356
356
|
}
|
|
357
357
|
}
|
|
358
358
|
|
|
@@ -78,7 +78,9 @@ export function pullEnv(cwd) {
|
|
|
78
78
|
/**
|
|
79
79
|
* Whether a frontend exists in this folder: a `package.json` (a bundled project), an `index.html` at
|
|
80
80
|
* the root (plain pages), or an `index.html` inside the folder the config's `site.outputDirectory`
|
|
81
|
-
* names (a static site laid out for release, pages under site/).
|
|
81
|
+
* names (a static site laid out for release, pages under site/). `publicIndex` and `astroPages` tell
|
|
82
|
+
* a dropped site on its Wix Dev Machine apart from an Astro project: the machine copies the drop's
|
|
83
|
+
* pages into public/ of an Astro starter that has no src/pages/. Nothing else counts — not this
|
|
82
84
|
* skill's code, not the skills folder, not AGENTS.md: those say who ran here, not what is here.
|
|
83
85
|
*/
|
|
84
86
|
export function frontendPresent(cwd = process.cwd()) {
|
|
@@ -89,13 +91,16 @@ export function frontendPresent(cwd = process.cwd()) {
|
|
|
89
91
|
rootIndex: has("index.html"),
|
|
90
92
|
outputIndex: !!outDir && outDir !== "." && has(join(outDir, "index.html")),
|
|
91
93
|
outputDirectory: outDir || null,
|
|
94
|
+
publicIndex: has(join("public", "index.html")),
|
|
95
|
+
astroPages: has(join("src", "pages")),
|
|
92
96
|
};
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
/**
|
|
96
|
-
* What the folder IS, from
|
|
100
|
+
* What the folder IS, from a few file facts — the one classification every script and SKILL.md
|
|
97
101
|
* step 3 share: wix.config.json, its site.outputDirectory, the migration variables in .env.local,
|
|
98
|
-
* package.json, index.html
|
|
102
|
+
* package.json, index.html (at the root or in public/), src/pages/. `migrationActive` comes from
|
|
103
|
+
* siteContext (it needs `.env.local`).
|
|
99
104
|
*
|
|
100
105
|
* empty nothing that reads as a project → setup CREATE: the run makes the site, seeds the plan
|
|
101
106
|
* project a frontend, no wix.config.json → setup ADOPT: `init` gives it a new, empty site, seeds the plan
|
|
@@ -103,6 +108,9 @@ export function frontendPresent(cwd = process.cwd()) {
|
|
|
103
108
|
* migration a config whose .env.local declares an ACTIVE editor migration, with or without the blank
|
|
104
109
|
* Astro starter the download carries → setup MIGRATE; nothing seeded. Decided before the
|
|
105
110
|
* frontend test: the starter's package.json must not read as a project to iterate on
|
|
111
|
+
* static-in-astro a config, a package.json, public/index.html and no src/pages/ (a site published
|
|
112
|
+
* through the drop flow, on its Wix Dev Machine) → the pages stay plain files in
|
|
113
|
+
* public/; the static REST layer deploys beside them; build and release
|
|
106
114
|
* wix-project a config AND a frontend (package.json, or index.html in the output folder) → iterate:
|
|
107
115
|
* never scaffold, init or reseed; deploy.mjs adds a solution, edits, release
|
|
108
116
|
* published-static a config, index.html at the ROOT, no package.json, no laid-out output folder (a site
|
|
@@ -117,12 +125,14 @@ export function folderShape(cwd = process.cwd(), { migrationActive = false } = {
|
|
|
117
125
|
project: "setup.mjs --vertical <v> --stack <stack> [--plan]: init links the folder to a new, empty site, seeds the plan and deploys (ADOPT)",
|
|
118
126
|
"config-only": "attach.mjs: the site exists and has no frontend yet; read what it holds (the vertical's read-site.mjs) — nothing is seeded; the vertical's seed module with a plan only when the brief supplies or describes content",
|
|
119
127
|
migration: "setup.mjs (MIGRATE): the shipped code into the starter the download carries (or the composed template around a bare config); the site being migrated owns its content — guides/migration.md",
|
|
128
|
+
"static-in-astro": "a dropped site on its Dev Machine: the pages are plain files in public/, served as they are. deploy.mjs <vertical…> --stack static --out public puts the REST layer in public/js/wix/; wire the pages in place with <script type=\"module\">: do the least that makes them work: connect each control the pages show and add only what a control can't work without (more only when the brief asks), and no Astro pages. Read the site (read-site.mjs); run the vertical's seed module with a plan only for content the brief supplies or describes. Release with npx @wix/cli@latest build && npx @wix/cli@latest release; the URL stays",
|
|
120
129
|
"wix-project": "iterate: never scaffold, init or reseed. deploy.mjs <vertical…> --stack <stack> adds a solution, then ONE npm install; file edits for a change; release. Read the site (read-site.mjs) before any seed module runs",
|
|
121
130
|
"published-static": "setup.mjs --vertical <v>: the config's site, no init; site/ becomes the upload and the REST layer lands in site/js/wix/; move the pages, styles and assets into site/. Nothing is seeded: read the site, then run the vertical's seed module with a plan when the brief gives content; release keeps the URL",
|
|
122
131
|
};
|
|
123
132
|
let shape;
|
|
124
133
|
if (!config) shape = f.packageJson || f.rootIndex ? "project" : "empty";
|
|
125
134
|
else if (migrationActive) shape = "migration";
|
|
135
|
+
else if (f.packageJson && f.publicIndex && !f.astroPages) shape = "static-in-astro";
|
|
126
136
|
else if (f.packageJson || f.outputIndex) shape = "wix-project";
|
|
127
137
|
else if (f.rootIndex) shape = "published-static";
|
|
128
138
|
else shape = "config-only";
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
// <out>/js/wix/ — shared/rest/ (client, media, config) + each vertical's rest/ + the
|
|
19
19
|
// vertical's transport-agnostic core files (types, *-core) from its app/ — writes
|
|
20
20
|
// the client id, and strips it to browser-ready ESM with tsc (comments kept, the
|
|
21
|
-
// .ts sources kept beside the .js). No package.json is touched.
|
|
22
|
-
//
|
|
21
|
+
// .ts sources kept beside the .js). No package.json is touched. Every vertical
|
|
22
|
+
// with a rest/ folder is supported. `--out <dir>` (default: the project root)
|
|
23
23
|
// is the folder the site is served from — the one wix.config.json's
|
|
24
24
|
// site.outputDirectory points at — so the modules land where the pages import
|
|
25
25
|
// them and nothing else in the project gets uploaded.
|
|
@@ -431,7 +431,7 @@ if (siteSearch) {
|
|
|
431
431
|
result.capabilities = { ...result.capabilities, siteSearch: true };
|
|
432
432
|
if (siteSearch.install) {
|
|
433
433
|
// Indexing needs the Wix Site Search app on the site — a site mutation the deploy never makes.
|
|
434
|
-
result.note = [result.note,
|
|
434
|
+
result.note = [result.note, `siteSearch.install: run node ${join(REF, "shared", "capabilities", "site-search", "seed", "install.mjs")} from the project root after the seed (results appear about half a minute later)`].filter(Boolean).join("; ");
|
|
435
435
|
}
|
|
436
436
|
}
|
|
437
437
|
|
|
@@ -276,7 +276,7 @@ if (stack !== "static" && existsSync(join(projectDir, "package.json"))) {
|
|
|
276
276
|
const madeTheSite = mode === "create" || mode === "adopt";
|
|
277
277
|
let seed = null;
|
|
278
278
|
if (planPath && !madeTheSite) {
|
|
279
|
-
emit("seed_skipped", { reason: `${mode}: the site existed before this run; setup seeds only a site it created. Read what the site holds (
|
|
279
|
+
emit("seed_skipped", { reason: `${mode}: the site existed before this run; setup seeds only a site it created. Read what the site holds (${join(TEMPLATES, vertical, "seed", "read-site.mjs")}), then run templates/${vertical}/seed/seed-*.mjs ${planPath} yourself when the brief supplies or describes content` });
|
|
280
280
|
}
|
|
281
281
|
if (planPath && madeTheSite) {
|
|
282
282
|
const seedDir = join(TEMPLATES, vertical, "seed");
|
|
@@ -325,7 +325,7 @@ emit("ready_for_brand_layer", {
|
|
|
325
325
|
? "a migration preview: the site being migrated owns its content (read it with the vertical's read-site.mjs when the brief allows probing; never seed it); theme + write the home page; "
|
|
326
326
|
: madeTheSite
|
|
327
327
|
? (planPath ? "theme + write the home page; " : "the site is new and empty and no plan was given, so nothing was seeded yet: seed it now (a plan per step 2 — the brief's content, or one drafted per the vertical's SEED.md — then the vertical's seed module), and name the placeholder content in the closing message; theme + write the home page; ")
|
|
328
|
-
: `nothing was seeded (setup seeds only a site it created): read what the site holds with
|
|
328
|
+
: `nothing was seeded (setup seeds only a site it created): read what the site holds with ${join(TEMPLATES, vertical, "seed", "read-site.mjs")}, then run ${join(TEMPLATES, vertical, "seed")}/seed-*.mjs <plan> when the brief supplies or describes content; theme + write the home page; `) +
|
|
329
329
|
(others.length && mode !== "migrate"
|
|
330
330
|
? `${others.join(", ")} deployed too, no further install needed: run each one's seed module (templates/<vertical>/seed/) with its own plan when the brief gives it content (the members seed installs the Members Area app and needs no plan); `
|
|
331
331
|
: "") +
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
//
|
|
8
8
|
// Resolution, in order:
|
|
9
9
|
// 1. the sibling skill folder, `<SKILL_ROOT>/../wix-headless-templates/`: a checkout of the repository,
|
|
10
|
-
// or
|
|
10
|
+
// or an install that carries both skills (the cold start's `npx skills add`, the npm package,
|
|
11
|
+
// the Codex and Cursor plugins; the Claude plugin leaves it out, so there it is fetched).
|
|
11
12
|
// 2. the cache `<SKILL_ROOT>/templates/`, filled by an earlier call (`--refresh` refetches).
|
|
12
13
|
// 3. a fetch: a sparse, shallow clone of `skills/wix-headless-templates/` from the repository the skill was installed
|
|
13
14
|
// from (skills-lock.json's `source`, default wix/skills), at the branch or tag the install
|
|
@@ -52,7 +52,7 @@ feed without covers looks broken.
|
|
|
52
52
|
- Cover — the default is a `coverImagePrompt` (AI-generated, ~1 Wix AI credit per image,
|
|
53
53
|
account-billed): brand-contextual — subject, aesthetic/mood, palette, lighting — always
|
|
54
54
|
ending "no text, no watermarks". At least one image in the set shows the real subject of the business — the actual product/space/service, not abstract decoration. For an asset the user actually supplied use
|
|
55
|
-
`coverImagePath` (a file on this machine — uploaded to Wix Media) or `coverImageUrl` (their
|
|
55
|
+
`coverImageMediaId` (a file already in the site's Media Manager, such as one the user uploaded there: used as it is, no copy; a `static.wixstatic.com/media/<file id>` URL of a site file counts as one), `coverImagePath` (a file on this machine — uploaded to Wix Media) or `coverImageUrl` (their
|
|
56
56
|
own hosted URL; verify it with `curl -sI` → 200) — never a stock-photo or guessed URL. Covers resolve
|
|
57
57
|
in parallel and never block the seed; a failed cover leaves that post text-only (the script
|
|
58
58
|
re-publishes each post it covers).
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// Plan shape (see SEED.md):
|
|
13
13
|
// { "categories"?: [name], "tags"?: [name],
|
|
14
14
|
// "posts": [{ "title", "content": [blocks] | "richContent"?, "category"?|"categories"?,
|
|
15
|
-
// "tags"?, "coverImageUrl"? | "coverImagePrompt"? }] }
|
|
15
|
+
// "tags"?, "coverImageMediaId"? | "coverImageUrl"? | "coverImagePrompt"? }] }
|
|
16
16
|
// content blocks: { type:"heading", text, level? } | { type:"paragraph", text }
|
|
17
17
|
// | { type:"quote", text } | { type:"bulleted"|"ordered", items:[text,…] }
|
|
18
18
|
//
|
|
@@ -255,7 +255,7 @@ export async function setupBlog(ctx, { posts = [], categories = [], tags = [] }
|
|
|
255
255
|
// never depends on images.
|
|
256
256
|
const files = await resolveItemImages(ctx, created.map((c, i) => (
|
|
257
257
|
c?.id && c?.success
|
|
258
|
-
? { path: posts[i]?.coverImagePath, url: posts[i]?.coverImageUrl, prompt: posts[i]?.coverImagePrompt, displayName: `post-${i}.png` }
|
|
258
|
+
? { mediaId: posts[i]?.coverImageMediaId, path: posts[i]?.coverImagePath, url: posts[i]?.coverImageUrl, prompt: posts[i]?.coverImagePrompt, displayName: `post-${i}.png` }
|
|
259
259
|
: null
|
|
260
260
|
)));
|
|
261
261
|
const covers = created
|
|
@@ -19,7 +19,7 @@ service an image — the default is an `imagePrompt` (AI-generated, ~1 Wix AI cr
|
|
|
19
19
|
account-billed): brand-contextual — subject, aesthetic/mood, palette, lighting — always ending
|
|
20
20
|
"no text, no watermarks". At least one image in the set shows the real subject of the business —
|
|
21
21
|
the actual product/space/service, not abstract decoration. For an asset the user actually supplied
|
|
22
|
-
use `imagePath` (a file on this machine — uploaded to Wix Media) or `imageUrl` (their own hosted
|
|
22
|
+
use `imageMediaId` (a file already in the site's Media Manager, such as one the user uploaded there: used as it is, no copy; a `static.wixstatic.com/media/<file id>` URL of a site file counts as one), `imagePath` (a file on this machine — uploaded to Wix Media) or `imageUrl` (their own hosted
|
|
23
23
|
URL; verify it with `curl -sI` → 200) — never a stock-photo or guessed URL. Images resolve in
|
|
24
24
|
parallel and never block the seed; a failed image leaves that service text-only.
|
|
25
25
|
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// "price"? (number | { "from": number }), "priceText"?, "free"?, "deposit"?, "payInFull"?,
|
|
18
18
|
// "duration"? (APPOINTMENT, minutes), "capacity"?, "waitlist"?, "maxParticipants"?,
|
|
19
19
|
// "requireManualApproval"?, "conferencing"?, "staff"? (name | [names]), "location"? (name),
|
|
20
|
-
// "category"? (name), "imageUrl"? | "imagePath"? | "imagePrompt"?,
|
|
20
|
+
// "category"? (name), "imageMediaId"? | "imageUrl"? | "imagePath"? | "imagePrompt"?,
|
|
21
21
|
// "sessions"?: [{ "start", "end", "capacity"? }], // CLASS: one-off sessions, local "YYYY-MM-DDThh:mm:ss"
|
|
22
22
|
// "weekly"?: { "days": ["MONDAY"], "time": "18:00", "duration": 60, "start", "end" } }] } // CLASS or COURSE: recurring
|
|
23
23
|
//
|
|
@@ -355,6 +355,7 @@ export async function setupBookings(ctx, { services = [], staff = [], locations
|
|
|
355
355
|
// Pass 2 — images: resolve (import by url / generate by prompt) in one parallel wave, then
|
|
356
356
|
// attach. Failures leave the service text-only; the seed's exit never depends on images.
|
|
357
357
|
const files = await resolveItemImages(ctx, created.map((c, i) => ({
|
|
358
|
+
mediaId: services[i]?.imageMediaId,
|
|
358
359
|
url: services[i]?.imageUrl,
|
|
359
360
|
path: services[i]?.imagePath,
|
|
360
361
|
prompt: services[i]?.imagePrompt,
|
|
@@ -16,7 +16,7 @@ amount with a minimum, the note and the cover-fee prompt on. A second campaign (
|
|
|
16
16
|
only when the brief lists several causes. Every campaign gets a cover image — the default is an
|
|
17
17
|
`imagePrompt` (AI-generated, ~1 Wix AI credit per image, account-billed): brand-contextual — subject,
|
|
18
18
|
mood, palette, lighting — always ending "no text, no watermarks"; a real subject of the cause, not
|
|
19
|
-
abstract decoration. For an asset the user supplied use `imagePath` (a file on this machine) or
|
|
19
|
+
abstract decoration. For an asset the user supplied use `imageMediaId` (a file already in the site's Media Manager, such as one the user uploaded there: used as it is, no copy; a `static.wixstatic.com/media/<file id>` URL of a site file counts as one), `imagePath` (a file on this machine) or
|
|
20
20
|
`imageUrl` (their own hosted URL; verify with `curl -sI` → 200) — never a stock or guessed URL.
|
|
21
21
|
Images resolve in parallel and never block the seed; a failed image leaves the campaign text-only.
|
|
22
22
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// "customAmount"?: { "enabled", "min"?, "max"? },
|
|
17
17
|
// "goal"?: { "target", "endDate"?, "acceptAfterGoal"?, "acceptAfterEndDate"? },
|
|
18
18
|
// "comments"?: false, "askCoverFee"?: false,
|
|
19
|
-
// "imageUrl"? | "imagePath"? | "imagePrompt"? }] }
|
|
19
|
+
// "imageMediaId"? | "imageUrl"? | "imagePath"? | "imagePrompt"? }] }
|
|
20
20
|
//
|
|
21
21
|
// Seeding is ADDITIVE and idempotent — a non-archived campaign with the same name is reused, never
|
|
22
22
|
// duplicated; nothing is deleted or overwritten. Unexpected shapes → read the live API reference;
|
|
@@ -196,8 +196,9 @@ export async function setupDonations(ctx, { campaigns = [], currency } = {}) {
|
|
|
196
196
|
|
|
197
197
|
// Pass 2 — images: resolve (upload a local file / import by url / generate by prompt) in one
|
|
198
198
|
// parallel wave, then attach. Failures leave the campaign text-only; the exit never depends on them.
|
|
199
|
-
const wanted = created.filter((c) => c.created && (c.plan.imageUrl || c.plan.imagePath || c.plan.imagePrompt));
|
|
199
|
+
const wanted = created.filter((c) => c.created && (c.plan.imageMediaId || c.plan.imageUrl || c.plan.imagePath || c.plan.imagePrompt));
|
|
200
200
|
const files = await resolveItemImages(ctx, wanted.map((c) => ({
|
|
201
|
+
mediaId: c.plan.imageMediaId,
|
|
201
202
|
url: c.plan.imageUrl,
|
|
202
203
|
path: c.plan.imagePath,
|
|
203
204
|
prompt: c.plan.imagePrompt,
|
|
@@ -17,7 +17,7 @@ the shape; the owner adds the rest in the dashboard) and make them exercise the
|
|
|
17
17
|
image (an events page without images looks broken) — the default is an `imagePrompt`
|
|
18
18
|
(AI-generated, ~1 Wix AI credit per image, account-billed): brand-contextual — subject,
|
|
19
19
|
aesthetic/mood, palette, lighting — always ending "no text, no watermarks". At least one image in the set shows the real subject of the business — the actual product/space/service, not abstract decoration. For an asset the user actually
|
|
20
|
-
supplied use `imagePath` (a file on this machine — uploaded to Wix Media) or `imageUrl`
|
|
20
|
+
supplied use `imageMediaId` (a file already in the site's Media Manager, such as one the user uploaded there: used as it is, no copy; a `static.wixstatic.com/media/<file id>` URL of a site file counts as one), `imagePath` (a file on this machine — uploaded to Wix Media) or `imageUrl`
|
|
21
21
|
(their own hosted URL; verify it with `curl -sI` → 200) — never a stock-photo or guessed URL. Images resolve in parallel and never block the
|
|
22
22
|
seed; a failed image leaves that event text-only.
|
|
23
23
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// "startDate", "endDate" (future ISO-8601 UTC), "timeZoneId",
|
|
15
15
|
// "location" ({name,type:"VENUE",address} | {name,type:"ONLINE"} | {locationTbd:true,name}),
|
|
16
16
|
// "ticketTiers"?: [{ "name" (≤30 chars), "price" (decimal STRING), "description"?, "initialLimit"?, "feeType"? }],
|
|
17
|
-
// "category"? (name), "imageUrl"? | "imagePrompt"?, "rsvpResponseType"? }] }
|
|
17
|
+
// "category"? (name), "imageMediaId"? | "imageUrl"? | "imagePrompt"?, "rsvpResponseType"? }] }
|
|
18
18
|
//
|
|
19
19
|
// Seeding is ADDITIVE — never deletes or overwrites existing content. Unexpected shapes →
|
|
20
20
|
// read the live API reference; every call below
|
|
@@ -260,7 +260,7 @@ export async function setupEvents(ctx, { events = [], currency } = {}) {
|
|
|
260
260
|
catch (err) { console.error(`guest control skipped for "${ev.title}": ${err.message}`); }
|
|
261
261
|
}
|
|
262
262
|
await publishEvent(ctx, e.id);
|
|
263
|
-
created.push({ ...e, category: ev.category, imageUrl: ev.imageUrl, imagePrompt: ev.imagePrompt, ticketCount: tiers.length, feeTypes: tiers.map((t) => t.feeType), ...(ev.guests ? { guestControl } : {}) });
|
|
263
|
+
created.push({ ...e, category: ev.category, imageMediaId: ev.imageMediaId, imageUrl: ev.imageUrl, imagePrompt: ev.imagePrompt, ticketCount: tiers.length, feeTypes: tiers.map((t) => t.feeType), ...(ev.guests ? { guestControl } : {}) });
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
const names = [...new Set(created.map((e) => e.category).filter(Boolean))];
|
|
@@ -273,6 +273,7 @@ export async function setupEvents(ctx, { events = [], currency } = {}) {
|
|
|
273
273
|
// Pass 2 — images: resolve (import by url / generate by prompt) in one parallel wave, then
|
|
274
274
|
// attach. Failures leave the event text-only; the seed's exit never depends on images.
|
|
275
275
|
const files = await resolveItemImages(ctx, created.map((e) => ({
|
|
276
|
+
mediaId: e.imageMediaId,
|
|
276
277
|
url: e.imageUrl,
|
|
277
278
|
path: e.imagePath,
|
|
278
279
|
prompt: e.imagePrompt,
|
|
@@ -53,8 +53,10 @@ rows (Role, Year, Client…).
|
|
|
53
53
|
- Every image field's default is a prompt (`coverImagePrompt` / `items[].imagePrompt` —
|
|
54
54
|
AI-generated, ~1 Wix AI credit per image, account-billed): brand-contextual — subject,
|
|
55
55
|
aesthetic/mood, palette, lighting — always ending "no text, no watermarks". At least one image in the set shows the real subject of the business — the actual product/space/service, not abstract decoration. For an asset the user actually supplied
|
|
56
|
-
use a
|
|
57
|
-
|
|
56
|
+
use a Media Manager file (`coverImageMediaId` / `items[].imageMediaId` — a file already in the
|
|
57
|
+
site's Media Manager, such as one the user uploaded there: used as it is, no copy; a
|
|
58
|
+
`static.wixstatic.com/media/<file id>` URL of a site file counts as one), a path
|
|
59
|
+
(`coverImagePath` / `items[].imagePath` — a file on this machine, uploaded to Wix Media) or a url (`coverImageUrl` / `items[].imageUrl` — their own hosted URL; verify it
|
|
58
60
|
with `curl -sI` → 200) — never a stock-photo or guessed URL. All images —
|
|
59
61
|
covers and gallery items alike — resolve in one parallel wave and never block the seed; a
|
|
60
62
|
failed image skips just that item/cover. The **cover** is the
|
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
// project's gallery items, and imports+attaches cover images. Prints a JSON result to stdout.
|
|
10
10
|
//
|
|
11
11
|
// Plan shape (see SEED.md):
|
|
12
|
-
// { "collections": [{ "title", "description"?, "hidden"?, "coverImageUrl"? | "coverImagePrompt"? }],
|
|
12
|
+
// { "collections": [{ "title", "description"?, "hidden"?, "coverImageMediaId"? | "coverImageUrl"? | "coverImagePrompt"? }],
|
|
13
13
|
// "projects": [{ "title", "description"?, "hidden"?,
|
|
14
14
|
// "collection"? (title), // resolved to that collection's id
|
|
15
15
|
// "details"?: [{ "label", "text" }],
|
|
16
|
-
// "coverImageUrl"? | "coverImagePrompt"?,
|
|
17
|
-
// "items"?: [{ "sortOrder", "title"?, "imageUrl" | "imagePrompt" }] }] }
|
|
16
|
+
// "coverImageMediaId"? | "coverImageUrl"? | "coverImagePrompt"?,
|
|
17
|
+
// "items"?: [{ "sortOrder", "title"?, "imageMediaId" | "imageUrl" | "imagePrompt" }] }] }
|
|
18
18
|
//
|
|
19
19
|
// Seeding is ADDITIVE — never deletes or overwrites existing content. A fresh Portfolio
|
|
20
20
|
// install ships its own sample content ("My Portfolio" + sample projects); removing it is the
|
|
@@ -186,13 +186,13 @@ export async function setupPortfolio(ctx, { collections = [], projects = [] } =
|
|
|
186
186
|
projects.forEach((p, pi) => {
|
|
187
187
|
for (const it of p.items ?? []) {
|
|
188
188
|
galleryRefs.push({ pi, it, spec: specs.length });
|
|
189
|
-
specs.push({ path: it.imagePath, url: it.imageUrl, prompt: it.imagePrompt, displayName: `${it.title || "item"}.png` });
|
|
189
|
+
specs.push({ mediaId: it.imageMediaId, path: it.imagePath, url: it.imageUrl, prompt: it.imagePrompt, displayName: `${it.title || "item"}.png` });
|
|
190
190
|
}
|
|
191
191
|
});
|
|
192
192
|
const projCoverAt = specs.length;
|
|
193
|
-
projects.forEach((p) => specs.push({ path: p.coverImagePath, url: p.coverImageUrl, prompt: p.coverImagePrompt, displayName: `${p.title || "project"}-cover.png` }));
|
|
193
|
+
projects.forEach((p) => specs.push({ mediaId: p.coverImageMediaId, path: p.coverImagePath, url: p.coverImageUrl, prompt: p.coverImagePrompt, displayName: `${p.title || "project"}-cover.png` }));
|
|
194
194
|
const colCoverAt = specs.length;
|
|
195
|
-
collections.forEach((c) => specs.push({ path: c.coverImagePath, url: c.coverImageUrl, prompt: c.coverImagePrompt, displayName: `${c.title || "collection"}-cover.png` }));
|
|
195
|
+
collections.forEach((c) => specs.push({ mediaId: c.coverImageMediaId, path: c.coverImagePath, url: c.coverImageUrl, prompt: c.coverImagePrompt, displayName: `${c.title || "collection"}-cover.png` }));
|
|
196
196
|
const files = await resolveItemImages(ctx, specs);
|
|
197
197
|
const dims = { height: 1024, width: 1024 };
|
|
198
198
|
|
|
@@ -19,7 +19,7 @@ exercise the UI: one hourly and one daily rental when the business allows it, a
|
|
|
19
19
|
approval-gated one where it fits, and an image per rental — the default is an `imagePrompt`
|
|
20
20
|
(AI-generated, ~1 Wix AI credit per image, account-billed): brand-contextual — subject,
|
|
21
21
|
aesthetic/mood, palette, lighting — always ending "no text, no watermarks". At least one image shows
|
|
22
|
-
the real thing being rented. For an asset the user actually supplied use `imagePath` (a file on this
|
|
22
|
+
the real thing being rented. For an asset the user actually supplied use `imageMediaId` (a file already in the site's Media Manager, such as one the user uploaded there: used as it is, no copy; a `static.wixstatic.com/media/<file id>` URL of a site file counts as one), `imagePath` (a file on this
|
|
23
23
|
machine — uploaded to Wix Media) or `imageUrl` (their own hosted URL; verify it with `curl -sI` →
|
|
24
24
|
200) — never a stock-photo or guessed URL. Images resolve in parallel and never block the seed.
|
|
25
25
|
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// "resourceTypes": [{ "name": "Kayaks", "resources": ["Kayak 1", "Kayak 2"] }],
|
|
18
18
|
// "rentals": [{ "name", "description", "tagLine"?, "unit": "HOUR"|"DAY", "rate": 25, "min"?, "max"?,
|
|
19
19
|
// "resourceType": "Kayaks", "resources"?: ["Kayak 1"], "free"?, "requireManualApproval"?,
|
|
20
|
-
// "imageUrl"? | "imagePath"? | "imagePrompt"? }] }
|
|
20
|
+
// "imageMediaId"? | "imageUrl"? | "imagePath"? | "imagePrompt"? }] }
|
|
21
21
|
//
|
|
22
22
|
// Seeding is ADDITIVE — never deletes or overwrites existing content. Unexpected shapes →
|
|
23
23
|
// read the live API reference; every call below carries a docs: line with its reference page.
|
|
@@ -268,6 +268,7 @@ export async function setupRentals(ctx, { resourceTypes = [], rentals = [], curr
|
|
|
268
268
|
// Pass 2 — images: resolve (import by url / generate by prompt) in one parallel wave, then attach.
|
|
269
269
|
// Failures leave the rental text-only; the seed's exit never depends on images.
|
|
270
270
|
const files = await resolveItemImages(ctx, created.map((c, i) => ({
|
|
271
|
+
mediaId: rentals[i]?.imageMediaId,
|
|
271
272
|
url: rentals[i]?.imageUrl,
|
|
272
273
|
path: rentals[i]?.imagePath,
|
|
273
274
|
prompt: rentals[i]?.imagePrompt,
|
|
@@ -16,7 +16,7 @@ of 2–3 items each** (the seed shows the shape; the owner adds the rest in the
|
|
|
16
16
|
every item with an image (a menu without photos looks broken) — the default is an
|
|
17
17
|
`imagePrompt` (AI-generated, ~1 Wix AI credit per image, account-billed): brand-contextual —
|
|
18
18
|
subject, aesthetic/mood, palette, lighting — always ending "no text, no watermarks". At least one image in the set shows the real subject of the business — the actual product/space/service, not abstract decoration. For an asset the user actually
|
|
19
|
-
supplied use `imagePath` (a file on this machine — uploaded to Wix Media) or `imageUrl`
|
|
19
|
+
supplied use `imageMediaId` (a file already in the site's Media Manager, such as one the user uploaded there: used as it is, no copy; a `static.wixstatic.com/media/<file id>` URL of a site file counts as one), `imagePath` (a file on this machine — uploaded to Wix Media) or `imageUrl`
|
|
20
20
|
(their own hosted URL; verify it with `curl -sI` → 200) — never a stock-photo or guessed URL. Images resolve in parallel and never
|
|
21
21
|
block the seed, a failed image leaves that item text-only — and both add-ons on for a
|
|
22
22
|
restaurant that takes orders and reservations. Give at least one dish a choice (a `variants`
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// "items": [{ "name", "description"?, "price"? | "variants"?: [{ "name", "price" }],
|
|
15
15
|
// "modifierGroups"?: [{ "name", "required"?, "min"?, "max"?,
|
|
16
16
|
// "modifiers": [{ "name", "price"?, "preSelected"?, "inStock"? }] }],
|
|
17
|
-
// "acceptSpecialRequests"?, "imageUrl"? | "imagePath"? | "imagePrompt"? }] }] }],
|
|
17
|
+
// "acceptSpecialRequests"?, "imageMediaId"? | "imageUrl"? | "imagePath"? | "imagePrompt"? }] }] }],
|
|
18
18
|
// "ordering"?: true | { "address"? }, // menu-first add-on; address is STEP 0
|
|
19
19
|
// "reservations"?: true | { "partySize"? { "min","max" }, "address"? } }
|
|
20
20
|
//
|
|
@@ -459,12 +459,13 @@ export async function setupRestaurants(ctx, plan) {
|
|
|
459
459
|
const flat = m.sections.flatMap((s) => s.items || []);
|
|
460
460
|
flat.forEach((it, i) => {
|
|
461
461
|
const created = createdMenus[mi]?.items?.[i];
|
|
462
|
-
if ((it.imageUrl || it.imagePath || it.imagePrompt) && created?.id) {
|
|
463
|
-
imageItems.push({ ...created, imageUrl: it.imageUrl, imagePath: it.imagePath, imagePrompt: it.imagePrompt, name: it.name });
|
|
462
|
+
if ((it.imageMediaId || it.imageUrl || it.imagePath || it.imagePrompt) && created?.id) {
|
|
463
|
+
imageItems.push({ ...created, imageMediaId: it.imageMediaId, imageUrl: it.imageUrl, imagePath: it.imagePath, imagePrompt: it.imagePrompt, name: it.name });
|
|
464
464
|
}
|
|
465
465
|
});
|
|
466
466
|
});
|
|
467
467
|
const files = await resolveItemImages(ctx, imageItems.map((it) => ({
|
|
468
|
+
mediaId: it.imageMediaId,
|
|
468
469
|
url: it.imageUrl,
|
|
469
470
|
path: it.imagePath,
|
|
470
471
|
prompt: it.imagePrompt,
|
|
@@ -124,6 +124,37 @@ async function findExisting(ctx, displayName) {
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
/**
|
|
128
|
+
* The Media Manager file a spec names, when it names one: `mediaId`, or the file id at the end of a
|
|
129
|
+
* `static.wixstatic.com/media/<fileId>` URL. Returns { id, url, existing: true } for a file on this
|
|
130
|
+
* site, waiting out a short PENDING (an upload still processing), or null when the URL's file is
|
|
131
|
+
* not on this site (it is then imported like any external URL). An explicit `mediaId` that is not
|
|
132
|
+
* on the site throws, so the product is reported text-only with the reason.
|
|
133
|
+
*/
|
|
134
|
+
// docs: https://dev.wix.com/docs/api-reference/assets/media/media-manager/files/get-file-descriptor.md
|
|
135
|
+
const WIX_MEDIA_URL = /^https?:\/\/static\.wixstatic\.com\/media\/([^/?#]+)/;
|
|
136
|
+
async function existingMediaFile(ctx, s) {
|
|
137
|
+
const id = s.mediaId ?? s.url?.match(WIX_MEDIA_URL)?.[1];
|
|
138
|
+
if (!id) return null;
|
|
139
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
140
|
+
const res = await fetch(`${API}/site-media/v1/files/get-file-by-id?fileId=${encodeURIComponent(id)}`, {
|
|
141
|
+
headers: { Authorization: `Bearer ${ctx.token}`, "wix-site-id": ctx.siteId },
|
|
142
|
+
signal: AbortSignal.timeout(20_000),
|
|
143
|
+
});
|
|
144
|
+
if (res.status === 404) {
|
|
145
|
+
if (s.mediaId) throw new Error(`no Media Manager file ${id} on this site`);
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const json = await res.json().catch(() => ({}));
|
|
149
|
+
if (!res.ok) throw new Error(`GET get-file-by-id -> ${res.status}: ${JSON.stringify(json).slice(0, 200)}`);
|
|
150
|
+
const f = json.file;
|
|
151
|
+
if (f?.operationStatus === "FAILED") throw new Error(`Media Manager file ${id} failed processing`);
|
|
152
|
+
if (f?.url && f.operationStatus !== "PENDING") return { id: f.id, url: f.url, existing: true };
|
|
153
|
+
await new Promise((r) => setTimeout(r, 3000));
|
|
154
|
+
}
|
|
155
|
+
throw new Error(`Media Manager file ${id} still processing`);
|
|
156
|
+
}
|
|
157
|
+
|
|
127
158
|
/** Short stable fingerprint of an image's source (url, prompt + size, or file bytes). */
|
|
128
159
|
function sourceHash(s) {
|
|
129
160
|
const h = createHash("sha1");
|
|
@@ -149,12 +180,17 @@ export async function importImage(ctx, url, displayName = "image.png") {
|
|
|
149
180
|
|
|
150
181
|
/**
|
|
151
182
|
* THE seed entry point. Resolves a batch of image specs to Wix Media files in ONE parallel
|
|
152
|
-
* wave. Each spec: {
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
183
|
+
* wave. Each spec: { mediaId } (a file already in this site's Media Manager — used as it is,
|
|
184
|
+
* as is a static.wixstatic.com/media URL of one) OR { path } (LOCAL file — the user's own asset,
|
|
185
|
+
* uploaded) OR { url } (verified external URL — imported) OR { prompt } (generated, ~1 credit) —
|
|
186
|
+
* plus optional
|
|
187
|
+
* displayName, width, height. Returns an array aligned with the input: { file, error } per
|
|
188
|
+
* spec — `file` is { id, url } on success and null otherwise; `error` is the reason it is null
|
|
189
|
+
* (the thrown message, "empty spec", or "timed out"). Never throws. Every failure is also
|
|
190
|
+
* printed to stderr, so a seed log names the image and the reason even when the seed's
|
|
191
|
+
* own report does not.
|
|
156
192
|
*/
|
|
157
|
-
export async function
|
|
193
|
+
export async function resolveItemImagesDetailed(ctx, specs, { perImageBudgetMs = 120_000 } = {}) {
|
|
158
194
|
// unref: the budget timer must never keep the seed process alive after the work is done —
|
|
159
195
|
// a lingering timer delays the seed's exit (and the run's .seed-exit marker) by the budget.
|
|
160
196
|
const deadline = new Promise((r) => {
|
|
@@ -163,8 +199,11 @@ export async function resolveItemImages(ctx, specs, { perImageBudgetMs = 120_000
|
|
|
163
199
|
});
|
|
164
200
|
const results = await Promise.allSettled(
|
|
165
201
|
(specs ?? []).map(async (s) => {
|
|
166
|
-
if (!s || (!s.path && !s.url && !s.prompt)) return null;
|
|
202
|
+
if (!s || (!s.path && !s.url && !s.prompt && !s.mediaId)) return null;
|
|
167
203
|
const resolve = (async () => {
|
|
204
|
+
// A file already in this site's Media Manager is used as it is: no import, no copy.
|
|
205
|
+
const inMedia = await existingMediaFile(ctx, s);
|
|
206
|
+
if (inMedia) return inMedia;
|
|
168
207
|
const name = stableName(s);
|
|
169
208
|
// uploadImage swaps in the file's own extension; look for what it will actually be named
|
|
170
209
|
const uploadedName = s.path ? name.replace(/\.[a-z0-9]+$/i, "") + extname(s.path).toLowerCase() : name;
|
|
@@ -179,5 +218,22 @@ export async function resolveItemImages(ctx, specs, { perImageBudgetMs = 120_000
|
|
|
179
218
|
return Promise.race([resolve, deadline]);
|
|
180
219
|
}),
|
|
181
220
|
);
|
|
182
|
-
return results.map((r) =>
|
|
221
|
+
return results.map((r, i) => {
|
|
222
|
+
const spec = specs?.[i] ?? {};
|
|
223
|
+
const label = spec.displayName ?? spec.mediaId ?? spec.path ?? spec.url ?? (spec.prompt ? "(prompt)" : "(empty)");
|
|
224
|
+
if (r.status === "fulfilled") {
|
|
225
|
+
if (r.value) return { file: r.value, error: null };
|
|
226
|
+
const error = !spec.path && !spec.url && !spec.prompt && !spec.mediaId ? "empty spec" : `timed out after ${perImageBudgetMs} ms`;
|
|
227
|
+
if (error !== "empty spec") console.error(`image ${label}: ${error}`);
|
|
228
|
+
return { file: null, error };
|
|
229
|
+
}
|
|
230
|
+
const error = r.reason?.message ?? String(r.reason);
|
|
231
|
+
console.error(`image ${label}: ${error.slice(0, 200)}`);
|
|
232
|
+
return { file: null, error };
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** The plain array of { id, url } | null the seeds consume today; failures are still printed. */
|
|
237
|
+
export async function resolveItemImages(ctx, specs, opts) {
|
|
238
|
+
return (await resolveItemImagesDetailed(ctx, specs, opts)).map((r) => r.file);
|
|
183
239
|
}
|
|
@@ -54,8 +54,8 @@ to a file if you want it later). `.seed-exit` and `seed-result.json` are written
|
|
|
54
54
|
`type: "color"` renders as real swatches (give every color choice a `colorCode`); anything
|
|
55
55
|
else renders as text pills. Variants are expanded automatically (full cross-product, each
|
|
56
56
|
carrying the product's price/compareAtPrice/quantity) — keep option counts small.
|
|
57
|
-
A choice may carry its own photo — `
|
|
58
|
-
same
|
|
57
|
+
A choice may carry its own photo — `imageMediaId` | `imageUrl` | `imagePath` | `imagePrompt`
|
|
58
|
+
(+ `altText`), the same forms as a product image: it joins the product's gallery and the product page shows
|
|
59
59
|
it when that choice is picked (a colour's photo). Give it when the source has one per colour.
|
|
60
60
|
- `variantPrices` — `{ "<choice name>": price }` when one option's choices are priced differently
|
|
61
61
|
("L": 74): every variant carrying that choice takes that price (the first priced choice wins);
|
|
@@ -76,9 +76,14 @@ to a file if you want it later). `.seed-exit` and `seed-result.json` are written
|
|
|
76
76
|
- **Give every product an image** (a store without product images looks broken: gray boxes on
|
|
77
77
|
tiles, PDP, and cart) — the default is an `imagePrompt` (AI-generated, ~1 Wix AI credit
|
|
78
78
|
per image, account-billed): brand-contextual — subject, aesthetic/mood, palette, lighting —
|
|
79
|
-
always ending "no text, no watermarks". At least one image in the set shows the real subject of the business — the actual product/space/service, not abstract decoration. For an asset the user actually supplied use `
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
always ending "no text, no watermarks". At least one image in the set shows the real subject of the business — the actual product/space/service, not abstract decoration. For an asset the user actually supplied use `imageMediaId` (a file
|
|
80
|
+
already in the site's Media Manager, such as one the user uploaded there: the product uses that
|
|
81
|
+
file as it is, no copy), `imagePath` (a file on this machine — uploaded to Wix Media) or
|
|
82
|
+
`imageUrl` (their own hosted URL; verify it with `curl -sI` → 200) — never a stock-photo or
|
|
83
|
+
guessed URL. A `static.wixstatic.com/media/<file id>` URL of a file on this site counts as its
|
|
84
|
+
`imageMediaId`. List the site's files to find an id: the
|
|
85
|
+
[Media Manager](https://dev.wix.com/docs/api-reference/assets/media/media-manager/files/list-files.md)
|
|
86
|
+
names each by the name it was uploaded under. Images resolve in parallel and never block the seed; a failed image leaves
|
|
82
87
|
that product text-only. Seed text-only only when the user explicitly asks.
|
|
83
88
|
- `digitalFilePath` (a file on this machine) or `digitalFileUrl` — makes the product a digital
|
|
84
89
|
download, uploaded and created with both the file and stock (`quantity` is ignored). It's also the
|
|
@@ -95,6 +100,13 @@ to a file if you want it later). `.seed-exit` and `seed-result.json` are written
|
|
|
95
100
|
- `quantity` — tracked stock, a non-negative integer. For stock that isn't counted (made to
|
|
96
101
|
order, print on demand, unlimited) use `"inStock": true` **instead** of `quantity`; sending
|
|
97
102
|
both is rejected.
|
|
103
|
+
Right after the bulk create, a product's own stock summary can still read `OUT_OF_STOCK`
|
|
104
|
+
while its inventory items are already stocked; the items are what a buyer's add is checked
|
|
105
|
+
against. A product **without options** can still end up with no item (a create that ran while
|
|
106
|
+
the catalog was provisioning): the script stocks those afterwards with one Bulk Create
|
|
107
|
+
Inventory Items call (`productId`, its single `variantId`, and `inStock: true` or the
|
|
108
|
+
`quantity`), where `ALREADY_EXISTS` means the item is already there. A seed made call by call
|
|
109
|
+
reads the items (Query Inventory Items) and creates only the missing ones.
|
|
98
110
|
- `currency` — 3-letter ISO code. Set it **only when the brief names one**: a sentence about
|
|
99
111
|
currency ("prices in euros") or a price written with its unit ("9 dollars", "$9", "€20"). Do **not** infer it from a language, a country, or an address
|
|
100
112
|
— an unrequested switch silently reprices the whole catalog. The seed applies it before
|