@wix/agent-skills 1.31.1 → 1.31.2
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/references/STORES_VERSIONING.md +1 -1
- package/skills/wix-headless-kit/SKILL.md +16 -7
- package/skills/wix-headless-kit/guides/api-run.md +81 -51
- package/skills/wix-headless-kit/guides/existing-site.md +6 -0
- package/skills/wix-headless-kit/install/AGENTS.md +3 -2
- package/skills/wix-headless-kit/install/attach.mjs +32 -27
- package/skills/wix-headless-kit/install/background.mjs +32 -0
- package/skills/wix-headless-kit/install/bootstrap.mjs +28 -11
- package/skills/wix-headless-kit/install/check.mjs +2 -2
- package/skills/wix-headless-kit/install/context.mjs +49 -5
- package/skills/wix-headless-kit/install/deploy.mjs +33 -8
- package/skills/wix-headless-kit/install/env-ignore.mjs +16 -0
- package/skills/wix-headless-kit/install/install.mjs +95 -0
- package/skills/wix-headless-kit/install/pins.json +1 -1
- package/skills/wix-headless-kit/install/proc.mjs +46 -0
- package/skills/wix-headless-kit/install/setup.mjs +16 -25
- package/skills/wix-headless-kit/install/templates.mjs +2 -2
- package/skills/wix-headless-templates/blog/app/components/blog/RichContent.tsx +3 -2
- package/skills/wix-headless-templates/blog/project/package-lock.json +1287 -1299
- package/skills/wix-headless-templates/blog/project/package.json +1 -1
- package/skills/wix-headless-templates/blog/project/src/components/blog/RichContent.tsx +3 -2
- package/skills/wix-headless-templates/blog/rest/authors.ts +5 -5
- package/skills/wix-headless-templates/blog/rest/comments.ts +4 -4
- package/skills/wix-headless-templates/blog/rest/post-likes.ts +3 -3
- package/skills/wix-headless-templates/blog/rest/posts.ts +4 -4
- package/skills/wix-headless-templates/blog/rest/taxonomy.ts +5 -5
- package/skills/wix-headless-templates/blog/seed/seed-blog.mjs +2 -1
- package/skills/wix-headless-templates/bookings/rest/booking.ts +4 -4
- package/skills/wix-headless-templates/bookings/rest/services.ts +4 -4
- package/skills/wix-headless-templates/bookings/seed/seed-bookings.mjs +2 -1
- package/skills/wix-headless-templates/cms/rest/collections.ts +3 -3
- package/skills/wix-headless-templates/cms/rest/items.ts +4 -4
- package/skills/wix-headless-templates/cms/seed/seed-cms.mjs +2 -1
- package/skills/wix-headless-templates/donations/rest/campaigns.ts +4 -4
- package/skills/wix-headless-templates/donations/rest/donate.ts +3 -3
- package/skills/wix-headless-templates/donations/seed/seed-donations.mjs +2 -1
- package/skills/wix-headless-templates/events/rest/events.ts +4 -4
- package/skills/wix-headless-templates/events/rest/orders.ts +4 -4
- package/skills/wix-headless-templates/events/rest/registration.ts +4 -4
- package/skills/wix-headless-templates/events/seed/seed-events.mjs +2 -1
- package/skills/wix-headless-templates/faq/rest/faq.ts +4 -4
- package/skills/wix-headless-templates/faq/seed/seed-faq.mjs +2 -1
- package/skills/wix-headless-templates/forms/rest/forms.ts +4 -4
- package/skills/wix-headless-templates/forms/rest/submissions.ts +4 -4
- package/skills/wix-headless-templates/forms/seed/seed-forms.mjs +2 -1
- package/skills/wix-headless-templates/members/rest/auth.ts +3 -3
- package/skills/wix-headless-templates/members/rest/members.ts +4 -4
- package/skills/wix-headless-templates/members/seed/seed-members.mjs +2 -1
- package/skills/wix-headless-templates/portfolio/rest/portfolio.ts +4 -4
- package/skills/wix-headless-templates/portfolio/seed/seed-portfolio.mjs +2 -1
- package/skills/wix-headless-templates/pricing-plans/rest/plan-access.ts +3 -3
- package/skills/wix-headless-templates/pricing-plans/rest/plans.ts +4 -4
- package/skills/wix-headless-templates/pricing-plans/rest/purchase.ts +3 -3
- package/skills/wix-headless-templates/pricing-plans/seed/seed-pricing-plans.mjs +2 -1
- package/skills/wix-headless-templates/rentals/rest/rentals.ts +4 -4
- package/skills/wix-headless-templates/rentals/seed/seed-rentals.mjs +2 -1
- package/skills/wix-headless-templates/restaurants/rest/menu.ts +4 -4
- package/skills/wix-headless-templates/restaurants/rest/ordering.ts +5 -5
- package/skills/wix-headless-templates/restaurants/rest/reservations.ts +4 -4
- package/skills/wix-headless-templates/restaurants/seed/seed-restaurants.mjs +2 -1
- package/skills/wix-headless-templates/shared/capabilities/site-search/rest/search.ts +4 -4
- package/skills/wix-headless-templates/shared/capabilities/site-search/seed/install.mjs +6 -4
- package/skills/wix-headless-templates/shared/seed/main.mjs +15 -0
- package/skills/wix-headless-templates/shared/seed/wix-cli.mjs +43 -11
- package/skills/wix-headless-templates/storefront/INSTRUCTIONS.md +1 -1
- package/skills/wix-headless-templates/storefront/rest/cart.ts +5 -5
- package/skills/wix-headless-templates/storefront/rest/catalog.ts +5 -5
- package/skills/wix-headless-templates/storefront/seed/SEED.md +27 -9
- package/skills/wix-headless-templates/storefront/seed/seed-store.mjs +2 -1
- package/skills/wix-manage/SKILL.md +3 -3
- package/skills/wix-manage/references/app-installation/install-wix-apps.md +5 -1
- package/skills/wix-manage/references/app-installation/list-installed-apps.md +19 -9
- package/skills/wix-manage/references/bookings/booking-service-policy-setup.md +3 -0
- package/skills/wix-manage/references/bookings/external-calendar-integration.md +3 -0
- package/skills/wix-manage/references/bookings/multi-resource-service-creation.md +3 -0
- package/skills/wix-manage/references/cms/cms-data-items-crud.md +11 -2
- package/skills/wix-manage/references/cms/cms-publishing-flow.md +63 -16
- package/skills/wix-manage/references/forms/update-form.md +3 -0
- package/skills/wix-manage/references/pricing-plans/create-and-update-pricing-plans.md +3 -0
- package/skills/wix-manage/references/restaurants/wix-restaurants-setup.md +3 -0
- package/skills/wix-manage/references/sites/create-headless-site.md +7 -50
- package/skills/wix-manage/references/sites/manage-oauth-apps.md +4 -4
- package/skills/wix-manage/references/sites/read-site-context.md +4 -0
- package/skills/wix-manage/references/sites/upload-static-site.md +67 -31
- package/skills/wix-manage/references/stores/add-store-pages-to-site.md +3 -0
- package/skills/wix-manage/references/stores/change-store-currency.md +3 -0
- package/skills/wix-manage/references/stores/create-product-catalog-v3.md +3 -0
- package/skills/wix-manage/references/stores/find-products-query-and-search-catalog-v3.md +3 -0
- package/skills/wix-manage/references/stores/update-product-with-options.md +192 -22
package/package.json
CHANGED
|
@@ -130,4 +130,4 @@ Payload changes: V1 `changedFields` → V3 `modifiedFields`. Top-level `entityId
|
|
|
130
130
|
- [stores/INVENTORY.md](stores/INVENTORY.md) — inventory read and write operations
|
|
131
131
|
- [stores/CATEGORIES.md](stores/CATEGORIES.md) — collections (V1) ↔ categories (V3)
|
|
132
132
|
|
|
133
|
-
For methods not listed here, use `SearchWixSDKDocumentation` then `ReadFullDocsArticle
|
|
133
|
+
For methods not listed here, use `SearchWixSDKDocumentation` then `ReadFullDocsMethodSchema` (use `ReadFullDocsArticle` only for prose). Always return the required permission scopes to the user.
|
|
@@ -106,6 +106,11 @@ paths below are relative to that folder, never assumed under `<SKILL_ROOT>`.
|
|
|
106
106
|
`node <SKILL_ROOT>/install/check.mjs` says whether the skill or its templates have a newer version
|
|
107
107
|
and prints the update commands; it changes nothing.
|
|
108
108
|
|
|
109
|
+
Throughout any run, keep the site working at every step so the user can try it as it grows. Steps
|
|
110
|
+
only the site owner can take never hold a flow back: drafted content is live, and anything that
|
|
111
|
+
takes money reaches Wix's checkout before payments are connected. The owner's steps go in the
|
|
112
|
+
closing with their links, as next steps the user can take or ask for.
|
|
113
|
+
|
|
109
114
|
Throughout any run: if the user asks to send feedback to Wix, complains or gets frustrated, or the
|
|
110
115
|
run hits friction of any kind: anything that cost more turns than it should have, whether or not
|
|
111
116
|
it ended in an error (a confusing error, a doc gap, a seed that had to be re-run, a shipped file
|
|
@@ -143,8 +148,9 @@ only after an explicit yes, never automatically. Step 5 ends with the same self-
|
|
|
143
148
|
`astro.config.mjs` `integrations: [wix(), react()]`, `adapter: wixHostingAdapter()`,
|
|
144
149
|
`output: "server"`, `security: { checkOrigin: false }`, `image.domains` with
|
|
145
150
|
`static.wixstatic.com`. An Astro project made without the CLI has none of that; add it
|
|
146
|
-
before deploying, and the site serves every route. The integration
|
|
147
|
-
|
|
151
|
+
before deploying, and the site serves every route. The integration's current major
|
|
152
|
+
(`@wix/astro` 3, as in this skill's templates) requires **Astro 7**: a project on an earlier
|
|
153
|
+
major moves to 7 first, or is connected as a React host.
|
|
148
154
|
- **React and other bundlers** release their own build as files. So routes are hash routes,
|
|
149
155
|
or one emitted HTML file per route linked by its file name — decided before the first route
|
|
150
156
|
is written; any URL handed to Wix as a return target must be one the host serves. Verify by
|
|
@@ -189,10 +195,13 @@ only after an explicit yes, never automatically. Step 5 ends with the same self-
|
|
|
189
195
|
| a frontend, no config (a `package.json`, or `index.html` at the root: someone's Astro, Vite, Next, plain HTML) | **project** → adopt | `init` in place gives it a new, empty site, then deploys; `--stack` from step 1 is required; make the project what that stack needs on Wix hosting (step 1) before or right after | **yes**, from `--plan` |
|
|
190
196
|
| a config whose `.env.local` declares an active editor migration (`EDITOR_MIGRATION_STATUS=ACTIVE`), with or without the blank Astro starter the download carries | **migration** → migrate | the shipped code into the starter (or the composed template around a bare config), deployed with the migrated site's app as the client, the install starts; `ready_for_brand_layer` says `mode: "migrate"`, the parent as `siteId`, the child as `deploySiteId` (`guides/migration.md`) | no, ever |
|
|
191
197
|
| a config, no frontend | **config-only** → refuses | the site exists and has no frontend yet: `attach.mjs` (below) takes the site from the config, reuses its hosting, scaffolds and deploys. That config is what `init` leaves behind, and `init` always creates a site: this site was made for this run and is empty | **yes**: draft the plan as for create, then `attach.mjs --plan` |
|
|
192
|
-
| a config and a frontend (a `package.json`, or `index.html` inside the folder `site.outputDirectory` names) | **wix-project** → refuses | iterate: never scaffold, `init` or reseed. `deploy.mjs <vertical…> --stack <stack>` adds a solution (the client id comes from `.env.local`, the config as the fallback), then
|
|
198
|
+
| a config and a frontend (a `package.json`, or `index.html` inside the folder `site.outputDirectory` names) | **wix-project** → refuses | iterate: never scaffold, `init` or reseed. `deploy.mjs <vertical…> --stack <stack>` adds a solution (the client id comes from `.env.local`, the config as the fallback), then one install, `node <SKILL_ROOT>/install/install.mjs` (nothing when `node_modules` already satisfies the lock); a change is file edits; then release | no |
|
|
193
199
|
| a config, a `package.json`, `public/index.html` and no `src/pages/` (a site published through the drop flow, on its Wix Dev Machine) | **static-in-astro** → refuses | the pages stay plain files in `public/`: `deploy.mjs <vertical…> --stack static --out public` puts the REST layer in `public/js/wix/`, the pages are wired in place (reference mode), and no Astro pages are added; release with `build` then `release`, the URL stays | no |
|
|
200
|
+
| a config and the CLI's blank Astro starter with no work of its own (`wix-astro-blank`, only the blank template's packages: a Dev Machine's never-released site, a bare `wix create`) | **blank-starter** → refuses | `attach.mjs --business-name <brand> --vertical <v…> [--plan plan.json]` on the config's site: the vertical's composed template takes the starter's place (its lockfile with it), the install and the seed start detached; build the brand layer while they run | **yes**, from `--plan`: the site was made for this run and is empty, unless `read-site.mjs` shows content |
|
|
194
201
|
| a config, `index.html` at the root, no `package.json` (a site published through the drop flow and downloaded) | **published-static** | the config's site, no `init`: `site/` becomes the upload, the REST layer deploys into `site/js/wix/`; the `next` says to move the pages, styles and assets in; release keeps the URL | no |
|
|
195
202
|
|
|
203
|
+
A folder no row names is **wix-project**: the fill-only path — never scaffold, `init`, reseed or delete; deploy, edit, release. A wrong guess costs a slower run, never the user's work.
|
|
204
|
+
|
|
196
205
|
**Who decides the seed: where the site came from, never the brief's wording.** A site made
|
|
197
206
|
for this run is empty by construction, so it is always seeded — with the brief's content when
|
|
198
207
|
it supplies or describes any, otherwise with demo content the agent drafts per the vertical's
|
|
@@ -232,7 +241,7 @@ only after an explicit yes, never automatically. Step 5 ends with the same self-
|
|
|
232
241
|
It emits one JSON event per line and returns in **~35s**: **scaffolds** the project from the
|
|
233
242
|
vertical's composed template (`wix create` copies it: the code and its lockfile arrive with
|
|
234
243
|
the scaffold), **deploys** whatever the folder still lacks (patching `package.json` with every
|
|
235
|
-
dependency the code imports), then **starts two detached background jobs** — the dependency install (`npm ci --ignore-scripts || npm install --ignore-scripts`)
|
|
244
|
+
dependency the code imports), then **starts two detached background jobs** — the dependency install (`install/install.mjs`: nothing when `node_modules` already satisfies the lock, `npm install --ignore-scripts` over a tree that is there, else `npm ci --ignore-scripts || npm install --ignore-scripts`)
|
|
236
245
|
and the **seed** — whose logs and completion markers are in the events. The final
|
|
237
246
|
`ready_for_brand_layer` event carries the project dir, siteId, ready-made dashboard links,
|
|
238
247
|
and both markers. Relay notable events. On an `error` event, recover just that step via the
|
|
@@ -240,8 +249,8 @@ only after an explicit yes, never automatically. Step 5 ends with the same self-
|
|
|
240
249
|
|
|
241
250
|
**Recovering one step, or adding a solution later:** the pieces run on their own from the
|
|
242
251
|
project root — `node <SKILL_ROOT>/install/deploy.mjs <vertical…> --stack <stack>` (the client
|
|
243
|
-
id is read from `wix.config.json`), ONE `
|
|
244
|
-
|
|
252
|
+
id is read from `wix.config.json`), ONE `node <SKILL_ROOT>/install/install.mjs`
|
|
253
|
+
(**never a second npm install concurrently**: two npms in one
|
|
245
254
|
`node_modules` race and redo each other's work; setup already started one — wait on its
|
|
246
255
|
marker), the vertical's seed module per its `seed/SEED.md`.
|
|
247
256
|
A code change on an existing project is done when it is **released** (step 5) and the live
|
|
@@ -266,7 +275,7 @@ only after an explicit yes, never automatically. Step 5 ends with the same self-
|
|
|
266
275
|
`templates/shared/CUSTOM_OPERATIONS.md` before writing it. Use one documented path and
|
|
267
276
|
implement it; do not reverse-engineer SDK internals.
|
|
268
277
|
5. **When both background jobs have completed** — the install's marker
|
|
269
|
-
(
|
|
278
|
+
(`.install-exit`, containing `0`) and the seed's (`.seed-exit`) both exist — **verify the
|
|
270
279
|
seed succeeded** (`.seed-exit` contains `0`; `seed-result.json` has the created counts for
|
|
271
280
|
your summary — if non-zero, read `seed.log` and re-run the seed module manually). Those two
|
|
272
281
|
seed files exist **only when setup started the seed** (attach runs none: only the install
|
|
@@ -3,10 +3,17 @@
|
|
|
3
3
|
Read when the commands this skill is built on cannot run where you are, or stopped: your shell
|
|
4
4
|
can't reach Wix, there is no shell, or the install, the
|
|
5
5
|
CLI or the login is blocked. Wix is then reachable through the Wix MCP: every call below is an
|
|
6
|
-
`ExecuteWixAPI` call, and files reach the site through the upload recipe.
|
|
6
|
+
`ExecuteWixAPI` call, and files reach the site through the upload recipe. With no Wix MCP tools in
|
|
7
|
+
your tool list (a plugin's skills can be on disk while its Wix connector is not connected), ask the
|
|
8
|
+
user to connect Wix to this chat first. It walks the run of
|
|
7
9
|
SKILL.md step by step and names, for each, the Wix API call the CLI or the script performs and the
|
|
8
10
|
file beside this skill that carries the contract; where a script can run where you are, it says so.
|
|
9
11
|
|
|
12
|
+
As in SKILL.md's run, keep the site working at every step so the user can try it as it grows.
|
|
13
|
+
Steps only the site owner can take never hold a flow back: drafted content is live, and anything
|
|
14
|
+
that takes money reaches Wix's checkout before payments are connected. The owner's steps go in the
|
|
15
|
+
closing with their links, as next steps the user can take or ask for.
|
|
16
|
+
|
|
10
17
|
## Where these skills are
|
|
11
18
|
|
|
12
19
|
This skill is one of a set, and the guide reads from three of them:
|
|
@@ -25,13 +32,25 @@ This skill is one of a set, and the guide reads from three of them:
|
|
|
25
32
|
exact shape before writing a call.
|
|
26
33
|
|
|
27
34
|
Installed, they sit beside each other wherever your client keeps them: under `.agents/skills/` after
|
|
28
|
-
`npx skills add
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
`npx skills add` or a similar skills folder, or in a plugin's folder. A skill your client offers you
|
|
36
|
+
by name is read from there, or through the client's skill tool when it is served rather than a file,
|
|
37
|
+
with no install, clone or online read. List them first, through that tool or the folder: a catalog
|
|
38
|
+
shown at the start can leave skills out. Online, they are published alongside each other under the
|
|
39
|
+
same names (the registry also serves each without its `wix-` prefix, `headless-templates` for
|
|
40
|
+
`wix-headless-templates`, which is the `base` its manifest shows; both forms resolve), and in the
|
|
32
41
|
`wix/skills` repository on GitHub under `skills/{skill}/`; a sandbox that reaches GitHub has the
|
|
33
|
-
whole set on disk from a clone. Online pages are read with the Wix MCP's `ReadFullDocsArticle`
|
|
34
|
-
|
|
42
|
+
whole set on disk from a clone. Online pages are read with the Wix MCP's `ReadFullDocsArticle` when
|
|
43
|
+
it is connected: a client's web fetch may open only URLs the user gave or a search returned.
|
|
44
|
+
|
|
45
|
+
Online, a skill's files are found through its manifest,
|
|
46
|
+
`https://www.wix.com/skills/{skill}.manifest.json` (for the templates,
|
|
47
|
+
`headless-templates.manifest.json`): every file it holds, by path, with its size in bytes. A
|
|
48
|
+
folder's URL lists its files, and `<folder>/_content` returns them in one response. Open it once
|
|
49
|
+
before the first file and take each path from it: the files this guide names by pattern
|
|
50
|
+
(`{solution}/rest/*.ts`, `*-core.ts`), and the files an import points to. A path the manifest
|
|
51
|
+
doesn't list is a wrong path; find the file there before writing anything in its place. A copy you
|
|
52
|
+
hold, served or on disk, is current when its size in bytes matches the manifest's; a file whose size
|
|
53
|
+
differs is read online.
|
|
35
54
|
|
|
36
55
|
What such a run does not produce: the Astro build and its release. Those need a machine with the
|
|
37
56
|
CLI. Everything else the kit does to a site, it does through calls you can make, the frontend
|
|
@@ -75,32 +94,25 @@ Restaurants, Donations, FAQ), goes on with the Apps Installer:
|
|
|
75
94
|
## 4. The seed
|
|
76
95
|
|
|
77
96
|
`seed-{solution}.mjs` is a sequence of REST calls, and `SEED.md` is its contract: the plan shape
|
|
78
|
-
(what to create, how many, which images), the order the entities need,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
the
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
with the drop, not from the Media Manager: it goes in by its live URL, imported once through
|
|
98
|
-
`{MANAGE}/references/media/upload-media-to-wix.md`, and the file id the import returns is what the
|
|
99
|
-
product or service takes. Image prompts need the Media Manager; without
|
|
100
|
-
it, products and services stay text-only, say so. The scripts read
|
|
101
|
-
a response body directly; `ExecuteWixAPI`'s `wix.request` returns `{ status, data }`, and the body
|
|
102
|
-
is `data`. A generated image is billed when it is generated, so a read of the wrong level discards
|
|
103
|
-
a paid image.
|
|
97
|
+
(what to create, how many, which images), the order the entities need, the request bodies and the
|
|
98
|
+
traps the script encodes. Read `SEED.md` before the first create: it maps the brief's words to the
|
|
99
|
+
fields, and the script's builder functions turn those fields into each request, so the bodies you
|
|
100
|
+
send are the ones they build. A call `SEED.md` only names is made through `{MANAGE}`'s recipe for
|
|
101
|
+
it. Keep the script's rules: create, never delete, and report what the site already held. The closing
|
|
102
|
+
names entities you drafted as placeholders.
|
|
103
|
+
|
|
104
|
+
A photo already in the site's Media Manager, such as one the user uploaded, goes in by its file id
|
|
105
|
+
(`imageMediaId` in `SEED.md`), as it is: an import by URL would copy it. A photo on a site the user
|
|
106
|
+
dropped is served with the drop, not from the Media Manager: it goes in by its live URL, imported
|
|
107
|
+
once through `{MANAGE}/references/media/upload-media-to-wix.md`, and the file id the import returns
|
|
108
|
+
is what the entity takes. An `imagePrompt` is generated through
|
|
109
|
+
`{MANAGE}/references/media/generate-image-with-ai.md` (`POST /runwareschemaless/v1/request`, the
|
|
110
|
+
body `generateImage` in `{TEMPLATES}/shared/seed/images.mjs` builds), then imported through
|
|
111
|
+
`upload-media-to-wix.md`, and the file id goes on the entity, as for a dropped photo; this is the
|
|
112
|
+
seed's default for every entity that shows a picture. An entity stays text-only only when generation
|
|
113
|
+
or the import fails; say which. The scripts read a response body directly; `ExecuteWixAPI`'s
|
|
114
|
+
`wix.request` returns `{ status, data }`, and the body is `data`. A generated image is billed when
|
|
115
|
+
it is generated, so a read of the wrong level discards a paid image.
|
|
104
116
|
|
|
105
117
|
## 5. The frontend
|
|
106
118
|
|
|
@@ -108,6 +120,9 @@ The run is not done until the pages use what the backend holds: every control th
|
|
|
108
120
|
it (add to cart, the cart, book, submit) works on the live site. A backend with untouched pages is
|
|
109
121
|
half a run.
|
|
110
122
|
|
|
123
|
+
A payment (a cart, a paid booking, a ticket, a plan, a donation) ends in Wix's hosted checkout,
|
|
124
|
+
reached through a redirect session, and is wired on the first pass, payments connected or not.
|
|
125
|
+
|
|
111
126
|
The Astro pages cannot be released here: that is a run with a machine and a CLI login, which
|
|
112
127
|
attaches to this site (`guides/existing-site.md`) rather than making a new one. What ships from here
|
|
113
128
|
is static files, dropped onto the site from step 2 through
|
|
@@ -145,16 +160,14 @@ with `{TEMPLATES}/shared/app/wix/sdk.ts` for the client wiring): those files cal
|
|
|
145
160
|
modules the page imports, and they are verified. Copy the form of the call, not the file. A page
|
|
146
161
|
that talks to Wix over plain `fetch`, with no SDK, takes them from the REST twins instead:
|
|
147
162
|
`{TEMPLATES}/shared/rest/client.ts` (the visitor token and the request) and
|
|
148
|
-
`{TEMPLATES}/{solution}/rest/*.ts` (the calls
|
|
149
|
-
|
|
150
|
-
`
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
line's quantity is `quantityInfo.confirmedQuantity`). What you write is the markup, the rendering
|
|
157
|
-
of what the cores return, and the event handlers.
|
|
163
|
+
`{TEMPLATES}/{solution}/rest/*.ts` (the calls), each importing its helpers by their real paths.
|
|
164
|
+
The cores,
|
|
165
|
+
`{TEMPLATES}/{solution}/app/wix/{solution}/*-core.ts`, hold the rules the page needs and nothing else
|
|
166
|
+
does: which fields to read, how a price or a duration is formatted, which slot is bookable, how an
|
|
167
|
+
id is read. Copy those core functions into the page as they are, with only the types removed, and
|
|
168
|
+
run every API response through them: a raw field read by hand is where a page breaks (a name can be
|
|
169
|
+
a translatable object, a quantity a nested one). What you write is the markup, the rendering of what
|
|
170
|
+
the cores return, and the event handlers.
|
|
158
171
|
|
|
159
172
|
**A call those files don't cover** is confirmed before it is written, the way `{DOCS}` describes:
|
|
160
173
|
the method's SDK page, and when the page and the package disagree, the package's own type
|
|
@@ -162,14 +175,31 @@ declarations (the `@wix/auto_sdk_{solution}_<module>` package the solution packa
|
|
|
162
175
|
its `index.d.ts`) are the truth. Two shapes that recur, as examples of what the types settle and
|
|
163
176
|
the kit's files already encode: SDK query methods return a query builder finished with `.find()`;
|
|
164
177
|
and an entity's id arrives as `_id` on some objects and `id` on others (the kit reads both,
|
|
165
|
-
`rawId`). Dates travel as local wall-clock strings in the business time zone.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
178
|
+
`rawId`). Dates travel as local wall-clock strings in the business time zone.
|
|
179
|
+
|
|
180
|
+
**The check**, before every upload: a page cannot be opened here, so a wrong shape fails silently in
|
|
181
|
+
the browser. In one `ExecuteWixAPI` script, make each control's calls the way the page makes them,
|
|
182
|
+
as a visitor, with the bodies from `{solution}/rest/*.ts`. Then read the solution's cores,
|
|
183
|
+
`{TEMPLATES}/{solution}/app/wix/{solution}/*-core.ts` (online, that folder's `_content` returns them
|
|
184
|
+
in one response), and run each response through them in your sandbox's Node, as the page does:
|
|
185
|
+
|
|
186
|
+
```js
|
|
187
|
+
async function () {
|
|
188
|
+
const api = "https://www.wixapis.com", clientId = "<appId>";
|
|
189
|
+
const post = (path, body, token) => fetch(api + path, { method: "POST",
|
|
190
|
+
headers: { "Content-Type": "application/json", ...(token && { Authorization: token }) },
|
|
191
|
+
body: JSON.stringify(body) }).then(async (r) => ({ path, status: r.status, data: await r.json() }));
|
|
192
|
+
const { data: { access_token: token } } = await post("/oauth2/token", { clientId, grantType: "anonymous" });
|
|
193
|
+
const results = [];
|
|
194
|
+
// each call the control makes, in order: results.push(await post(path, body, token));
|
|
195
|
+
// a payment's last call is the redirect session the solution's rest/*.ts creates
|
|
196
|
+
return results;
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Done when every call returns 200, the cores turn each response into the names, prices, quantities or
|
|
201
|
+
times the page shows, and a payment's redirect session returns a URL. The upload waits for that: a
|
|
202
|
+
failing call is fixed first, on the site or in the page. The closing reports the check.
|
|
173
203
|
|
|
174
204
|
## 6. Closing
|
|
175
205
|
|
|
@@ -27,6 +27,12 @@ shape as setup (SKILL.md step 3):
|
|
|
27
27
|
node <SKILL_ROOT>/install/attach.mjs --site <siteId> --business-name "<site name>" --vertical <vertical>[,<vertical>] [--plan plan.json]
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
The same call takes a folder that already holds the site's config and the CLI's blank Astro
|
|
31
|
+
starter with no work of its own (`context.mjs` says `blank-starter`: a Wix Dev Machine for a
|
|
32
|
+
never-released site, or a bare `wix create`): `--site` comes from the config, the vertical's
|
|
33
|
+
composed template takes the starter's place, its lockfile with it, and the install and the seed
|
|
34
|
+
run detached as below. Do not install by hand there; the brand layer is yours while they run.
|
|
35
|
+
|
|
30
36
|
`init`/`wix create` always create a site, so they are not used here. attach does what they do
|
|
31
37
|
after creating one — the site's OAuth app, Wix hosting, `wix.config.json` — against the site
|
|
32
38
|
given, copies the first vertical's composed template, deploys the rest, and starts the install
|
|
@@ -17,8 +17,9 @@ call is memory, and an empty or error reply to it says nothing about the site.
|
|
|
17
17
|
|
|
18
18
|
## Skills
|
|
19
19
|
|
|
20
|
-
Installed at `.agents/skills/`
|
|
21
|
-
`npx skills add wix/skills --skill {{SKILL}} --skill wix-docs --skill wix-manage
|
|
20
|
+
Installed at `.agents/skills/` by
|
|
21
|
+
`npx skills add wix/skills --skill {{SKILL}} --skill wix-docs --skill wix-manage`; a Wix plugin keeps
|
|
22
|
+
its copy, under the same names, in the client's own skills folder.
|
|
22
23
|
|
|
23
24
|
- `{{SKILL}}` — the code in this app and how to extend it. The shipped code is the
|
|
24
25
|
`wix-headless-templates` skill: `node .agents/skills/{{SKILL}}/install/templates.mjs` prints where it
|
|
@@ -46,13 +46,16 @@
|
|
|
46
46
|
// Emits ONE JSON event per line (attached, scaffolded, deployed, install_started,
|
|
47
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
|
-
import {
|
|
50
|
-
import { cpSync, existsSync, mkdirSync,
|
|
49
|
+
import { spawnSync } from "node:child_process";
|
|
50
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from "node:fs";
|
|
51
51
|
import { basename, dirname, join, resolve } from "node:path";
|
|
52
52
|
import { fileURLToPath } from "node:url";
|
|
53
53
|
import { writeAgentsMd } from "./agents-md.mjs";
|
|
54
|
+
import { ignoreEnvLocal } from "./env-ignore.mjs";
|
|
54
55
|
import { frontendPresent, siteContext } from "./context.mjs";
|
|
55
56
|
import { listVerticals, templatesDir } from "./templates.mjs";
|
|
57
|
+
import { failure, npmSync, startBackground } from "./proc.mjs";
|
|
58
|
+
import { blankStarter } from "./context.mjs";
|
|
56
59
|
const PINS = JSON.parse(readFileSync(new URL("./pins.json", import.meta.url), "utf8"));
|
|
57
60
|
const WIX_CLI = `@wix/cli@${PINS["@wix/cli"]}`;
|
|
58
61
|
|
|
@@ -90,6 +93,10 @@ const cwdConfig = has("wix.config.json") ? JSON.parse(readFileSync(join(cwd, "wi
|
|
|
90
93
|
// config's site.outputDirectory names (a static site laid out for release) — install/context.mjs.
|
|
91
94
|
const fp = frontendPresent(cwd);
|
|
92
95
|
const hasProject = fp.packageJson || fp.rootIndex || fp.outputIndex;
|
|
96
|
+
// The CLI's blank starter with no work of its own (a Dev Machine's never-released site, a bare
|
|
97
|
+
// `wix create`): not a project to link, a scaffold to complete — the vertical's composed template
|
|
98
|
+
// takes its place (deploy.mjs), the install and the seed run detached as for a new site.
|
|
99
|
+
const starter = !!cwdConfig && fp.packageJson && !(fp.publicIndex && !fp.astroPages) && blankStarter(cwd);
|
|
93
100
|
// A migration preview (wix.config.json names a deploy-only site, .env.local names the site being
|
|
94
101
|
// migrated) is already provisioned: its app, hosting and credentials came with the folder. Nothing
|
|
95
102
|
// here applies — setup.mjs takes it (SKILL.md step 3).
|
|
@@ -122,24 +129,24 @@ if (hosting0 === "self" && stack === "astro" && !hasProject && !stackFlag) {
|
|
|
122
129
|
fail("args", "--hosting self scaffolds nothing: pass --stack for the frontend you will host (react|lib|static), or attach from inside the project on disk");
|
|
123
130
|
}
|
|
124
131
|
if (!subfolder) {
|
|
125
|
-
if (hasProject && cwdConfig) {
|
|
126
|
-
fail("place", "this folder is already a Wix project with a frontend (wix.config.json and a project): nothing to attach. deploy.mjs <vertical> adds this skill's code or a solution to it; then
|
|
132
|
+
if (hasProject && cwdConfig && !starter) {
|
|
133
|
+
fail("place", "this folder is already a Wix project with a frontend (wix.config.json and a project): nothing to attach. deploy.mjs <vertical> adds this skill's code or a solution to it; then node <SKILL_ROOT>/install/install.mjs");
|
|
127
134
|
}
|
|
128
135
|
if (cwdConfig && (cwdConfig.siteId ?? cwdConfig.projectId) !== siteId) {
|
|
129
136
|
fail("place", `this folder is attached to site ${cwdConfig.siteId ?? cwdConfig.projectId}, not ${siteId} — a folder is never re-pointed; attach the other site with --subfolder`);
|
|
130
137
|
}
|
|
131
|
-
if (hasProject && !stackFlag) {
|
|
138
|
+
if (hasProject && !starter && !stackFlag) {
|
|
132
139
|
fail("args", "attaching a project on disk needs --stack astro|react|lib|static — the stack you resolved in SKILL.md step 1 for this project");
|
|
133
140
|
}
|
|
134
141
|
}
|
|
135
|
-
const mode = !subfolder && hasProject ? "link" : hosting0 === "self" ? "config-only" : "scaffold";
|
|
136
|
-
emit("folder", { mode, stack, hosting: hosting0, project: hasProject, config: cwdConfig ? "same site" : null, siteOrigin });
|
|
142
|
+
const mode = !subfolder && hasProject && !starter ? "link" : hosting0 === "self" ? "config-only" : "scaffold";
|
|
143
|
+
emit("folder", { mode, stack, hosting: hosting0, project: hasProject, starter, config: cwdConfig ? "same site" : null, siteOrigin });
|
|
137
144
|
|
|
138
145
|
// ---- http ---------------------------------------------------------------------------------------
|
|
139
146
|
const cliToken = (site) => {
|
|
140
|
-
const r =
|
|
147
|
+
const r = npmSync("npx", ["-y", WIX_CLI, "token", ...(site ? ["--site", site] : [])], { encoding: "utf8", timeout: 120_000 });
|
|
141
148
|
const t = (r.stdout || "").trim();
|
|
142
|
-
if (r.status !== 0 || !t) fail("auth", (r
|
|
149
|
+
if (r.status !== 0 || !t) fail("auth", failure(r, `no token — is the Wix CLI logged in? (npx ${WIX_CLI} whoami)`).slice(-400));
|
|
143
150
|
return t;
|
|
144
151
|
};
|
|
145
152
|
async function call(base, path, { method = "POST", token, site, body, query } = {}) {
|
|
@@ -163,6 +170,13 @@ async function call(base, path, { method = "POST", token, site, body, query } =
|
|
|
163
170
|
}
|
|
164
171
|
|
|
165
172
|
const folderName = flag("folder-name") ?? (businessName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "site");
|
|
173
|
+
const renamePackage = (dir, name) => {
|
|
174
|
+
const pkgPath = join(dir, "package.json");
|
|
175
|
+
if (!existsSync(pkgPath)) return;
|
|
176
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
177
|
+
pkg.name = name;
|
|
178
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
179
|
+
};
|
|
166
180
|
const projectDir = subfolder ? resolve(cwd, folderName) : cwd;
|
|
167
181
|
if (subfolder && existsSync(join(projectDir, "wix.config.json"))) fail("args", `${folderName}/ is already a Wix project — pick --folder-name, or run deploy.mjs there`);
|
|
168
182
|
|
|
@@ -277,13 +291,9 @@ if (stack === "astro" && mode === "scaffold") {
|
|
|
277
291
|
// here because the CLI's create makes a new site and this folder gets an existing one. Further
|
|
278
292
|
// verticals are deployed below.
|
|
279
293
|
cpSync(join(templatesDir({ need: `${verticals[0]}/project` }), verticals[0], "project"), projectDir, { recursive: true, force: false, errorOnExist: false });
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
284
|
-
const gi = join(projectDir, ".gitignore");
|
|
285
|
-
const cur = existsSync(gi) ? readFileSync(gi, "utf8") : "";
|
|
286
|
-
if (!/\.env/.test(cur)) writeFileSync(gi, cur + "\n# local env (pulled from Wix)\n.env.local\n.env\n");
|
|
294
|
+
// The blank starter keeps its name through deploy, which recognizes it by name and gives it the
|
|
295
|
+
// composed package.json and lockfile; the project takes the folder's name after that (below).
|
|
296
|
+
if (!starter) renamePackage(projectDir, folderName);
|
|
287
297
|
}
|
|
288
298
|
writeFileSync(join(projectDir, "wix.config.json"), JSON.stringify({ appId, siteId }, null, 2) + "\n");
|
|
289
299
|
// The env the CLI's build reads. Same content `wix env pull` writes; written here so the build
|
|
@@ -297,6 +307,7 @@ writeFileSync(join(projectDir, ".env.local"), [
|
|
|
297
307
|
`WIX_CLIENT_SECRET=${quote(secrets.appSecret)}`,
|
|
298
308
|
"",
|
|
299
309
|
].join("\n"));
|
|
310
|
+
ignoreEnvLocal(projectDir); // in every mode: a linked project's own .gitignore may not cover it
|
|
300
311
|
emit(mode === "link" ? "linked" : mode === "config-only" ? "configured" : "scaffolded", { folder: folderName, stack, template: mode === "scaffold" && stack === "astro" ? join(TEMPLATES, verticals[0], "project") : null });
|
|
301
312
|
// the agent config files `wix create` writes (attach never runs the CLI's scaffold at all); fill-only
|
|
302
313
|
emit("agent_configs", writeAgentsMd(projectDir, { skill: basename(SKILL_ROOT), stack }));
|
|
@@ -313,20 +324,19 @@ if (mode !== "link" && (stack !== "astro" || mode === "config-only")) {
|
|
|
313
324
|
let deployResult = {};
|
|
314
325
|
{
|
|
315
326
|
const deploy = spawnSync("node", [join(SKILL_ROOT, "install", "deploy.mjs"), ...verticals, "--stack", stack, ...(planPath ? ["--plan", resolve(planPath)] : [])], { cwd: projectDir, encoding: "utf8", timeout: 60_000 });
|
|
316
|
-
if (deploy.status !== 0) fail("deploy", deploy
|
|
327
|
+
if (deploy.status !== 0) fail("deploy", failure(deploy));
|
|
317
328
|
try { deployResult = JSON.parse(deploy.stdout); } catch { /* keep going */ }
|
|
318
329
|
if (deployResult.error) fail("deploy", deployResult.error);
|
|
319
330
|
emit("deployed", deployResult);
|
|
331
|
+
if (starter) renamePackage(projectDir, folderName);
|
|
320
332
|
}
|
|
321
333
|
|
|
322
334
|
// ---- 4 · dependency install, detached (any project with a package.json) --------------------------
|
|
323
335
|
let install = null;
|
|
324
336
|
if (existsSync(join(projectDir, "package.json"))) {
|
|
325
337
|
const installLog = join(projectDir, "npm-install.log");
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
child.unref();
|
|
329
|
-
install = { log: installLog, doneMarker: "node_modules/.package-lock.json" };
|
|
338
|
+
startBackground("install", [], { cwd: projectDir, log: installLog });
|
|
339
|
+
install = { log: installLog, doneMarker: ".install-exit", success: "file contains 0" };
|
|
330
340
|
emit("install_started", install);
|
|
331
341
|
}
|
|
332
342
|
|
|
@@ -339,12 +349,7 @@ if (planPath) {
|
|
|
339
349
|
const seedDir = join(TEMPLATES, verticals[0], "seed");
|
|
340
350
|
const seedName = existsSync(seedDir) ? readdirSync(seedDir).find((f) => f.startsWith("seed-") && f.endsWith(".mjs")) : undefined;
|
|
341
351
|
if (!seedName) fail("seed", `no seed module found under ${seedDir}`);
|
|
342
|
-
|
|
343
|
-
"sh",
|
|
344
|
-
["-c", `node "${join(seedDir, seedName)}" "${resolve(planPath)}" > seed-result.json 2> seed.log; echo $? > .seed-exit`],
|
|
345
|
-
{ cwd: projectDir, detached: true, stdio: "ignore" },
|
|
346
|
-
);
|
|
347
|
-
seedChild.unref();
|
|
352
|
+
startBackground("seed", [join(seedDir, seedName), resolve(planPath)], { cwd: projectDir });
|
|
348
353
|
seed = { resultFile: "seed-result.json", log: "seed.log", doneMarker: ".seed-exit", success: "file contains 0" };
|
|
349
354
|
emit("seeding_started", { vertical: verticals[0], ...seed, ...(verticals.length > 1 ? { note: `the plan seeds ${verticals[0]}; the other verticals' seeds run afterwards, each with its own plan` } : {}) });
|
|
350
355
|
} else {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// The detached jobs setup.mjs and attach.mjs start (through proc.mjs startBackground), run by node
|
|
2
|
+
// so they need no shell on any OS. Each leaves the files the caller syncs on:
|
|
3
|
+
//
|
|
4
|
+
// node background.mjs install install.mjs: nothing when node_modules already satisfies
|
|
5
|
+
// the lock, npm install when a tree is there, else npm ci
|
|
6
|
+
// (falling back to npm install); --ignore-scripts, output
|
|
7
|
+
// inherited (the install log); the exit code to .install-exit
|
|
8
|
+
// node background.mjs seed <seed.mjs> <plan> the seed's stdout to seed-result.json, its stderr to
|
|
9
|
+
// seed.log, then its exit code to .seed-exit
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
import { closeSync, openSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { runInstall } from "./install.mjs";
|
|
13
|
+
|
|
14
|
+
const [job, ...args] = process.argv.slice(2);
|
|
15
|
+
|
|
16
|
+
if (job === "install") {
|
|
17
|
+
const r = runInstall(process.cwd());
|
|
18
|
+
console.log(`install: ${r.decision}${r.lock ? ` (${r.lock.missing} of ${r.lock.total} lock entries were missing)` : ""}`);
|
|
19
|
+
process.exit(r.status);
|
|
20
|
+
} else if (job === "seed") {
|
|
21
|
+
const [seedFile, plan] = args;
|
|
22
|
+
const out = openSync("seed-result.json", "w");
|
|
23
|
+
const err = openSync("seed.log", "w");
|
|
24
|
+
const r = spawnSync(process.execPath, [seedFile, plan], { stdio: ["ignore", out, err], windowsHide: true });
|
|
25
|
+
closeSync(out);
|
|
26
|
+
closeSync(err);
|
|
27
|
+
if (r.error) writeFileSync("seed.log", `the seed could not start: ${r.error.message}\n`, { flag: "a" });
|
|
28
|
+
writeFileSync(".seed-exit", `${r.status ?? 1}\n`);
|
|
29
|
+
} else {
|
|
30
|
+
console.error("usage: background.mjs install <ci|install> | seed <seed.mjs> <plan.json>");
|
|
31
|
+
process.exit(2);
|
|
32
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
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
|
-
import { join } from 'node:path';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
6
|
const PINS = JSON.parse(readFileSync(new URL("./pins.json", import.meta.url), "utf8"));
|
|
7
7
|
const WIX_CLI = `@wix/cli@${PINS["@wix/cli"]}`;
|
|
8
8
|
|
|
@@ -14,10 +14,17 @@ const fail = (event, extra = {}) => {
|
|
|
14
14
|
process.exit(1);
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
// ──
|
|
17
|
+
// ── npx on every platform ────────────────────────────────────────────────────
|
|
18
|
+
// On Windows npx is a .cmd shim, which Node starts only through a shell; a detached shell opens its
|
|
19
|
+
// own console, and the login's JSON events never reach the log. npm ships npx's JS entry beside
|
|
20
|
+
// node.exe, and that starts directly with node: no shell, no window, and the pid is the CLI's.
|
|
21
|
+
// A Node without npm beside it (a version manager's shim) keeps the .cmd through the shell.
|
|
18
22
|
const isWin = process.platform === 'win32';
|
|
19
|
-
const
|
|
20
|
-
const
|
|
23
|
+
const NPX_JS = isWin ? join(dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npx-cli.js') : null;
|
|
24
|
+
const NPX = NPX_JS && existsSync(NPX_JS)
|
|
25
|
+
? { file: process.execPath, pre: [NPX_JS], shell: false }
|
|
26
|
+
: { file: isWin ? 'npx.cmd' : 'npx', pre: [], shell: isWin };
|
|
27
|
+
const WIX = [NPX.file, ...NPX.pre, '-y', WIX_CLI]; // run the CLI via npx — no global install/mutation
|
|
21
28
|
|
|
22
29
|
// Force the CLI into non-interactive "agent" mode. Without an agent signal in
|
|
23
30
|
// the env, `wix login` renders an interactive Ink TUI (device code + keypress)
|
|
@@ -36,17 +43,26 @@ const PIDFILE = join(STATE_DIR, 'login.pid');
|
|
|
36
43
|
|
|
37
44
|
// run a command, capture stdout+stderr (combined), return {status, out}
|
|
38
45
|
function capture(cmd, args, opts = {}) {
|
|
39
|
-
const r = spawnSync(cmd, args, { encoding: 'utf8', shell:
|
|
46
|
+
const r = spawnSync(cmd, args, { encoding: 'utf8', shell: NPX.shell, windowsHide: true, env: AGENT_ENV, ...opts });
|
|
40
47
|
return { status: r.status ?? 1, out: `${r.stdout || ''}${r.stderr || ''}`, error: r.error };
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
// ── 1. CLI reachable (via npx — no install) ──────────────────────────────────
|
|
51
|
+
// A line holding only a version is the CLI's answer ("npm notice" lines carry versions too).
|
|
52
|
+
const VERSION_LINE = /^\s*(\d+\.\d+\.\d+\S*)\s*$/m;
|
|
53
|
+
const answered = (r) => r.status === 0 || VERSION_LINE.test(r.out);
|
|
54
|
+
|
|
44
55
|
function checkCli() {
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
let r = capture(WIX[0], [...WIX.slice(1), '--version']);
|
|
57
|
+
// On a cold npm cache npx can exit non-zero after the version, the install's warnings around
|
|
58
|
+
// it; a second run finds the cache warm.
|
|
59
|
+
if (!answered(r)) r = capture(WIX[0], [...WIX.slice(1), '--version']);
|
|
60
|
+
if (!answered(r)) {
|
|
61
|
+
fail('cli_unreachable', { detail: (r.out.trim() || String(r.error?.message || `exit ${r.status}`)).slice(0, 400) });
|
|
62
|
+
}
|
|
47
63
|
// npx interleaves "npm notice …" lines with the version, so don't just take the
|
|
48
|
-
// last line — pick the first semver-looking token
|
|
49
|
-
const version = (r.out.match(/\d+\.\d+\.\d+[^\s]*/) || [])[0] || r.out.trim().split('\n').pop();
|
|
64
|
+
// last line — pick the version line, else the first semver-looking token.
|
|
65
|
+
const version = r.out.match(VERSION_LINE)?.[1] || (r.out.match(/\d+\.\d+\.\d+[^\s]*/) || [])[0] || r.out.trim().split('\n').pop();
|
|
50
66
|
emit('cli_ok', { version });
|
|
51
67
|
}
|
|
52
68
|
|
|
@@ -108,7 +124,8 @@ function startLogin() {
|
|
|
108
124
|
detached: true,
|
|
109
125
|
stdio: ['ignore', out, out],
|
|
110
126
|
env: AGENT_ENV,
|
|
111
|
-
shell:
|
|
127
|
+
shell: NPX.shell,
|
|
128
|
+
windowsHide: true,
|
|
112
129
|
});
|
|
113
130
|
child.on('error', (e) => fail('login_failed', { detail: String(e) }));
|
|
114
131
|
writeFileSync(PIDFILE, JSON.stringify({ pid: child.pid, startedAt: Date.now() }));
|
|
@@ -127,7 +144,7 @@ async function waitForCode(timeoutMs = 60000) {
|
|
|
127
144
|
}
|
|
128
145
|
const detail = existsSync(LOG) ? readFileSync(LOG, 'utf8').trim().slice(-1500) : '';
|
|
129
146
|
fail('login_failed', {
|
|
130
|
-
detail: detail || `wix login
|
|
147
|
+
detail: detail || `wix login wrote nothing within ${timeoutMs / 1000}s; \`${['npx', '-y', WIX.at(-1), 'login'].join(' ')}\` run in the foreground prints the code.`,
|
|
131
148
|
});
|
|
132
149
|
}
|
|
133
150
|
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
// what future deploys copy and what the agent reads, not `src/`. Nothing here touches `src/`.
|
|
14
14
|
import { mkdtempSync, readdirSync, rmSync } from "node:fs";
|
|
15
15
|
import { tmpdir } from "node:os";
|
|
16
|
-
import { join, relative } from "node:path";
|
|
16
|
+
import { basename, join, relative } from "node:path";
|
|
17
17
|
import { spawnSync } from "node:child_process";
|
|
18
18
|
import { SKILL_ROOT, TEMPLATES_PATH, installSource, templatesDir, templatesSource } from "./templates.mjs";
|
|
19
19
|
|
|
20
20
|
const git = (args, opts = {}) => spawnSync("git", args, { encoding: "utf8", timeout: 120_000, ...opts });
|
|
21
|
-
const skillName = SKILL_ROOT
|
|
21
|
+
const skillName = basename(SKILL_ROOT);
|
|
22
22
|
const { repo } = installSource();
|
|
23
23
|
|
|
24
24
|
const out = { skill: {}, templates: {}, update: {} };
|