@wix/agent-skills 1.27.0 → 1.29.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 +2 -2
  2. package/skills/wix-app/SKILL.md +1 -1
  3. package/skills/wix-app/references/APP_VALIDATION.md +5 -1
  4. package/skills/wix-app/references/SDK_MODULE_MAP.md +1 -1
  5. package/skills/wix-app/references/editor-react-component/PARTS.md +1 -1
  6. package/skills/wix-app/references/service-plugin/DISCOUNT-TRIGGERS.md +1 -1
  7. package/skills/wix-app/references/stores/GET_PRODUCT.md +9 -1
  8. package/skills/wix-app/references/stores/INVENTORY.md +2 -0
  9. package/skills/wix-app/references/stores/QUERY.md +2 -2
  10. package/skills/wix-app/references/stores/WRITE.md +1 -1
  11. package/skills/wix-headless/references/astro.md +1 -1
  12. package/skills/wix-headless-kit/SKILL.md +4 -0
  13. package/skills/wix-headless-kit/guides/no-machine.md +115 -0
  14. package/skills/wix-headless-kit/install/attach.mjs +5 -3
  15. package/skills/wix-headless-kit/install/bootstrap.mjs +3 -1
  16. package/skills/wix-headless-kit/install/context.mjs +4 -2
  17. package/skills/wix-headless-kit/install/deploy.mjs +4 -2
  18. package/skills/wix-headless-kit/install/pins.json +8 -0
  19. package/skills/wix-headless-kit/install/setup.mjs +10 -8
  20. package/skills/wix-headless-kit/install/templates.mjs +18 -8
  21. package/skills/wix-headless-templates/donations/INSTRUCTIONS.md +8 -9
  22. package/skills/wix-headless-templates/donations/app/wix/donations/donate.ts +18 -45
  23. package/skills/wix-headless-templates/donations/app/wix/donations/donations-core.ts +18 -19
  24. package/skills/wix-headless-templates/donations/project/src/wix/donations/donate.ts +18 -45
  25. package/skills/wix-headless-templates/donations/project/src/wix/donations/donations-core.ts +18 -19
  26. package/skills/wix-headless-templates/donations/rest/donate.ts +13 -39
  27. package/skills/wix-headless-templates/pricing-plans/INSTRUCTIONS.md +4 -2
  28. package/skills/wix-headless-templates/restaurants/app/wix/restaurants/types.ts +1 -1
  29. package/skills/wix-headless-templates/restaurants/project/src/wix/restaurants/types.ts +1 -1
  30. package/skills/wix-headless-templates/restaurants/seed/SEED.md +4 -4
  31. package/skills/wix-headless-templates/restaurants/seed/seed-restaurants.mjs +1 -1
  32. package/skills/wix-headless-templates/shared/capabilities/site-search/seed/install.mjs +2 -2
  33. package/skills/wix-headless-templates/shared/seed/wix-cli.mjs +6 -3
  34. package/skills/wix-headless-templates/storefront/app/wix/storefront/cart.ts +1 -2
  35. package/skills/wix-headless-templates/storefront/project/src/wix/storefront/cart.ts +1 -2
  36. package/skills/wix-manage/references/bookings/diagnose-availability-issues.md +1 -1
  37. package/skills/wix-manage/references/ecommerce/ecom-shipping.md +2 -2
  38. package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-create-coupon.md +1 -1
  39. package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-create-discount-rule.md +2 -2
  40. package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-flow-bundle-and-save.md +1 -1
  41. package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-flow-seasonal-promotion.md +1 -1
  42. package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-flow-stock-mover.md +2 -2
  43. package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-flow-upsell-boost.md +3 -3
  44. package/skills/wix-manage/references/ecommerce/shipping/ecom-shipping-free-shipping.md +3 -3
  45. package/skills/wix-manage/references/ecommerce/shipping/ecom-shipping-optimize-rates.md +1 -1
  46. package/skills/wix-manage/references/ecommerce/shipping/ecom-shipping-setup-rates.md +3 -3
  47. package/skills/wix-manage/references/sites/upload-static-site.md +61 -42
  48. package/skills/wix-replatform/resources/rp-target-wix/lib/wix-writers.js +1 -1
  49. package/skills/wix-vibe-headless/references/restaurants/seed/seed-restaurants.cjs +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/agent-skills",
3
- "version": "1.27.0",
3
+ "version": "1.29.0",
4
4
  "description": "Wix Skills for AI coding agents — versioned npm distribution of the wix/skills content.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -12,7 +12,7 @@
12
12
  "url": "https://github.com/wix/skills/issues"
13
13
  },
14
14
  "scripts": {
15
- "version": "node .github/scripts/sync-plugin-versions.mjs && git add .claude-plugin/plugin.json .codex-plugin/plugin.json .cursor-plugin/plugin.json plugin.json gemini-extension.json"
15
+ "version": "node .github/scripts/sync-plugin-versions.mjs && git add .claude-plugin/plugin.json .codex-plugin/plugin.json .cursor-plugin/plugin.json plugin.json gemini-extension.json skills/wix-headless-kit/install/pins.json"
16
16
  },
17
17
  "files": [
18
18
  "skills/**",
@@ -492,7 +492,7 @@ Execute these steps sequentially after all implementation is complete. See [APP_
492
492
  1. **Package Installation** — Detect package manager, run install
493
493
  2. **TypeScript Compilation** — `npx tsc --noEmit -p .`
494
494
  3. **Build** — `npx wix build`
495
- 4. **Preview** — `npx wix preview`, in the foreground: it uploads, prints the preview URLs and exits on its own, so no `timeout`, backgrounding or `sleep`
495
+ 4. **Preview** — `npx wix preview`, in the foreground and unpiped: it uploads, prints the preview URLs and exits on its own, so no `timeout`, backgrounding, `sleep` or `| tail`. If your shell moves it to the background anyway, it stalled: read what it printed and `.wix/debug.log` once, then report that the preview did not complete. Don't poll, and don't promise a URL
496
496
 
497
497
  Stop and report errors if any step fails. Check `.wix/debug.log` on failures.
498
498
 
@@ -85,14 +85,18 @@ npx wix build
85
85
 
86
86
  ### Step 4: Preview Deployment
87
87
 
88
- `npx wix preview` is a one-shot command, not a server: it uploads a preview build, prints the preview URLs and **exits on its own**, in seconds. Run it in the foreground and read its output:
88
+ `npx wix preview` is a one-shot command, not a server: it uploads a preview build, prints the preview URLs and **exits on its own**, in seconds. Run it in the foreground, exactly as written, and read its output:
89
89
 
90
90
  ```bash
91
91
  npx wix preview
92
92
  ```
93
93
 
94
+ Don't pipe it (`| tail`, `| head`, `| grep`). The output is short, and a pipe holds back everything until the command exits, so a slow preview shows nothing at all.
95
+
94
96
  Don't wrap it in `timeout` (macOS has none, so the call fails before preview runs), background it, `sleep` or poll a log for it, or `kill` it afterwards. There is no process left to wait for or stop.
95
97
 
98
+ **If it doesn't return:** your shell may move a long call to the background on its own (for example, after a 120s limit). That means the preview stalled, not that it's still working normally. Read the output it left and `.wix/debug.log` **once**. If neither has the preview URLs, report "preview did not complete" with whatever they show and stop validation. Don't sleep, poll, schedule a wake-up or start the preview again, and don't tell the user a URL is coming.
99
+
96
100
  **Success criteria:**
97
101
  - The command exits 0
98
102
  - It printed `✔ Preview created successfully!` and the preview URLs
@@ -15,7 +15,7 @@ Data owned by an existing Wix business app is read and written through that app'
15
15
  | pricing plans / subscriptions | `@wix/pricing-plans` |
16
16
  | bookings / services / staff / time slots | `@wix/bookings` |
17
17
  | calendar events / schedules | `@wix/calendar` |
18
- | table reservations | `@wix/table-reservations` |
18
+ | table reservations | `@wix/table-reservations` (namespace `reservations`) — not `@wix/restaurants` |
19
19
  | restaurant menus / online orders | `@wix/restaurants` |
20
20
  | blog posts | `@wix/blog` |
21
21
  | site events / tickets / RSVPs | `@wix/events` |
@@ -51,7 +51,7 @@ CSS driven by a data attribute, or disable it when it should remain visible.
51
51
  After generating the manifest, confirm that its `elements` include every
52
52
  intended named part.
53
53
 
54
- ## Sanity Check
54
+ ## Plausibility Check
55
55
 
56
56
  For each candidate, ask:
57
57
 
@@ -76,7 +76,7 @@ customTriggers.provideHandlers({
76
76
  1. **Trigger IDs must match** - The `customTriggerId` in `getEligibleTriggers` must match an `_id` from `listTriggers`
77
77
  2. **Both handlers required** - You must implement both `getEligibleTriggers` and `listTriggers`
78
78
  3. **Dynamic eligibility** - `getEligibleTriggers` is called during checkout to determine which triggers are currently active
79
- 4. **Static list** - `listTriggers` provides the master list of all possible triggers for configuration in the Wix dashboard
79
+ 4. **Static list** - `listTriggers` provides the full list of all possible triggers for configuration in the Wix dashboard
80
80
  5. **Manual step required, every time** — this plugin only supplies *eligibility*. It has no visible effect until the merchant creates an Automatic Discount that uses your trigger. Tell the user this step is required; don't imply the discount is live just because the plugin is installed. Confirmed live, click-by-click in the dashboard:
81
81
  1. Go to **Catalog** (left sidebar) → **Discounts** → **Automatic Discounts**.
82
82
  2. Click **Create Discount**.
@@ -4,13 +4,21 @@
4
4
 
5
5
  ```typescript
6
6
  if (v === 'V3_CATALOG') {
7
- const product = await productsV3.getProduct(id); // returns Product directly
7
+ const product = await productsV3.getProduct(id); // returns V3Product directly
8
8
  return product;
9
9
  }
10
10
  const { product } = await products.getProduct(id); // V1 wraps in { product }
11
11
  return product;
12
12
  ```
13
13
 
14
+ The V3 entity type is `productsV3.V3Product`.
15
+
16
+ ---
17
+
18
+ ## Product page URL
19
+
20
+ V3 `product.url` is populated only when the request's `fields` includes `'URL'` — `getProduct(id, { fields: ['URL'] })`, or the direct-call `searchProducts` / `queryProducts(query, { fields: ['URL'] })` (the fluent builder's `.find()` takes no options). V1 `productPageUrl` is an object, `{ base, path }`, not a string.
21
+
14
22
  ---
15
23
 
16
24
  ## Read product options and choices (V1 vs V3)
@@ -18,6 +18,8 @@ if (v === 'V3_CATALOG') {
18
18
  }
19
19
  ```
20
20
 
21
+ V3 increments work only for inventory items with `trackQuantity: true`; the entries are `{ inventoryItemId, incrementBy }` with `incrementBy` 1 to 99999.
22
+
21
23
  To find a V3 inventory item ID, use `inventoryItemsV3.searchInventoryItems` filtered by `productId` / `variantId`.
22
24
 
23
25
  ---
@@ -8,7 +8,7 @@ Both versions expose a fluent query builder, but the paging method differs:
8
8
  |--------|--------------------------------|----------------------------------|
9
9
  | API style | Fluent builder: `.skip().limit().find()` | Fluent builder: `.skipTo(cursor).limit().find()` |
10
10
  | Pagination | Offset (`.skip(n)`) | Cursor (`.skipTo(cursor)`) |
11
- | Result `items` | `res.items` (V1 `Product[]`) | `res.items` (V3 `Product[]`) |
11
+ | Result `items` | `res.items` (V1 `Product[]`) | `res.items` (V3 `productsV3.V3Product[]`) |
12
12
  | Total count | `res.totalCount` | **None** — V3 only has `cursors.next` + `hasNext()` |
13
13
  | `hasNext` | `res.hasNext()` (method) | `res.hasNext()` (method) |
14
14
  | Next cursor | n/a | `res.cursors.next` (string) |
@@ -35,7 +35,7 @@ export async function listProductsPage(
35
35
  if (v === 'V3_CATALOG') {
36
36
  let builder = productsV3.queryProducts().limit(limit);
37
37
  if (typeof cursorOrSkip === 'string') builder = builder.skipTo(cursorOrSkip);
38
- // Do NOT chain a sort — see OVERVIEW.md gotcha #10.
38
+ // Do NOT chain a sort — see [STORES_VERSIONING.md](../STORES_VERSIONING.md) gotcha #10.
39
39
  const res = await builder.find();
40
40
  return {
41
41
  products: res.items,
@@ -32,7 +32,7 @@ return product;
32
32
 
33
33
  ```typescript
34
34
  if (v === 'V3_CATALOG') {
35
- // Signature: updateProduct(_id, productFields). Returns Product directly.
35
+ // Signature: updateProduct(_id, productFields). Returns V3Product directly.
36
36
  const current = await productsV3.getProduct(id);
37
37
  if (current.revision == null) throw new Error(`Product ${id} has no revision`);
38
38
  return await productsV3.updateProduct(id, {
@@ -81,7 +81,7 @@ The **per-vertical values** — which `WIX_APPS.*PageMetadata` accessor + `seoTa
81
81
 
82
82
  ## 7 — Caveats (the gaps the docs don't mention)
83
83
 
84
- The heart of the file: tribal knowledge the docs won't surface. These are **guidance the model must follow when it writes the frontend** — not things this skill writes for it.
84
+ The heart of the file: hard-won knowledge the docs won't surface. These are **guidance the model must follow when it writes the frontend** — not things this skill writes for it.
85
85
 
86
86
  | Caveat | What it says |
87
87
  |---|---|
@@ -302,6 +302,10 @@ browser versus the server, the OAuth allow-list for a self-hosted origin, pre-re
302
302
  how to close such a run. A public site is still better served by managed Astro; say so when you
303
303
  close.
304
304
 
305
+ Without a machine at all, or when the install, the CLI or the login is blocked where you are,
306
+ read `<SKILL_ROOT>/guides/no-machine.md`: the same run, step by step, as the Wix API calls the
307
+ scripts make and the files beside this skill that carry the contracts.
308
+
305
309
  ## Verticals
306
310
 
307
311
  The shortlist. Match the brief against the first column; when it names a Wix product or a
@@ -0,0 +1,115 @@
1
+ # No machine: the kit's run through the Wix APIs
2
+
3
+ Read when you cannot run the commands this skill is built on: no shell, no file system, no Node, a
4
+ sandbox that blocks the install, the CLI or the login, or a host that only talks to Wix through
5
+ the Wix MCP. Nothing in this guide installs, downloads or runs anything. It walks the run of
6
+ SKILL.md step by step and names, for each, the Wix API call the CLI or the script performs, and
7
+ the file beside this skill that carries the contract.
8
+
9
+ ## Where these skills are
10
+
11
+ This skill is one of a set, and the guide reads from three of them:
12
+
13
+ - `<SKILL_ROOT>` — this skill, `wix-headless-kit`: the run, its guides, the Verticals table.
14
+ - `<TEMPLATES>` — `wix-headless-templates`: the shipped, verified code per Wix Business Solution,
15
+ one folder each, with its `INSTRUCTIONS.md` (the contracts), `seed/` (the seed script and
16
+ `SEED.md`) and `rest/` (the data layer over plain `fetch`). Installed beside this skill, or
17
+ fetched to `<SKILL_ROOT>/templates/`.
18
+ - `<MANAGE>` — `wix-manage`: REST recipes to configure and manage a site's business solutions, one
19
+ recipe per operation with the exact endpoint and payload; its `SKILL.md` is the index.
20
+ - `<DOCS>` — `wix-docs`: how to look up the Wix API and SDK documentation and confirm a method's
21
+ exact shape before writing a call.
22
+
23
+ They install together and sit beside each other. When you are reading this skill online rather
24
+ than from an install, the others are published alongside it, by those names, and each skill's
25
+ manifest lists its files. Read each file in full: a fetch tool that summarizes drops the exact
26
+ calls and shapes; a tool that returns the file's text, or one that runs code and can fetch a URL,
27
+ does not.
28
+
29
+ Two things such a run does not produce, and never claims: a frontend built from the shipped `app/`
30
+ code (that needs a build), and a release of one. Everything else the kit does to a site, it does
31
+ through calls you can make.
32
+
33
+ ## 0. Identity
34
+
35
+ Every call below runs as the site owner, with whatever identity your host gives you: the Wix
36
+ MCP's `ExecuteWixAPI` carries the user's login; an API key from the user's account goes in the
37
+ `Authorization` header of any call, as `<MANAGE>/SKILL.md` describes; a site token from the Wix CLI
38
+ works the same way when the user runs the CLI for you. The site's OAuth app (`client_id`) mints
39
+ visitor tokens only, which cover what a visitor may see and nothing here.
40
+
41
+ ## 1. The brief → the solutions
42
+
43
+ Unchanged from SKILL.md step 2 and its Verticals table: the brief names the business, the table
44
+ names the solutions it needs. Read each solution's `<TEMPLATES>/<solution>/INSTRUCTIONS.md` for
45
+ what it covers and `<TEMPLATES>/<solution>/seed/SEED.md` for what a seeded site holds.
46
+
47
+ ## 2. The site
48
+
49
+ The CLI's `npm create @wix/new` is one call: `POST /headless-business-setup/v1/headless-business/provision`,
50
+ with `origin: agent-<your-id>`, a name, and the solutions to install as `seedOptions`. The recipe
51
+ is `<MANAGE>/references/sites/create-headless-site.md`; it returns the `metaSiteId` and the
52
+ site's OAuth client `appId`. Do not create a site any other way: a project from Create Project or
53
+ a template is neither headless nor publishable, and a later run with a machine cannot attach to it.
54
+
55
+ A site that already exists (the brief names it) is read first, never seeded unasked: SKILL.md
56
+ step 2 and `<SKILL_ROOT>/guides/existing-site.md` apply as written.
57
+
58
+ ## 3. The apps
59
+
60
+ A solution the provision call did not install, or a capability's app (Members Area, Forms,
61
+ Restaurants, Donations, FAQ), goes on with the Apps Installer:
62
+ `<MANAGE>/references/app-installation/install-wix-apps.md`. The app ids are in each
63
+ `seed-<solution>.mjs`, which installs them before seeding.
64
+
65
+ ## 4. The seed
66
+
67
+ `seed-<solution>.mjs` is a sequence of REST calls, and `SEED.md` is its contract: the plan shape
68
+ (what to create, how many, which images), the order the entities need, and the traps the script
69
+ encodes (a Bookings service needs a category to be visible and takes resource ids, not staff ids;
70
+ a Forms field is registered by its `validation` block; a product's choice photos are linked after
71
+ the gallery holds them). Read `SEED.md`, read the script for any call `SEED.md` only names, and make the same
72
+ calls through `<MANAGE>`'s recipe for each (services, products, posts, events, collections). Keep
73
+ the script's rules: create, never delete, and report what the site already held. Image prompts
74
+ need the Media Manager; without it, products and services stay text-only, say so.
75
+
76
+ ## 5. The frontend
77
+
78
+ The shipped `app/` code cannot be built here, and the Astro pages cannot be released. A page can
79
+ still be shipped: one self-contained `index.html` that loads the Wix SDK from a package CDN and
80
+ talks to the site as a visitor, dropped onto the site from step 2 through
81
+ `<MANAGE>/references/sites/upload-static-site.md`. The kit's own frontend for this site is a run
82
+ with a machine, which attaches to this site (`guides/existing-site.md`) rather than making a new one.
83
+
84
+ **The page.** One file, a few hundred lines, written in full before the first drop: the drop takes
85
+ the complete file set in one call, and a response has a hard size limit, so there is no second
86
+ pass. The SDK comes from a pinned package URL, `https://esm.sh/@wix/sdk@<version>` and the
87
+ solution's package (`@wix/bookings`, `@wix/stores`, `@wix/blog`, …); the client is
88
+ `createClient({ modules: { … }, auth: OAuthStrategy({ clientId }) })` with the `appId` the provision
89
+ call returned, and the SDK mints and refreshes the visitor token itself. Markup, a render function
90
+ per state, the event handlers: that is what you write. Keep what the page shows to what the site
91
+ holds; nothing invented, as in SKILL.md step 4.
92
+
93
+ **The calls.** Take every SDK call's shape from the kit's own transport for the solution,
94
+ `<TEMPLATES>/<solution>/app/wix/<solution>/*.ts` (`services.ts`, `booking.ts`, `catalog.ts`, …,
95
+ with `<TEMPLATES>/shared/app/wix/sdk.ts` for the client wiring): those files call the same SDK
96
+ modules the page imports, and they are verified. Copy the form of the call, not the file. The
97
+ cores beside them (`*-core.ts`) hold the rules the page needs and nothing else does: which fields
98
+ to read, how a price or a duration is formatted, which slot is bookable, how an id is read.
99
+
100
+ **A call those files don't cover** is confirmed before it is written, the way `<DOCS>` describes:
101
+ the method's SDK page, and when the page and the package disagree, the package's own type
102
+ declarations (the `@wix/auto_sdk_<solution>_<module>` package the solution package depends on,
103
+ its `index.d.ts`) are the truth. Two shapes that recur, as examples of what the types settle and
104
+ the kit's files already encode: SDK query methods return a query builder finished with `.find()`;
105
+ and an entity's id arrives as `_id` on some objects and `id` on others (the kit reads both,
106
+ `rawId`). Dates travel as local
107
+ wall-clock strings in the business time zone. A page cannot be run here, so a wrong shape fails
108
+ silently in the browser; the file beside the skill is the check that is available.
109
+
110
+ ## 6. Closing
111
+
112
+ As in SKILL.md step 5, minus the live URL when nothing was released: what exists on the site, by
113
+ name; the dashboard link `https://manage.wix.com/dashboard/<metaSiteId>` and each solution's own
114
+ pages from its INSTRUCTIONS.md table; what is placeholder; payments, when the site takes money;
115
+ and what this run could not do, with the site id, so a run with a machine continues here.
@@ -44,7 +44,7 @@
44
44
  // business instead.
45
45
  //
46
46
  // Emits ONE JSON event per line (attached, scaffolded, deployed, install_started,
47
- // ready_for_brand_layer, or error). Requires a logged-in Wix CLI (`npx @wix/cli@latest whoami`)
47
+ // ready_for_brand_layer, or error). Requires a logged-in Wix CLI (`npx @wix/cli@<pinned> whoami`, the version in pins.json)
48
48
  // whose account owns or co-manages the site.
49
49
  import { spawn, spawnSync, execFileSync } from "node:child_process";
50
50
  import { cpSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync, readdirSync } from "node:fs";
@@ -53,6 +53,8 @@ import { fileURLToPath } from "node:url";
53
53
  import { writeAgentsMd } from "./agents-md.mjs";
54
54
  import { frontendPresent, siteContext } from "./context.mjs";
55
55
  import { listVerticals, templatesDir } from "./templates.mjs";
56
+ const PINS = JSON.parse(readFileSync(new URL("./pins.json", import.meta.url), "utf8"));
57
+ const WIX_CLI = `@wix/cli@${PINS["@wix/cli"]}`;
56
58
 
57
59
  const SKILL_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
58
60
  const MANAGE = "https://manage.wix.com";
@@ -135,9 +137,9 @@ emit("folder", { mode, stack, hosting: hosting0, project: hasProject, config: cw
135
137
 
136
138
  // ---- http ---------------------------------------------------------------------------------------
137
139
  const cliToken = (site) => {
138
- const r = spawnSync("npx", ["-y", "@wix/cli@latest", "token", ...(site ? ["--site", site] : [])], { encoding: "utf8", timeout: 120_000 });
140
+ const r = spawnSync("npx", ["-y", WIX_CLI, "token", ...(site ? ["--site", site] : [])], { encoding: "utf8", timeout: 120_000 });
139
141
  const t = (r.stdout || "").trim();
140
- if (r.status !== 0 || !t) fail("auth", (r.stderr || r.stdout || "no token — is the Wix CLI logged in? (npx @wix/cli@latest whoami)").slice(-400));
142
+ if (r.status !== 0 || !t) fail("auth", (r.stderr || r.stdout || `no token — is the Wix CLI logged in? (npx ${WIX_CLI} whoami)`).slice(-400));
141
143
  return t;
142
144
  };
143
145
  async function call(base, path, { method = "POST", token, site, body, query } = {}) {
@@ -3,6 +3,8 @@ import { spawn, spawnSync } from 'node:child_process';
3
3
  import { existsSync, mkdirSync, openSync, readFileSync, writeFileSync } from 'node:fs';
4
4
  import { tmpdir } from 'node:os';
5
5
  import { join } from 'node:path';
6
+ const PINS = JSON.parse(readFileSync(new URL("./pins.json", import.meta.url), "utf8"));
7
+ const WIX_CLI = `@wix/cli@${PINS["@wix/cli"]}`;
6
8
 
7
9
  // ── tiny event protocol (one JSON object per line) ───────────────────────────
8
10
  const emit = (event, extra = {}) =>
@@ -15,7 +17,7 @@ const fail = (event, extra = {}) => {
15
17
  // ── platform-safe binary names (npm/npx are .cmd on Windows) ─────────────────
16
18
  const isWin = process.platform === 'win32';
17
19
  const bin = (name) => (isWin ? `${name}.cmd` : name);
18
- const WIX = [bin('npx'), '-y', '@wix/cli@latest']; // run the CLI via npx — no global install/mutation
20
+ const WIX = [bin('npx'), '-y', WIX_CLI]; // run the CLI via npx — no global install/mutation
19
21
 
20
22
  // Force the CLI into non-interactive "agent" mode. Without an agent signal in
21
23
  // the env, `wix login` renders an interactive Ink TUI (device code + keypress)
@@ -19,6 +19,8 @@
19
19
  import { spawnSync } from "node:child_process";
20
20
  import { existsSync, readFileSync } from "node:fs";
21
21
  import { join } from "node:path";
22
+ const PINS = JSON.parse(readFileSync(new URL("./pins.json", import.meta.url), "utf8"));
23
+ const WIX_CLI = `@wix/cli@${PINS["@wix/cli"]}`;
22
24
 
23
25
  // The variables the editor-to-headless migration writes into the child project's `prod`
24
26
  // environment (headless-bo, editorMigrations/environmentMetadata.ts), which `wix env pull` copies
@@ -57,7 +59,7 @@ export function readWixConfig(cwd) {
57
59
  try { return JSON.parse(readFileSync(file, "utf8")); } catch { return null; }
58
60
  }
59
61
 
60
- const ENV_PULL = ["-y", "@wix/cli@latest", "env", "pull"];
62
+ const ENV_PULL = ["-y", WIX_CLI, "env", "pull"];
61
63
  const runPull = (dir) => spawnSync("npx", ENV_PULL, { cwd: dir, env: { ...process.env, CI: "1" }, encoding: "utf8", timeout: 180_000 });
62
64
 
63
65
  /**
@@ -70,7 +72,7 @@ export function pullEnv(cwd) {
70
72
  const envFile = join(cwd, ".env.local");
71
73
  const r = runPull(cwd);
72
74
  if (r.status === 0 && existsSync(envFile)) return { ok: true, via: "in place" };
73
- return { ok: false, error: (r.stderr || r.stdout || "env pull produced no .env.local — is the Wix CLI logged in? (npx @wix/cli@latest whoami)").trim().slice(-400) };
75
+ return { ok: false, error: (r.stderr || r.stdout || `env pull produced no .env.local — is the Wix CLI logged in? (npx ${WIX_CLI} whoami)`).trim().slice(-400) };
74
76
  }
75
77
 
76
78
  /**
@@ -46,6 +46,8 @@ import { fileURLToPath } from "node:url";
46
46
  import { templatesDir } from "./templates.mjs";
47
47
  import { syncLockRoot } from "./lock.mjs";
48
48
  import { siteContext } from "./context.mjs";
49
+ const PINS = JSON.parse(readFileSync(new URL("./pins.json", import.meta.url), "utf8"));
50
+ const WIX_CLI = `@wix/cli@${PINS["@wix/cli"]}`;
49
51
 
50
52
  const SKILL_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
51
53
  // The shipped code: the repository's templates/ (a checkout, the cache, or fetched now).
@@ -275,7 +277,7 @@ if (stack === "static") {
275
277
  // .env.local (`wix env pull`; on a migration preview the parent site's app), else the config's appId.
276
278
  let clientId = clientIdFlag ?? (existsSync(join(PROJECT, "wix.config.json")) ? siteContext({ cwd: PROJECT }).content.clientId : null);
277
279
  if (!clientId) {
278
- console.log(JSON.stringify({ error: "static stack needs the public OAuth client id — run `npm create @wix/new@latest init` here first, or pass --client-id" }));
280
+ console.log(JSON.stringify({ error: `static stack needs the public OAuth client id — run \`npm create @wix/new@${PINS["@wix/create-new"]} init\` here first, or pass --client-id` }));
279
281
  process.exit(1);
280
282
  }
281
283
  const missing = requested.filter((v) => !existsSync(join(REF, v, "rest")));
@@ -336,7 +338,7 @@ if (stack === "static") {
336
338
  const sources = readdirSync(JS).filter((f) => f.endsWith(".ts")).map((f) => join(JS, f));
337
339
  const tsc = spawnSync(
338
340
  "npx",
339
- ["-y", "-p", "typescript@5", "tsc", ...sources, "--outDir", JS, "--module", "esnext", "--target", "es2022",
341
+ ["-y", "-p", `typescript@${PINS.typescript}`, "tsc", ...sources, "--outDir", JS, "--module", "esnext", "--target", "es2022",
340
342
  "--moduleResolution", "bundler", "--lib", "es2022,dom", "--strict", "--skipLibCheck", "--removeComments", "false"],
341
343
  { encoding: "utf8", timeout: 180_000 },
342
344
  );
@@ -0,0 +1,8 @@
1
+ {
2
+ "@wix/cli": "1.1.258",
3
+ "@wix/create-new": "0.0.124",
4
+ "typescript": "5.8.3",
5
+ "templates": {
6
+ "ref": "v1.29.0"
7
+ }
8
+ }
@@ -37,6 +37,8 @@ import { fileURLToPath } from "node:url";
37
37
  import { AGENT_CONFIG_FILES, writeAgentsMd } from "./agents-md.mjs";
38
38
  import { listVerticals, templatesDir } from "./templates.mjs";
39
39
  import { folderShape, siteContext } from "./context.mjs";
40
+ const PINS = JSON.parse(readFileSync(new URL("./pins.json", import.meta.url), "utf8"));
41
+ const WIX_CLI = `@wix/cli@${PINS["@wix/cli"]}`;
40
42
 
41
43
  const SKILL_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
42
44
 
@@ -134,14 +136,14 @@ if (mode === "create") {
134
136
  // commit" is noise here, and becomes a nested-repo (submodule gitlink) hazard
135
137
  // if the project is later placed inside an existing repo. --skip-install for the
136
138
  // same reason: deps install in a detached step below.
137
- ["create", "@wix/new@latest", "--", "headless",
139
+ ["create", `@wix/new@${PINS["@wix/create-new"]}`, "--", "headless",
138
140
  "--folder-name", folderName, "--business-name", businessName,
139
141
  "--site-template", "blank", ...(template ? ["--template-path", template] : []),
140
142
  "--skip-install", "--skip-git", "--no-publish"],
141
143
  { env: { ...process.env, CI: "1" }, encoding: "utf8", timeout: 300_000 },
142
144
  );
143
145
  if (scaffold.status !== 0 || !existsSync(join(projectDir, "wix.config.json"))) {
144
- 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));
146
+ fail("scaffold", (scaffold.stderr || scaffold.stdout || `scaffold produced no wix.config.json — is the Wix CLI logged in? (npx ${WIX_CLI} whoami)`).slice(-600));
145
147
  }
146
148
  }
147
149
  } else if (mode === "migrate") {
@@ -177,10 +179,10 @@ if (mode === "create") {
177
179
  // wix.config.json and .env.local, touches nothing else. Non-interactive under CI=1; the site is
178
180
  // named after the folder (rename it in the dashboard).
179
181
  emit("adopting", { folder: cwd });
180
- const init = spawnSync("npm", ["create", "@wix/new@latest", "--", "init"],
182
+ const init = spawnSync("npm", ["create", `@wix/new@${PINS["@wix/create-new"]}`, "--", "init"],
181
183
  { cwd, env: { ...process.env, CI: "1" }, encoding: "utf8", timeout: 300_000 });
182
184
  if (init.status !== 0 || !has("wix.config.json")) {
183
- fail("init", (init.stderr || init.stdout || "init produced no wix.config.json — is the Wix CLI logged in? (npx @wix/cli@latest whoami)").slice(-600));
185
+ fail("init", (init.stderr || init.stdout || `init produced no wix.config.json — is the Wix CLI logged in? (npx ${WIX_CLI} whoami)`).slice(-600));
184
186
  }
185
187
  }
186
188
  const wixConfig = JSON.parse(readFileSync(join(projectDir, "wix.config.json"), "utf8"));
@@ -296,10 +298,10 @@ if (planPath && madeTheSite) {
296
298
 
297
299
  // ---- done ----------------------------------------------------------------------------------------
298
300
  const release = {
299
- astro: "npx @wix/cli@latest build, then npx @wix/cli@latest release",
300
- react: "the project's own build, then npx @wix/cli@latest release of the build folder named in wix.config.json (what Wix hosting serves and how routes must be shaped: SKILL.md step 1)",
301
- lib: "the project's own build, then npx @wix/cli@latest release of the build folder named in wix.config.json (SKILL.md step 1)",
302
- static: `npx @wix/cli@latest release — no build; it uploads ${STATIC_OUT}/ whole (wix.config.json site.outputDirectory), so the pages, styles and assets move into ${STATIC_OUT}/ first and import the modules from ./js/wix/ there; the root keeps the config, the plan, the seed output and the skills`,
301
+ astro: `npx ${WIX_CLI} build, then npx ${WIX_CLI} release`,
302
+ react: `the project's own build, then npx ${WIX_CLI} release of the build folder named in wix.config.json (what Wix hosting serves and how routes must be shaped: SKILL.md step 1)`,
303
+ lib: `the project's own build, then npx ${WIX_CLI} release of the build folder named in wix.config.json (SKILL.md step 1)`,
304
+ static: `npx ${WIX_CLI} release — no build; it uploads ${STATIC_OUT}/ whole (wix.config.json site.outputDirectory), so the pages, styles and assets move into ${STATIC_OUT}/ first and import the modules from ./js/wix/ there; the root keeps the config, the plan, the seed output and the skills`,
303
305
  }[stack];
304
306
  // On a migration preview the links and `siteId` are the PARENT's — the site whose content the pages
305
307
  // show and whose dashboard manages it; `deploySiteId` is where `wix release` goes.
@@ -11,9 +11,13 @@
11
11
  // 2. the cache `<SKILL_ROOT>/templates/`, filled by an earlier call (`--refresh` refetches).
12
12
  // 3. a fetch: a sparse, shallow clone of `skills/wix-headless-templates/` from the repository the skill was installed
13
13
  // from (skills-lock.json's `source`, default wix/skills), at the branch or tag the install
14
- // named (its `ref`, falling back to the default branch when that ref no longer exists) or the
15
- // repository's default branch. The lock records no commit, so there is nothing more exact to
16
- // pin to; `WIX_HEADLESS_FAST_TEMPLATES_REF=<branch|tag|sha>` overrides the ref.
14
+ // named (its `ref`, falling back to the default branch when that ref no longer exists), else
15
+ // the repository's default branch: a skill installed from the repository tracks the repository,
16
+ // so the templates match the kit beside them. A kit that came from a package (no skills-lock.json
17
+ // above it) fetches at the release tag in `install/pins.json` (`templates.ref`, kept equal to the
18
+ // package version by the release flow), the code it was released with; the package carries the
19
+ // templates skill beside the kit anyway, so that fetch is the rare path.
20
+ // `WIX_HEADLESS_FAST_TEMPLATES_REF=<branch|tag|sha>` overrides all of it.
17
21
  // The cache stays with the project: its `.gitignore` leaves out only the composed `project/`
18
22
  // folders (the scaffolds with their lockfiles, read once, at create or attach) and the repository
19
23
  // tooling, so the code layers, playbooks, seeds and readers are committed at the commit the project
@@ -29,6 +33,8 @@ export const SKILL_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..")
29
33
  export const DEFAULT_REPO = "https://github.com/wix/skills.git";
30
34
  /** The templates skill's folder in the repository. */
31
35
  export const TEMPLATES_PATH = "skills/wix-headless-templates";
36
+ /** Pinned versions, shared by the install scripts; `templates.ref` is the release tag the fetch defaults to. */
37
+ export const PINS = JSON.parse(readFileSync(new URL("./pins.json", import.meta.url), "utf8"));
32
38
 
33
39
  const git = (args, opts = {}) => spawnSync("git", args, { encoding: "utf8", timeout: 180_000, ...opts });
34
40
 
@@ -46,7 +52,7 @@ export function installSource() {
46
52
  if (typeof entry?.source === "string" && entry.source) {
47
53
  const parsed = parseSource(entry.source);
48
54
  // `ref` is the branch or tag the install named (skills-lock.json v1 keeps it beside `source`)
49
- return { ...parsed, ref: parsed.ref ?? (typeof entry.ref === "string" && entry.ref ? entry.ref : null) };
55
+ return { ...parsed, ref: parsed.ref ?? (typeof entry.ref === "string" && entry.ref ? entry.ref : null), fromLock: true };
50
56
  }
51
57
  } catch { /* fall through to the default */ }
52
58
  break;
@@ -55,7 +61,7 @@ export function installSource() {
55
61
  if (up === dir) break;
56
62
  dir = up;
57
63
  }
58
- return { repo: DEFAULT_REPO, ref: null };
64
+ return { repo: DEFAULT_REPO, ref: null, fromLock: false };
59
65
  }
60
66
 
61
67
  function parseSource(src) {
@@ -112,15 +118,19 @@ export function templatesSource(dir) {
112
118
  }
113
119
 
114
120
  function fetchTemplates(cache) {
115
- const { repo, ref: lockRef } = installSource();
121
+ const { repo, ref: lockRef, fromLock } = installSource();
116
122
  const envRef = process.env.WIX_HEADLESS_FAST_TEMPLATES_REF || null;
117
- let ref = envRef || lockRef || null;
123
+ // Installed from the repository (a lock above the skill) and no ref named: the default branch, so
124
+ // the templates match the kit. No lock at all is a package install: the release tag it shipped with.
125
+ const pinnedRef = fromLock ? null : (PINS.templates?.ref || null);
126
+ let ref = envRef || lockRef || pinnedRef || null;
118
127
  let r = cloneSparse(repo, ref);
119
128
  // The branch the skill was installed from can be gone by the time the code is first needed
120
129
  // (merged and deleted). The lock's ref then falls back to the default branch; an explicit env
121
130
  // ref does not.
122
131
  let fellBack = false;
123
132
  if (r.status !== 0 && ref && !envRef && /not found|couldn't find remote ref|unknown revision/i.test(r.stderr || "")) {
133
+ // (the pinned tag, too: a fork or a pre-release checkout may not carry it)
124
134
  rmSync(r.tmp, { recursive: true, force: true });
125
135
  fellBack = true; ref = null;
126
136
  r = cloneSparse(repo, null);
@@ -141,7 +151,7 @@ function fetchTemplates(cache) {
141
151
  catch { cpSync(join(tmp, TEMPLATES_PATH), cache, { recursive: true }); }
142
152
  rmSync(tmp, { recursive: true, force: true });
143
153
  writeFileSync(join(cache, ".gitignore"), IGNORED.join("\n") + "\n");
144
- writeFileSync(join(cache, ".source"), JSON.stringify({ repo, ref: ref ?? (fellBack ? `default branch (${lockRef} not found)` : "default branch"), commit, fetchedAt: new Date().toISOString() }, null, 2) + "\n");
154
+ writeFileSync(join(cache, ".source"), JSON.stringify({ repo, ref: ref ?? (fellBack ? `default branch (${lockRef || pinnedRef} not found)` : "default branch"), commit, fetchedAt: new Date().toISOString() }, null, 2) + "\n");
145
155
  }
146
156
 
147
157
  // A sparse, shallow clone of the templates skill's folder at a ref (branch, tag, or commit id) into a temp dir.
@@ -6,8 +6,9 @@ typed end-to-end. **The presentation is yours**: you design and implement the ca
6
6
  campaigns index, the campaign page's layout around the shipped form, the home page and the brand. You
7
7
  never write checkout logic; you never skip designing.
8
8
 
9
- A donation is an eCom checkout with one line that references the campaign; Wix's hosted checkout takes
10
- the payment and the donor's details, and the eCom order is the receipt. The campaign entity carries
9
+ A donation is an eCom cart (Cart V2) with one line that references the campaign; in V2 the cart IS the
10
+ checkout, so the cart's id is the id the redirect session takes — there is no separate checkout entity.
11
+ Wix's hosted checkout takes the payment and the donor's details, and the eCom order is the receipt. The campaign entity carries
11
12
  **no description and no slug**: the story on a campaign page is the site's own copy, and routes key by
12
13
  the campaign id (`/donate/<id>`).
13
14
 
@@ -29,7 +30,7 @@ Astro markup), your campaign page layout, and your home page.
29
30
  | `wix/media.ts` · `wix/money.ts` | `imgAttrs(url, sizes, ratio)` — every `<img>` attribute for a DTO image: `<img {...imgAttrs(c.imageUrl, "50vw", 9 / 16)} alt={c.name} />`; `imgSrc()` / `imgSrcSet()` underneath |
30
31
  | `wix/donations/types.ts` | the DTOs (`CampaignSummary`, `CampaignDetail`, `GoalProgress`, `DonationOptions`, `DonationReceipt`) — contracts inlined below |
31
32
  | `wix/donations/campaigns.ts` | `fetchCampaigns`, `fetchCampaign(id)`, `fetchDefaultCampaign` — the transport; the rules and DTO mappers are in `donations-core.ts` beside it (shared with the REST layer) |
32
- | `wix/donations/donate.ts` | `donationCheckoutUrl(campaignId, input)` — the checkout line + hosted redirect session; `fetchDonationReceipt(orderId)` — the thank-you page's order read; bodies in `donations-core.ts` |
33
+ | `wix/donations/donate.ts` | `donationCheckoutUrl(campaignId, input)` — the donation cart + hosted redirect session; `fetchDonationReceipt(orderId)` — the thank-you page's order read; bodies in `donations-core.ts` |
33
34
  | `wix/donations/donation-store.ts` · `campaigns-store.ts` · `receipt-store.ts` | the form, listing and receipt state machines, framework-free (`createDonationStore(campaign)`, `createCampaignsStore()`, `createReceiptStore()` — `getState`/`subscribe` + actions, one instance per surface); the hooks below bind them to React, every other stack uses them directly |
34
35
  | `hooks/donations/useDonation.ts` | React binding of `donation-store.ts`: the donate form's state and actions — contract below |
35
36
  | `hooks/donations/useCampaigns.ts` | React binding of `campaigns-store.ts`: the listing — contract below |
@@ -328,12 +329,10 @@ first real site and read the owning file if one bites.
328
329
  shows) and `options.currency` "" (the button reads "Donate" without an amount; preset labels still
329
330
  carry the API's own formatted amounts). If the campaign query itself is refused, the pages render
330
331
  their empty state.
331
- - **Direct Create Checkout vs the Wix widget's cart route.** The default creates the checkout directly
332
- (a donation must never merge into a storefront visitor's cart); if the Donations catalog refuses to
333
- price that line, `donationCheckoutUrl` falls back to the widget's own Create Cart → Create Checkout
334
- (`channelType: OTHER_PLATFORM`), reachable by name as `createDonationCheckoutViaCart`. The cart route
335
- can answer `CURRENT_CART_ALREADY_EXISTS` for a visitor who already holds a cart — surfaced as the
336
- form's error.
332
+ - **Create Cart is the whole path (Cart V2).** `donationCheckoutUrl` creates one fresh cart with the
333
+ donation line (`cart.source.channelType: WEB`, the donor note as `cart.note`) and hands that cart's
334
+ id straight to the redirect session — the cart is the checkout, so there is no separate Create
335
+ Checkout call and nothing merges into a storefront visitor's current cart.
337
336
  - **Site currency.** Comes from the metrics response ("returns only the site's default currency");
338
337
  On a campaign nobody has donated to yet the metrics carry no currency either, so the transports read the site's currency once from the eCommerce settings (BUSINESS_INFO); unknown → "".
339
338
  - **Reading the order on the thank-you page.** `GET /ecom/v1/orders/{id}` with the visitor's token is