@wix/agent-skills 1.31.0 → 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/SKILL.md +7 -8
- package/skills/wix-app/references/DASHBOARD_PAGE.md +1 -1
- package/skills/wix-app/references/STORES_VERSIONING.md +1 -1
- package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +11 -50
- package/skills/wix-app/references/dashboard-page/DATA_SOURCES.md +14 -10
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE.md +5 -8
- package/skills/wix-app/references/dashboard-page/TABLE_STATE.md +5 -4
- package/skills/wix-headless-kit/SKILL.md +31 -14
- package/skills/wix-headless-kit/guides/api-run.md +116 -31
- package/skills/wix-headless-kit/guides/existing-site.md +9 -2
- package/skills/wix-headless-kit/guides/reference-mode.md +9 -1
- package/skills/wix-headless-kit/install/AGENTS.md +10 -7
- package/skills/wix-headless-kit/install/attach.mjs +35 -30
- 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 +62 -8
- package/skills/wix-headless-kit/install/deploy.mjs +36 -11
- 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 +18 -27
- package/skills/wix-headless-kit/install/templates.mjs +4 -3
- 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.md +1 -1
- package/skills/wix-headless-templates/blog/seed/seed-blog.mjs +4 -3
- 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.md +1 -1
- package/skills/wix-headless-templates/bookings/seed/seed-bookings.mjs +4 -2
- 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.md +1 -1
- package/skills/wix-headless-templates/donations/seed/seed-donations.mjs +5 -3
- 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.md +1 -1
- package/skills/wix-headless-templates/events/seed/seed-events.mjs +5 -3
- 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.md +4 -2
- package/skills/wix-headless-templates/portfolio/seed/seed-portfolio.mjs +8 -7
- 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.md +1 -1
- package/skills/wix-headless-templates/rentals/seed/seed-rentals.mjs +4 -2
- 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.md +1 -1
- package/skills/wix-headless-templates/restaurants/seed/seed-restaurants.mjs +6 -4
- 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/images.mjs +41 -5
- 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 +44 -14
- package/skills/wix-headless-templates/storefront/seed/seed-store.mjs +23 -15
- package/skills/wix-manage/SKILL.md +28 -18
- 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/automations/activation-status.md +52 -0
- package/skills/wix-manage/references/automations/api-catalog.md +81 -0
- package/skills/wix-manage/references/automations/build-and-manage-automations.md +34 -0
- package/skills/wix-manage/references/bookings/booking-service-policy-setup.md +3 -0
- package/skills/wix-manage/references/bookings/create-and-update-booking-services.md +277 -363
- 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/custom-apps/use-app-skills-and-app-tools.md +3 -1
- 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/send-feedback-to-wix.md +92 -0
- package/skills/wix-manage/references/sites/upload-static-site.md +298 -283
- 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/skills/wix-manage/references/bookings/create-appointment-service.md +0 -243
- package/skills/wix-manage/references/bookings/create-booking-service-from-prompt.md +0 -31
- package/skills/wix-manage/references/bookings/create-class-service.md +0 -222
- package/skills/wix-manage/references/bookings/create-course-service.md +0 -251
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
# The kit's run as Wix API calls
|
|
2
2
|
|
|
3
|
-
Read when the commands this skill is built on cannot run where you are, or stopped:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
Read when the commands this skill is built on cannot run where you are, or stopped: your shell
|
|
4
|
+
can't reach Wix, there is no shell, or the install, the
|
|
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. 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
|
|
9
|
+
SKILL.md step by step and names, for each, the Wix API call the CLI or the script performs and the
|
|
10
|
+
file beside this skill that carries the contract; where a script can run where you are, it says so.
|
|
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.
|
|
8
16
|
|
|
9
17
|
## Where these skills are
|
|
10
18
|
|
|
@@ -13,20 +21,40 @@ This skill is one of a set, and the guide reads from three of them:
|
|
|
13
21
|
- `{SKILL_ROOT}` — this skill, `wix-headless-kit`: the run, its guides, the Verticals table.
|
|
14
22
|
- `{TEMPLATES}` — `wix-headless-templates`: the shipped, verified code per Wix Business Solution,
|
|
15
23
|
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`).
|
|
17
|
-
fetched to
|
|
24
|
+
`SEED.md`) and `rest/` (the data layer over plain `fetch`). Not every install carries it (a
|
|
25
|
+
plugin may leave it out): it is installed beside this skill, or fetched to
|
|
26
|
+
`{SKILL_ROOT}/templates/` by `node {SKILL_ROOT}/install/templates.mjs` (a git clone), or installed
|
|
27
|
+
with `npx skills@latest add wix/skills --skill wix-headless-templates`; with none of those, read
|
|
28
|
+
online at `https://www.wix.com/skills/wix-headless-templates/{path}`.
|
|
18
29
|
- `{MANAGE}` — `wix-manage`: REST recipes to configure and manage a site's business solutions, one
|
|
19
30
|
recipe per operation with the exact endpoint and payload; its `SKILL.md` is the index.
|
|
20
31
|
- `{DOCS}` — `wix-docs`: how to look up the Wix API and SDK documentation and confirm a method's
|
|
21
32
|
exact shape before writing a call.
|
|
22
33
|
|
|
23
|
-
Installed, they sit beside each other under `.agents/skills
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
Installed, they sit beside each other wherever your client keeps them: under `.agents/skills/` after
|
|
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
|
|
41
|
+
`wix/skills` repository on GitHub under `skills/{skill}/`; a sandbox that reaches GitHub has the
|
|
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.
|
|
27
54
|
|
|
28
55
|
What such a run does not produce: the Astro build and its release. Those need a machine with the
|
|
29
|
-
CLI. Everything else the kit does to a site, it does through calls you can make
|
|
56
|
+
CLI. Everything else the kit does to a site, it does through calls you can make, the frontend
|
|
57
|
+
included: step 5 ships it as static files.
|
|
30
58
|
|
|
31
59
|
## 0. Identity
|
|
32
60
|
|
|
@@ -42,6 +70,9 @@ Unchanged from SKILL.md step 2 and its Verticals table: the brief names the busi
|
|
|
42
70
|
names the solutions it needs. Read each solution's `{TEMPLATES}/{solution}/INSTRUCTIONS.md` for
|
|
43
71
|
what it covers and `{TEMPLATES}/{solution}/seed/SEED.md` for what a seeded site holds.
|
|
44
72
|
|
|
73
|
+
A brief that needs no solution (a game, a landing page, a tool) is a run of two steps: the site
|
|
74
|
+
(step 2, with no `seedOptions`) and the frontend (step 5). No apps, no seed.
|
|
75
|
+
|
|
45
76
|
## 2. The site
|
|
46
77
|
|
|
47
78
|
The CLI's `npm create @wix/new` is one call: `POST /headless-business-setup/v1/headless-business/provision`,
|
|
@@ -63,23 +94,45 @@ Restaurants, Donations, FAQ), goes on with the Apps Installer:
|
|
|
63
94
|
## 4. The seed
|
|
64
95
|
|
|
65
96
|
`seed-{solution}.mjs` is a sequence of REST calls, and `SEED.md` is its contract: the plan shape
|
|
66
|
-
(what to create, how many, which images), the order the entities need,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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.
|
|
73
116
|
|
|
74
117
|
## 5. The frontend
|
|
75
118
|
|
|
119
|
+
The run is not done until the pages use what the backend holds: every control the page shows for
|
|
120
|
+
it (add to cart, the cart, book, submit) works on the live site. A backend with untouched pages is
|
|
121
|
+
half a run.
|
|
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
|
+
|
|
76
126
|
The Astro pages cannot be released here: that is a run with a machine and a CLI login, which
|
|
77
127
|
attaches to this site (`guides/existing-site.md`) rather than making a new one. What ships from here
|
|
78
128
|
is static files, dropped onto the site from step 2 through
|
|
79
|
-
`{MANAGE}/references/sites/upload-static-site.md
|
|
80
|
-
|
|
129
|
+
`{MANAGE}/references/sites/upload-static-site.md`: the Wix MCP's upload tool, or a script that
|
|
130
|
+
downloads what the site serves, edits it and drops it back, so only what changed is written. Which
|
|
131
|
+
files depends on whether the compose can run, and on what writing them costs: when files reach the
|
|
132
|
+
upload tool as files (its `attachments`), anything on your disk travels as it is; when they don't,
|
|
133
|
+
every file you send is text you write, and the smallest set wins.
|
|
81
134
|
|
|
82
|
-
**When the compose can run** (the skills on disk and
|
|
135
|
+
**When the compose can run** (the skills on disk, Node, and npm reachable, for the TypeScript compiler it fetches), the frontend is the kit's own shipped code. The compose
|
|
83
136
|
`{SKILL_ROOT}/install/deploy.mjs {solution} --stack static --out site --client-id {appId}` fetches the
|
|
84
137
|
templates and writes the solution's REST data layer and stores to `site/js/wix/` as plain ES modules
|
|
85
138
|
(`guides/reference-mode.md`, the static site): the transport, the cores, the state machines, verified,
|
|
@@ -89,9 +142,11 @@ takes the complete file set in one call and a response has a hard size limit; th
|
|
|
89
142
|
later" section downloads what the site serves, so a later drop adds files to a live set without
|
|
90
143
|
resending what is already there.
|
|
91
144
|
|
|
92
|
-
**When it cannot**,
|
|
93
|
-
visitor.
|
|
94
|
-
|
|
145
|
+
**When it cannot**, or when its output would all have to be written out by hand, a page that loads
|
|
146
|
+
the Wix SDK from a package CDN and talks to the site as a visitor. One file per call, a few hundred lines, written in full inside that call: a response has a
|
|
147
|
+
hard size limit, and a call that carries the whole frontend at once exceeds it. A second file joins
|
|
148
|
+
the first the recipe's way, "Change it later": the same call downloads the live set and drops the
|
|
149
|
+
union. The
|
|
95
150
|
SDK comes from a pinned package URL, `https://esm.sh/@wix/sdk@{version}` and the solution's package
|
|
96
151
|
(`@wix/bookings`, `@wix/stores`, `@wix/blog`, …); the client is
|
|
97
152
|
`createClient({ modules: { … }, auth: OAuthStrategy({ clientId }) })` with the `appId` the provision
|
|
@@ -102,9 +157,17 @@ holds; nothing invented, as in SKILL.md step 4.
|
|
|
102
157
|
**The calls**, on that second path, take their shape from the kit's own transport for the solution,
|
|
103
158
|
`{TEMPLATES}/{solution}/app/wix/{solution}/*.ts` (`services.ts`, `booking.ts`, `catalog.ts`, …,
|
|
104
159
|
with `{TEMPLATES}/shared/app/wix/sdk.ts` for the client wiring): those files call the same SDK
|
|
105
|
-
modules the page imports, and they are verified. Copy the form of the call, not the file.
|
|
106
|
-
|
|
107
|
-
|
|
160
|
+
modules the page imports, and they are verified. Copy the form of the call, not the file. A page
|
|
161
|
+
that talks to Wix over plain `fetch`, with no SDK, takes them from the REST twins instead:
|
|
162
|
+
`{TEMPLATES}/shared/rest/client.ts` (the visitor token and the request) and
|
|
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.
|
|
108
171
|
|
|
109
172
|
**A call those files don't cover** is confirmed before it is written, the way `{DOCS}` describes:
|
|
110
173
|
the method's SDK page, and when the page and the package disagree, the package's own type
|
|
@@ -112,9 +175,31 @@ declarations (the `@wix/auto_sdk_{solution}_<module>` package the solution packa
|
|
|
112
175
|
its `index.d.ts`) are the truth. Two shapes that recur, as examples of what the types settle and
|
|
113
176
|
the kit's files already encode: SDK query methods return a query builder finished with `.find()`;
|
|
114
177
|
and an entity's id arrives as `_id` on some objects and `id` on others (the kit reads both,
|
|
115
|
-
`rawId`). Dates travel as local wall-clock strings in the business time zone.
|
|
116
|
-
|
|
117
|
-
|
|
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.
|
|
118
203
|
|
|
119
204
|
## 6. Closing
|
|
120
205
|
|
|
@@ -7,7 +7,8 @@ is the create flow's job on a site made for the run — a folder holding only a
|
|
|
7
7
|
is that case too: `init` made the site; SKILL.md step 3). (A project downloaded from Wix whose `.env.local` declares a
|
|
8
8
|
migration is the other way to arrive at an existing site: `guides/migration.md`; the reading
|
|
9
9
|
part below applies there too, against the migrated site.) `<SKILL_ROOT>` is the installed skill folder; the shipped code is
|
|
10
|
-
|
|
10
|
+
in the folder `node <SKILL_ROOT>/install/templates.mjs` prints (`<templates>` below; SKILL.md, "The
|
|
11
|
+
model").
|
|
11
12
|
|
|
12
13
|
**Read the site, then run attach.** First, one call tells you what
|
|
13
14
|
the site is — its name, currency, and the Wix apps installed on it:
|
|
@@ -26,6 +27,12 @@ shape as setup (SKILL.md step 3):
|
|
|
26
27
|
node <SKILL_ROOT>/install/attach.mjs --site <siteId> --business-name "<site name>" --vertical <vertical>[,<vertical>] [--plan plan.json]
|
|
27
28
|
```
|
|
28
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
|
+
|
|
29
36
|
`init`/`wix create` always create a site, so they are not used here. attach does what they do
|
|
30
37
|
after creating one — the site's OAuth app, Wix hosting, `wix.config.json` — against the site
|
|
31
38
|
given, copies the first vertical's composed template, deploys the rest, and starts the install
|
|
@@ -46,7 +53,7 @@ for: what the chosen verticals will render, roughly how much of it, and what it
|
|
|
46
53
|
six items in three groups design differently from six hundred. Run the vertical's reader:
|
|
47
54
|
|
|
48
55
|
```bash
|
|
49
|
-
node <
|
|
56
|
+
node <templates>/<vertical>/seed/read-site.mjs --site <siteId> [--limit <n>]
|
|
50
57
|
```
|
|
51
58
|
|
|
52
59
|
It prints one JSON: whether the vertical's app is installed, counts, one page of each entity
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# Reference mode: a static site, or a server-rendered app in another language
|
|
2
2
|
|
|
3
3
|
Read when SKILL.md step 1 resolves to a stack that cannot run `app/`. `<SKILL_ROOT>` is the
|
|
4
|
-
installed skill folder; the shipped code is
|
|
4
|
+
installed skill folder; the shipped code is in the folder `node <SKILL_ROOT>/install/templates.mjs`
|
|
5
|
+
prints (the `wix-headless-templates` skill beside this one, or a copy fetched into
|
|
6
|
+
`<SKILL_ROOT>/templates/`), and every `templates/…` path here is relative to it. Steps 2 to 5 of
|
|
5
7
|
SKILL.md still apply; this file is the mechanics that differ.
|
|
6
8
|
|
|
7
9
|
The data layer ships a second time as a **REST layer**: `templates/shared/rest/` (the auth seam
|
|
@@ -31,6 +33,12 @@ two transports: with `--plan` its REST twin and stores land in `js/wix/` beside
|
|
|
31
33
|
to it. Seed per the vertical's `SEED.md` (Node + the CLI token, no project dependencies).
|
|
32
34
|
Release with `npx @wix/cli@latest release` — no build. Item-page tags come from the entity's
|
|
33
35
|
`seoData`, set after the fetch (`document.title`, the meta description).
|
|
36
|
+
- **A dropped site on its Wix Dev Machine** (shape `static-in-astro`). The machine holds the
|
|
37
|
+
dropped pages in `public/` of an Astro starter with no pages of its own, and its Astro config
|
|
38
|
+
redirects `/` to `/index.html`. The pages stay where they are: run
|
|
39
|
+
`deploy.mjs <vertical> --stack static --out public`, which writes `public/js/wix/`, and wire the
|
|
40
|
+
pages in place as above. The config is left as it is, so the release goes through Astro:
|
|
41
|
+
`npx @wix/cli@latest build && npx @wix/cli@latest release`, to the same URL.
|
|
34
42
|
- **Server-rendered, another language (Flask, Laravel, Rails, …).** The same shape as managed
|
|
35
43
|
Astro — pages rendered on the server, the interactive surfaces in the browser — with hosting and
|
|
36
44
|
SEO plumbing theirs. `init` still runs in the project folder; run `deploy.mjs <vertical> --stack
|
|
@@ -17,15 +17,18 @@ 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
|
-
- `{{SKILL}}` — the code in this app and how to extend it. The shipped code
|
|
24
|
+
- `{{SKILL}}` — the code in this app and how to extend it. The shipped code is the
|
|
25
|
+
`wix-headless-templates` skill: `node .agents/skills/{{SKILL}}/install/templates.mjs` prints where it
|
|
26
|
+
is (`.agents/skills/wix-headless-templates/` when installed beside this skill, else
|
|
24
27
|
`.agents/skills/{{SKILL}}/templates/`, fetched once from the skill's repository and kept with this
|
|
25
|
-
project at the commit it was built from
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
adds one; `node
|
|
28
|
+
project at the commit it was built from, `templates/.source`) and fetches it when neither exists;
|
|
29
|
+
`<templates>` below is that folder. Each business solution has a playbook at
|
|
30
|
+
`<templates>/<solution>/INSTRUCTIONS.md`; `node .agents/skills/{{SKILL}}/install/deploy.mjs <solution> --stack {{STACK}}`
|
|
31
|
+
adds one; `node <templates>/<solution>/seed/read-site.mjs` reads what the
|
|
29
32
|
site holds for it, with the documentation URL of every call it made. A code change ends with a release; the live URL shows it, the dev server does not count.
|
|
30
33
|
`node .agents/skills/{{SKILL}}/install/check.mjs` says whether the skill or its templates have a newer
|
|
31
34
|
version and prints the update commands; an update touches the skill folder or `templates/`, never `src/`.
|
|
@@ -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 }));
|
|
@@ -304,7 +315,7 @@ emit("agent_configs", writeAgentsMd(projectDir, { skill: basename(SKILL_ROOT), s
|
|
|
304
315
|
if (mode !== "link" && (stack !== "astro" || mode === "config-only")) {
|
|
305
316
|
emit("ready", { projectDir, siteId, appId, baseUrl, hosting, frontend, stack, dashboardUrl: `https://manage.wix.com/dashboard/${siteId}`,
|
|
306
317
|
next: `scaffold the ${stack} project in this folder per SKILL.md, then deploy.mjs <vertical…> --stack ${stack}${planPath ? ` --plan ${planPath}` : ""} (the client id is read from wix.config.json); ` +
|
|
307
|
-
(planPath ? `then seed with the plan: node
|
|
318
|
+
(planPath ? `then seed with the plan: node ${join(TEMPLATES, verticals[0], "seed", `seed-${verticals[0]}.mjs`)} ${planPath}` : `no plan given, so nothing seeds — the site owns its content`) +
|
|
308
319
|
(hosting === "self" ? `; you host it: origins on the OAuth app allow-list now: ${origins.join(", ") || "none — add them before the first checkout test"}` : "") });
|
|
309
320
|
process.exit(0);
|
|
310
321
|
}
|
|
@@ -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,20 +349,15 @@ 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 {
|
|
351
356
|
if (siteOrigin === "init") {
|
|
352
|
-
emit("note", { step: "seed", detail: `this site was made for this run (init) and is empty: draft a plan per
|
|
357
|
+
emit("note", { step: "seed", detail: `this site was made for this run (init) and is empty: draft a plan per ${join(TEMPLATES, verticals[0], "seed", "SEED.md")} — the brief's content, else demo content — and run node <SKILL_ROOT>/templates/${verticals[0]}/seed/seed-<vertical>.mjs plan.json from the project root` });
|
|
353
358
|
}
|
|
354
359
|
if (verticals.includes("members")) {
|
|
355
|
-
emit("note", { step: "seed", detail:
|
|
360
|
+
emit("note", { step: "seed", detail: `members: the Members Area app (the profile layer, no content) is installed by ${join(TEMPLATES, "members", "seed", "seed-members.mjs")} — run it unless the site already has the app` });
|
|
356
361
|
}
|
|
357
362
|
}
|
|
358
363
|
|
|
@@ -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: {} };
|