@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.
- package/package.json +1 -1
- package/skills/wix-app/SKILL.md +14 -112
- package/skills/wix-app/references/APP_TOOLS.md +2 -1
- package/skills/wix-app/references/BACKEND_EVENT.md +3 -3
- package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +41 -152
- package/skills/wix-app/references/DASHBOARD_MODAL.md +4 -4
- package/skills/wix-app/references/SDK_MODULE_MAP.md +44 -0
- package/skills/wix-app/references/SITE_PLUGIN.md +36 -190
- package/skills/wix-app/references/app-tools/TOOLS.md +3 -1
- package/skills/wix-app/references/custom-element-widget/SETTINGS_PANEL.md +21 -0
- package/skills/wix-app/references/dashboard-page/ECOM_NAVIGATION.md +17 -0
- package/skills/wix-app/references/dashboard-plugin/SLOTS.md +4 -18
- package/skills/wix-app/references/site-plugin/EXAMPLES.md +1 -1
- package/skills/wix-headless-fast/SKILL.md +134 -49
- package/skills/wix-headless-fast/entry/skill.md +4 -3
- package/skills/wix-headless-fast/install/AGENTS.md +32 -0
- package/skills/wix-headless-fast/install/agents-md.mjs +39 -0
- package/skills/wix-headless-fast/install/attach.mjs +149 -42
- package/skills/wix-headless-fast/install/setup.mjs +239 -0
- package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +2 -0
- package/skills/wix-headless-fast/references/blog/seed/read-site.mjs +22 -0
- package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +2 -0
- package/skills/wix-headless-fast/references/bookings/seed/read-site.mjs +28 -0
- package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +2 -0
- package/skills/wix-headless-fast/references/cms/seed/read-site.mjs +20 -0
- package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +2 -0
- package/skills/wix-headless-fast/references/events/seed/read-site.mjs +26 -0
- package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +2 -0
- package/skills/wix-headless-fast/references/forms/seed/read-site.mjs +20 -0
- package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +2 -0
- package/skills/wix-headless-fast/references/members/seed/read-site.mjs +15 -0
- package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +2 -0
- package/skills/wix-headless-fast/references/portfolio/seed/read-site.mjs +20 -0
- package/skills/wix-headless-fast/references/portfolio/seed/seed-portfolio.mjs +19 -21
- package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +2 -0
- package/skills/wix-headless-fast/references/pricing-plans/seed/read-site.mjs +21 -0
- package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +2 -0
- package/skills/wix-headless-fast/references/restaurants/seed/read-site.mjs +25 -0
- package/skills/wix-headless-fast/references/restaurants/seed/seed-restaurants.mjs +25 -5
- package/skills/wix-headless-fast/references/shared/seed/read-site.mjs +99 -0
- package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +4 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +7 -2
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +2 -0
- package/skills/wix-headless-fast/references/storefront/seed/SEED.md +7 -1
- package/skills/wix-headless-fast/references/storefront/seed/read-site.mjs +38 -0
- package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +52 -16
- package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-create-coupon.md +10 -19
- package/skills/wix-manage/references/events/create-wix-event.md +5 -1
- 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
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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)
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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 ----------------------------------------------------------------------------------
|
package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-create-coupon.md
CHANGED
|
@@ -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
|
|
234
|
-
|
|
235
|
-
| All store products | `"stores"` | `
|
|
236
|
-
| Specific product | `"stores"` | `"product"
|
|
237
|
-
| Specific collection | `"stores"` | `"collection"
|
|
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"
|
|
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"
|
|
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
|
|
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
|
-
});
|