@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.
- package/package.json +2 -2
- package/skills/wix-app/SKILL.md +1 -1
- package/skills/wix-app/references/APP_VALIDATION.md +5 -1
- package/skills/wix-app/references/SDK_MODULE_MAP.md +1 -1
- package/skills/wix-app/references/editor-react-component/PARTS.md +1 -1
- package/skills/wix-app/references/service-plugin/DISCOUNT-TRIGGERS.md +1 -1
- package/skills/wix-app/references/stores/GET_PRODUCT.md +9 -1
- package/skills/wix-app/references/stores/INVENTORY.md +2 -0
- package/skills/wix-app/references/stores/QUERY.md +2 -2
- package/skills/wix-app/references/stores/WRITE.md +1 -1
- package/skills/wix-headless/references/astro.md +1 -1
- package/skills/wix-headless-kit/SKILL.md +4 -0
- package/skills/wix-headless-kit/guides/no-machine.md +115 -0
- package/skills/wix-headless-kit/install/attach.mjs +5 -3
- package/skills/wix-headless-kit/install/bootstrap.mjs +3 -1
- package/skills/wix-headless-kit/install/context.mjs +4 -2
- package/skills/wix-headless-kit/install/deploy.mjs +4 -2
- package/skills/wix-headless-kit/install/pins.json +8 -0
- package/skills/wix-headless-kit/install/setup.mjs +10 -8
- package/skills/wix-headless-kit/install/templates.mjs +18 -8
- package/skills/wix-headless-templates/donations/INSTRUCTIONS.md +8 -9
- package/skills/wix-headless-templates/donations/app/wix/donations/donate.ts +18 -45
- package/skills/wix-headless-templates/donations/app/wix/donations/donations-core.ts +18 -19
- package/skills/wix-headless-templates/donations/project/src/wix/donations/donate.ts +18 -45
- package/skills/wix-headless-templates/donations/project/src/wix/donations/donations-core.ts +18 -19
- package/skills/wix-headless-templates/donations/rest/donate.ts +13 -39
- package/skills/wix-headless-templates/pricing-plans/INSTRUCTIONS.md +4 -2
- package/skills/wix-headless-templates/restaurants/app/wix/restaurants/types.ts +1 -1
- package/skills/wix-headless-templates/restaurants/project/src/wix/restaurants/types.ts +1 -1
- package/skills/wix-headless-templates/restaurants/seed/SEED.md +4 -4
- package/skills/wix-headless-templates/restaurants/seed/seed-restaurants.mjs +1 -1
- package/skills/wix-headless-templates/shared/capabilities/site-search/seed/install.mjs +2 -2
- package/skills/wix-headless-templates/shared/seed/wix-cli.mjs +6 -3
- package/skills/wix-headless-templates/storefront/app/wix/storefront/cart.ts +1 -2
- package/skills/wix-headless-templates/storefront/project/src/wix/storefront/cart.ts +1 -2
- package/skills/wix-manage/references/bookings/diagnose-availability-issues.md +1 -1
- package/skills/wix-manage/references/ecommerce/ecom-shipping.md +2 -2
- package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-create-coupon.md +1 -1
- package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-create-discount-rule.md +2 -2
- package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-flow-bundle-and-save.md +1 -1
- package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-flow-seasonal-promotion.md +1 -1
- package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-flow-stock-mover.md +2 -2
- package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-flow-upsell-boost.md +3 -3
- package/skills/wix-manage/references/ecommerce/shipping/ecom-shipping-free-shipping.md +3 -3
- package/skills/wix-manage/references/ecommerce/shipping/ecom-shipping-optimize-rates.md +1 -1
- package/skills/wix-manage/references/ecommerce/shipping/ecom-shipping-setup-rates.md +3 -3
- package/skills/wix-manage/references/sites/upload-static-site.md +61 -42
- package/skills/wix-replatform/resources/rp-target-wix/lib/wix-writers.js +1 -1
- 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.
|
|
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/**",
|
package/skills/wix-app/SKILL.md
CHANGED
|
@@ -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 `
|
|
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` |
|
|
@@ -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
|
|
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
|
|
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 `
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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",
|
|
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 ||
|
|
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',
|
|
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",
|
|
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 ||
|
|
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:
|
|
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",
|
|
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
|
);
|
|
@@ -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
|
|
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 ||
|
|
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
|
|
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 ||
|
|
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:
|
|
300
|
-
react:
|
|
301
|
-
lib:
|
|
302
|
-
static: `npx
|
|
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)
|
|
15
|
-
// repository's default branch
|
|
16
|
-
//
|
|
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
|
-
|
|
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
|
|
10
|
-
|
|
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
|
|
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
|
-
- **
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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
|