@wix/agent-skills 1.20.1 → 1.21.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 (49) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/SKILL.md +14 -112
  3. package/skills/wix-app/references/APP_TOOLS.md +2 -1
  4. package/skills/wix-app/references/BACKEND_EVENT.md +3 -3
  5. package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +41 -152
  6. package/skills/wix-app/references/DASHBOARD_MODAL.md +4 -4
  7. package/skills/wix-app/references/SDK_MODULE_MAP.md +44 -0
  8. package/skills/wix-app/references/SITE_PLUGIN.md +36 -190
  9. package/skills/wix-app/references/app-tools/TOOLS.md +3 -1
  10. package/skills/wix-app/references/custom-element-widget/SETTINGS_PANEL.md +21 -0
  11. package/skills/wix-app/references/dashboard-page/ECOM_NAVIGATION.md +17 -0
  12. package/skills/wix-app/references/dashboard-plugin/SLOTS.md +4 -18
  13. package/skills/wix-app/references/site-plugin/EXAMPLES.md +1 -1
  14. package/skills/wix-headless-fast/SKILL.md +134 -49
  15. package/skills/wix-headless-fast/entry/skill.md +4 -3
  16. package/skills/wix-headless-fast/install/AGENTS.md +32 -0
  17. package/skills/wix-headless-fast/install/agents-md.mjs +39 -0
  18. package/skills/wix-headless-fast/install/attach.mjs +149 -42
  19. package/skills/wix-headless-fast/install/setup.mjs +239 -0
  20. package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +2 -0
  21. package/skills/wix-headless-fast/references/blog/seed/read-site.mjs +22 -0
  22. package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +2 -0
  23. package/skills/wix-headless-fast/references/bookings/seed/read-site.mjs +28 -0
  24. package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +2 -0
  25. package/skills/wix-headless-fast/references/cms/seed/read-site.mjs +20 -0
  26. package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +2 -0
  27. package/skills/wix-headless-fast/references/events/seed/read-site.mjs +26 -0
  28. package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +2 -0
  29. package/skills/wix-headless-fast/references/forms/seed/read-site.mjs +20 -0
  30. package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +2 -0
  31. package/skills/wix-headless-fast/references/members/seed/read-site.mjs +15 -0
  32. package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +2 -0
  33. package/skills/wix-headless-fast/references/portfolio/seed/read-site.mjs +20 -0
  34. package/skills/wix-headless-fast/references/portfolio/seed/seed-portfolio.mjs +19 -21
  35. package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +2 -0
  36. package/skills/wix-headless-fast/references/pricing-plans/seed/read-site.mjs +21 -0
  37. package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +2 -0
  38. package/skills/wix-headless-fast/references/restaurants/seed/read-site.mjs +25 -0
  39. package/skills/wix-headless-fast/references/restaurants/seed/seed-restaurants.mjs +25 -5
  40. package/skills/wix-headless-fast/references/shared/seed/read-site.mjs +99 -0
  41. package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +4 -0
  42. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +7 -2
  43. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +2 -0
  44. package/skills/wix-headless-fast/references/storefront/seed/SEED.md +7 -1
  45. package/skills/wix-headless-fast/references/storefront/seed/read-site.mjs +38 -0
  46. package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +52 -16
  47. package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-create-coupon.md +10 -19
  48. package/skills/wix-manage/references/events/create-wix-event.md +5 -1
  49. package/skills/wix-headless-fast/install/fast-path.mjs +0 -181
@@ -384,19 +384,47 @@ export async function addProductsToCategories(ctx, mapping) {
384
384
  // current revision is read right before the update, so attach any number of times, any pass.
385
385
  // Wix re-hosts each url server-side; the media can take a little while to appear on read-back
386
386
  // (propagation) — normal, not a failure.
387
+ // The bulk update returns 200 on PARTIAL failure, like the bulk create: each item's outcome is
388
+ // in results[].itemMetadata (success, error, originalIndex). Seen live (runs 85 and 86,
389
+ // 2026-09-26): one product of three came back with no media while the call succeeded — most
390
+ // likely its revision moved between the read and the update (variant stocking runs just
391
+ // before). So: pair results to inputs, retry the misses once with fresh revisions, and report
392
+ // what actually persisted. Returns { attached: [id], failures: [{ id, error }] }.
387
393
  // docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/bulk-update-products.md
388
394
  export async function attachProductImages(ctx, items) {
389
- if (!items?.length) return;
390
- const ids = items.map((it) => it.id);
391
- const q = await req(ctx, "/stores/v3/products/query", { body: { query: { filter: { id: { $in: ids } }, paging: { limit: ids.length } } } });
392
- const revById = new Map((q.products ?? []).map((p) => [p.id, p.revision]));
393
- return req(ctx, "/stores/v3/bulk/products/update", {
394
- body: {
395
- products: items.map((it) => ({
396
- product: { id: it.id, revision: revById.get(it.id), media: { itemsInfo: { items: [{ url: it.url, altText: it.altText }] } } },
397
- })),
398
- },
399
- });
395
+ if (!items?.length) return { attached: [], failures: [] };
396
+ const send = async (batch) => {
397
+ const ids = batch.map((it) => it.id);
398
+ const q = await req(ctx, "/stores/v3/products/query", { body: { query: { filter: { id: { $in: ids } }, paging: { limit: ids.length } } } });
399
+ const revById = new Map((q.products ?? []).map((p) => [p.id, p.revision]));
400
+ const r = await req(ctx, "/stores/v3/bulk/products/update", {
401
+ body: {
402
+ products: batch.map((it) => ({
403
+ product: { id: it.id, revision: revById.get(it.id), media: { itemsInfo: { items: [{ url: it.url, altText: it.altText }] } } },
404
+ })),
405
+ },
406
+ });
407
+ const ok = new Set();
408
+ const failed = [];
409
+ for (const x of r.results ?? []) {
410
+ const src = batch[x.itemMetadata?.originalIndex];
411
+ if (!src) continue;
412
+ if (x.itemMetadata?.success) ok.add(src.id);
413
+ else failed.push({ id: src.id, error: x.itemMetadata?.error?.description ?? x.itemMetadata?.error?.code ?? "unknown" });
414
+ }
415
+ // an input with no result at all did not persist either
416
+ for (const it of batch) if (!ok.has(it.id) && !failed.some((f) => f.id === it.id)) failed.push({ id: it.id, error: "no result for item" });
417
+ return { ok, failed };
418
+ };
419
+ const first = await send(items);
420
+ const attached = new Set(first.ok);
421
+ let failures = first.failed;
422
+ if (failures.length) {
423
+ const retry = await send(items.filter((it) => failures.some((f) => f.id === it.id)));
424
+ for (const id of retry.ok) attached.add(id);
425
+ failures = retry.failed;
426
+ }
427
+ return { attached: [...attached], failures };
400
428
  }
401
429
 
402
430
  // Reject plans the API would reject halfway through, while nothing has been created yet —
@@ -491,18 +519,26 @@ export async function setupStore(ctx, { products = [], categories = {}, currency
491
519
  const imageItems = withNames
492
520
  .map((p, i) => (files[i] && p.id ? { id: p.id, url: files[i].url, altText: products[i]?.altText ?? p.slug } : null))
493
521
  .filter(Boolean);
522
+ // imagesAttached counts the attaches the API CONFIRMED (per-item results), not the ones sent;
523
+ // imageFailures names the products left text-only and why. Neither blocks the seed: a re-run of
524
+ // the same plan reuses the products and attaches again.
494
525
  let imagesAttached = 0;
526
+ const imageFailures = [];
527
+ const nameOf = (id) => withNames.find((p) => p.id === id)?.name;
495
528
  try {
496
- if (imageItems.length) await attachProductImages(ctx, imageItems);
497
- imagesAttached = imageItems.length;
498
- } catch {
499
- /* never block on image failure — the products stay text-only */
529
+ if (imageItems.length) {
530
+ const r = await attachProductImages(ctx, imageItems);
531
+ imagesAttached = r.attached.length;
532
+ for (const f of r.failures) imageFailures.push({ name: nameOf(f.id), error: f.error });
533
+ }
534
+ } catch (e) {
535
+ for (const it of imageItems) imageFailures.push({ name: nameOf(it.id), error: e?.message ?? String(e) });
500
536
  }
501
537
 
502
538
  // failures is part of the result, not an exception: a partial seed still leaves a usable
503
539
  // store, and the agent needs the names to report rather than silently shipping a short
504
540
  // catalog. Re-run the seed to retry them — existing names are skipped, not duplicated.
505
- return { products: withNames, categories: cats, imagesAttached, failures };
541
+ return { products: withNames, categories: cats, imagesAttached, imageFailures, failures };
506
542
  }
507
543
 
508
544
  // ---- CLI entry ----------------------------------------------------------------------------------
@@ -47,10 +47,7 @@ Before creating a coupon, check for code conflicts and existing promotions on th
47
47
  "code": "SUMMER20",
48
48
  "percentOffRate": 20,
49
49
  "scope": {
50
- "namespace": "stores",
51
- "group": {
52
- "name": "product"
53
- }
50
+ "namespace": "stores"
54
51
  },
55
52
  "startTime": 1717200000000,
56
53
  "expirationTime": 1719792000000,
@@ -91,10 +88,7 @@ Check for: duplicate codes, overlapping scopes with active coupons, and cross-me
91
88
  "code": "SPRING15",
92
89
  "percentOffRate": 15,
93
90
  "scope": {
94
- "namespace": "stores",
95
- "group": {
96
- "name": "product"
97
- }
91
+ "namespace": "stores"
98
92
  },
99
93
  "startTime": 1714521600000,
100
94
  "usageLimit": 200,
@@ -167,10 +161,7 @@ Check for: duplicate codes, overlapping scopes with active coupons, and cross-me
167
161
  "code": "SAVE10",
168
162
  "moneyOffAmount": 10,
169
163
  "scope": {
170
- "namespace": "stores",
171
- "group": {
172
- "name": "product"
173
- }
164
+ "namespace": "stores"
174
165
  },
175
166
  "startTime": 1714521600000,
176
167
  "active": true
@@ -230,11 +221,11 @@ Instead of targeting a scope, you can require a minimum cart subtotal. This is a
230
221
 
231
222
  ## Scope values for Wix Stores
232
223
 
233
- | Scope target | `namespace` | `group.name` | `group.entityId` |
234
- |---|---|---|---|
235
- | All store products | `"stores"` | `"product"` | Omit (applies to all) |
236
- | Specific product | `"stores"` | `"product"` | Product UUID |
237
- | Specific collection | `"stores"` | `"collection"` | Collection UUID |
224
+ | Scope target | `namespace` | `group` |
225
+ |---|---|---|
226
+ | All store products | `"stores"` | Omit the `group` field entirely — a present-but-empty `group: {}` is rejected the same as a populated one missing `entityId` |
227
+ | Specific product | `"stores"` | `{ "name": "product", "entityId": "<product UUID>" }` |
228
+ | Specific collection | `"stores"` | `{ "name": "collection", "entityId": "<collection UUID>" }` |
238
229
 
239
230
  ---
240
231
 
@@ -246,7 +237,7 @@ When the recommendation output has `mechanism: "COUPON"`, use this mapping to co
246
237
 
247
238
  | Recommendation `scope` | Coupon `scope` |
248
239
  |---|---|
249
- | `SITE` | `{ "namespace": "stores", "group": { "name": "product" } }` (all products, no entityId) |
240
+ | `SITE` | `{ "namespace": "stores" }` (all products — omit `group` entirely) |
250
241
  | `CATEGORY` | `{ "namespace": "stores", "group": { "name": "collection", "entityId": "<first categoryId>" } }` |
251
242
  | `ITEMS` | `{ "namespace": "stores", "group": { "name": "product", "entityId": "<first productId>" } }` |
252
243
 
@@ -340,7 +331,7 @@ When the recommendation output has `mechanism: "COUPON"`, use this mapping to co
340
331
 
341
332
  | Error | Cause | Fix |
342
333
  |---|---|---|
343
- | `"When scope or minimumSubtotal is not used - only FreeShipping coupon is allowed"` | Coupon sent without `scope` or `minimumSubtotal` | Add `scope: { "namespace": "stores", "group": { "name": "product" } }` for site-wide, or set `minimumSubtotal` |
334
+ | `"When scope or minimumSubtotal is not used - only FreeShipping coupon is allowed"` | Coupon sent without `scope` or `minimumSubtotal` | Add `scope: { "namespace": "stores" }` for site-wide (omit `group` — do not send `group: {}`, which is rejected identically), or set `minimumSubtotal` |
344
335
  | Duplicate code | Another coupon uses the same code | Generate a different code |
345
336
  | Invalid startTime | Value too low (must be epoch ms, not seconds) | Multiply by 1000 if in seconds |
346
337
  | Both scope and minimumSubtotal set | These are oneOf — cannot use both | Choose scope OR minimumSubtotal |
@@ -200,7 +200,11 @@ The response is the definition under `ticketDefinition`:
200
200
  Keep `revision` — `PATCH /events/v3/ticket-definitions/{id}` requires the current value, and it
201
201
  increments on every update.
202
202
 
203
- - **`feeType` is required** — `FEE_ADDED_AT_CHECKOUT` or `FEE_INCLUDED`.
203
+ - **`feeType` is required.** A free ticket (`fixedPrice` of `"0"`) takes `NO_FEE`. A paid ticket
204
+ takes `FEE_ADDED_AT_CHECKOUT` (the guest pays the service fee) unless the user asked to absorb it,
205
+ then `FEE_INCLUDED`. `NO_FEE` on a paid ticket is accepted only on sites that do not collect ticket
206
+ fees. If a paid ticket fails with `400 INVALID_FEE_TYPE`, the site is one that does not collect
207
+ ticket fees: retry that ticket once with `NO_FEE`.
204
208
  - **`value` is a string.** `"value": 10` fails `400 Unexpected value for field value`.
205
209
  - **No writable `free` flag** — `pricingMethod.free` is read-only; a free ticket is a `fixedPrice`
206
210
  of `"0"`.
@@ -1,181 +0,0 @@
1
- // Fast path for a managed CREATE run — one deterministic call from "plan exists" to
2
- // "brand layer can start". Composes the pieces that also remain individually runnable
3
- // (deploy.mjs, the vertical's seed module); use those directly to recover a failed step,
4
- // or for connect/iterate runs (which must NOT scaffold and don't use this script).
5
- //
6
- // node <SKILL_ROOT>/install/fast-path.mjs --business-name "<Brand>" --plan plan.json \
7
- // --vertical <storefront|bookings|…> [--stack astro] [--subfolder [--folder-name <npm-safe-name>]]
8
- //
9
- // The project is created in the CURRENT DIRECTORY — the folder the entry had the agent work from,
10
- // which already holds the installed skills (`.agents/skills/`), so the project is self-contained
11
- // and a later session opened in it finds them. `--subfolder` (opt-in) leaves it in a new folder
12
- // named after the business instead, for a current folder that must stay as it is.
13
- //
14
- // It emits ONE JSON event per line and exits in ~35s with BOTH long steps — the dependency
15
- // install AND the seed — running detached in the background (logs + completion markers
16
- // reported in the final event), so the caller can build the brand layer while they finish.
17
- // Steps: scaffold (Wix CLI; requires a logged-in session) → deploy shipped code + deps +
18
- // lockfile → start `npm ci || npm install` detached → start the seed detached.
19
- import { spawn, spawnSync } from "node:child_process";
20
- import { existsSync, openSync, readFileSync, readdirSync, renameSync, rmSync } from "node:fs";
21
- import { dirname, join, resolve } from "node:path";
22
- import { fileURLToPath } from "node:url";
23
-
24
- const SKILL_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
25
-
26
- const emit = (event, extra = {}) => console.log(JSON.stringify({ event, ...extra }));
27
- const fail = (step, detail) => {
28
- emit("error", { step, detail: String(detail).slice(0, 600) });
29
- process.exit(1);
30
- };
31
-
32
- // ---- args ---------------------------------------------------------------------------------------
33
- const argv = process.argv.slice(2);
34
- const flag = (name) => {
35
- const i = argv.indexOf(`--${name}`);
36
- return i !== -1 && argv[i + 1] && !argv[i + 1].startsWith("--") ? argv[i + 1] : null;
37
- };
38
- const businessName = flag("business-name");
39
- const planPath = flag("plan");
40
- const vertical = flag("vertical");
41
- const stack = flag("stack") ?? "astro";
42
- // Opt-in: keep the project in a subfolder instead of the current directory.
43
- const subfolder = argv.includes("--subfolder");
44
- // --vertical is REQUIRED: a defaulted vertical deploys the wrong code and runs the wrong
45
- // seed against the plan — fail loudly with the discovered choices instead.
46
- const knownVerticals = readdirSync(join(SKILL_ROOT, "references"), { withFileTypes: true })
47
- .filter((d) => d.isDirectory() && d.name !== "shared" && existsSync(join(SKILL_ROOT, "references", d.name, "app")))
48
- .map((d) => d.name);
49
- if (!businessName || !planPath || !vertical) {
50
- fail("args", `usage: fast-path.mjs --business-name "<Brand>" --plan plan.json --vertical <${knownVerticals.join("|")}> [--stack astro]`);
51
- }
52
- if (!knownVerticals.includes(vertical)) {
53
- fail("args", `unknown vertical "${vertical}" — shipped verticals: ${knownVerticals.join(", ")}`);
54
- }
55
- if (!existsSync(planPath)) fail("args", `plan file not found: ${planPath}`);
56
- const plan = JSON.parse(readFileSync(planPath, "utf8"));
57
-
58
- const folderName =
59
- flag("folder-name") ??
60
- businessName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
61
- // `let`: the Wix CLI can only scaffold into a subfolder; by default we then move the scaffold up
62
- // into the current directory and repoint projectDir there, so the detached install/seed and every
63
- // reported path use the final location.
64
- let projectDir = resolve(process.cwd(), folderName);
65
-
66
- // ---- 1 · scaffold -------------------------------------------------------------------------------
67
- if (existsSync(join(process.cwd(), "wix.config.json"))) {
68
- fail("scaffold", "the current directory is already a Wix project — this script is for CREATE runs only; use deploy.mjs + the seed module directly");
69
- }
70
- if (existsSync(join(projectDir, "wix.config.json"))) {
71
- emit("scaffold_skipped", { reason: "project already exists", folder: folderName });
72
- } else {
73
- emit("scaffolding", { folder: folderName });
74
- const scaffold = spawnSync(
75
- "npm",
76
- // --skip-git: this wrapper composes its own steps and leaves version control to
77
- // the caller / the enclosing repo; the scaffold's own `git init` + "Initial
78
- // commit" is noise here, and becomes a nested-repo (submodule gitlink) hazard
79
- // if the project is later placed inside an existing repo. --skip-install for the
80
- // same reason: deps install in a detached step below.
81
- ["create", "@wix/new@latest", "--", "headless",
82
- "--folder-name", folderName, "--business-name", businessName,
83
- "--site-template", "--skip-install", "--skip-git", "--no-publish"],
84
- { env: { ...process.env, CI: "1" }, encoding: "utf8", timeout: 300_000 },
85
- );
86
- if (scaffold.status !== 0 || !existsSync(join(projectDir, "wix.config.json"))) {
87
- fail("scaffold", (scaffold.stderr || scaffold.stdout || "scaffold produced no wix.config.json — is the Wix CLI logged in? (npx @wix/cli@latest whoami)").slice(-600));
88
- }
89
- }
90
- const wixConfig = JSON.parse(readFileSync(join(projectDir, "wix.config.json"), "utf8"));
91
- const siteId = wixConfig.siteId ?? wixConfig.projectId;
92
- emit("scaffolded", { folder: folderName, siteId });
93
-
94
- // ---- 2 · deploy shipped code + deps + lockfile ---------------------------------------------------
95
- const deploy = spawnSync(
96
- "node",
97
- [
98
- join(SKILL_ROOT, "install", "deploy.mjs"),
99
- vertical,
100
- "--stack",
101
- stack,
102
- "--plan",
103
- resolve(planPath),
104
- ],
105
- { cwd: projectDir, encoding: "utf8", timeout: 60_000 },
106
- );
107
- if (deploy.status !== 0) fail("deploy", deploy.stderr || deploy.stdout);
108
- let deployResult = {};
109
- try { deployResult = JSON.parse(deploy.stdout); } catch { /* keep going with raw output below */ }
110
- if (deployResult.error) fail("deploy", deployResult.error);
111
- emit("deployed", deployResult);
112
-
113
- // ---- 2c · place the project in the current directory --------------------------------------------
114
- // The default. The Wix CLI scaffolds into a subfolder, so the scaffold is moved up into the current
115
- // directory — the folder that already holds the installed skills — and the subfolder removed. Done
116
- // HERE, before the detached install below, on purpose: no node_modules exists yet, so the move is
117
- // instant and cannot collide with a running install. A pure move: the scaffold was created with
118
- // --skip-git, so there is no nested repo to reconcile — git is whatever the folder already is.
119
- // Refuses rather than overwrite: an entry that already exists in the current directory stops the
120
- // move before anything is touched. `--subfolder` skips this step.
121
- const targetDir = process.cwd();
122
- if (!subfolder && projectDir !== targetDir) {
123
- const entries = readdirSync(projectDir);
124
- const clashes = entries.filter((e) => existsSync(join(targetDir, e)));
125
- if (clashes.length) {
126
- fail("place", `the current directory already has: ${clashes.join(", ")} — run with --subfolder to keep the project in ${folderName}/, or start in an empty folder`);
127
- }
128
- try {
129
- for (const entry of entries) renameSync(join(projectDir, entry), join(targetDir, entry));
130
- rmSync(projectDir, { recursive: true, force: true });
131
- projectDir = targetDir;
132
- emit("project_placed", { into: targetDir });
133
- } catch (e) {
134
- fail("place", e?.stack || e);
135
- }
136
- }
137
-
138
- // ---- 3 · start the dependency install, detached --------------------------------------------------
139
- const installLog = join(projectDir, "npm-install.log");
140
- const logFd = openSync(installLog, "a");
141
- const install = spawn("sh", ["-c", "npm ci --ignore-scripts || npm install --ignore-scripts"], {
142
- cwd: projectDir,
143
- detached: true,
144
- stdio: ["ignore", logFd, logFd],
145
- });
146
- install.unref();
147
- emit("install_started", { log: installLog, doneMarker: "node_modules/.package-lock.json" });
148
-
149
- // ---- 4 · start the seed, detached ----------------------------------------------------------------
150
- // The seed includes a Wix-side provisioning wait of unpredictable length (10-80s); running it
151
- // in the caller's foreground would idle the agent for exactly that long. Detach it like the
152
- // install: result JSON + exit-code marker land as files the caller syncs on before release.
153
- const seedDir = join(SKILL_ROOT, "references", vertical, "seed");
154
- const seedName = existsSync(seedDir)
155
- ? readdirSync(seedDir).find((f) => f.startsWith("seed-") && f.endsWith(".mjs"))
156
- : undefined;
157
- const seedFile = seedName ? join(seedDir, seedName) : null;
158
- if (!seedFile) fail("seed", `no seed module found under ${seedDir}`);
159
- const seedResultFile = join(projectDir, "seed-result.json");
160
- const seedLog = join(projectDir, "seed.log");
161
- const seedDoneMarker = join(projectDir, ".seed-exit");
162
- const planAbs = resolve(planPath);
163
- const seedChild = spawn(
164
- "sh",
165
- ["-c", `node "${seedFile}" "${planAbs}" > seed-result.json 2> seed.log; echo $? > .seed-exit`],
166
- { cwd: projectDir, detached: true, stdio: "ignore" },
167
- );
168
- seedChild.unref();
169
- emit("seeding_started", { vertical, resultFile: seedResultFile, log: seedLog, doneMarker: seedDoneMarker });
170
-
171
- // ---- done ----------------------------------------------------------------------------------------
172
- emit("ready_for_brand_layer", {
173
- projectDir,
174
- siteId,
175
- dashboardUrl: deployResult.dashboardUrl,
176
- productsUrl: deployResult.productsUrl,
177
- categoriesUrl: deployResult.categoriesUrl,
178
- install: { log: installLog, doneMarker: "node_modules/.package-lock.json" },
179
- seed: { resultFile: "seed-result.json", log: "seed.log", doneMarker: ".seed-exit", success: "file contains 0" },
180
- next: "theme SiteLayout + write the home page; then wait for BOTH done markers, verify .seed-exit is 0 (else read seed.log and re-run the seed module), build, release",
181
- });