@wix/agent-skills 1.31.1 → 1.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/README.md +4 -12
  2. package/package.json +1 -1
  3. package/skills/wix-app/SKILL.md +39 -15
  4. package/skills/wix-app/references/BACKEND_API.md +1 -1
  5. package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +11 -8
  6. package/skills/wix-app/references/SDK_MODULE_MAP.md +1 -1
  7. package/skills/wix-app/references/STORES_VERSIONING.md +24 -2
  8. package/skills/wix-app/references/dashboard-page/DATA_SOURCES.md +9 -13
  9. package/skills/wix-app/references/dashboard-page/IMAGE_RENDERING.md +61 -0
  10. package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +54 -64
  11. package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +14 -21
  12. package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +37 -30
  13. package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +43 -29
  14. package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +40 -39
  15. package/skills/wix-app/references/editor-react-component/PARTS.md +8 -6
  16. package/skills/wix-app/references/editor-react-component/PROPS-VS-CSS.md +22 -2
  17. package/skills/wix-app/scripts/scan-a11y-eslint.cjs +39 -16
  18. package/skills/wix-app/scripts/sdk-curated.json +45 -0
  19. package/skills/wix-app/scripts/sdk-lookup.cjs +1180 -0
  20. package/skills/wix-base44-connector/SKILL.md +4 -2
  21. package/skills/wix-base44-connector/scripts/utils.cjs +5 -2
  22. package/skills/wix-headless-kit/SKILL.md +20 -8
  23. package/skills/wix-headless-kit/guides/api-run.md +92 -68
  24. package/skills/wix-headless-kit/guides/existing-site.md +6 -0
  25. package/skills/wix-headless-kit/install/AGENTS.md +3 -2
  26. package/skills/wix-headless-kit/install/attach.mjs +32 -27
  27. package/skills/wix-headless-kit/install/background.mjs +32 -0
  28. package/skills/wix-headless-kit/install/bootstrap.mjs +28 -11
  29. package/skills/wix-headless-kit/install/check.mjs +2 -2
  30. package/skills/wix-headless-kit/install/context.mjs +49 -5
  31. package/skills/wix-headless-kit/install/deploy.mjs +33 -8
  32. package/skills/wix-headless-kit/install/env-ignore.mjs +16 -0
  33. package/skills/wix-headless-kit/install/install.mjs +95 -0
  34. package/skills/wix-headless-kit/install/pins.json +1 -1
  35. package/skills/wix-headless-kit/install/proc.mjs +46 -0
  36. package/skills/wix-headless-kit/install/setup.mjs +16 -25
  37. package/skills/wix-headless-kit/install/templates.mjs +2 -2
  38. package/skills/wix-headless-templates/blog/app/components/blog/RichContent.tsx +3 -2
  39. package/skills/wix-headless-templates/blog/project/package-lock.json +1287 -1299
  40. package/skills/wix-headless-templates/blog/project/package.json +1 -1
  41. package/skills/wix-headless-templates/blog/project/src/components/blog/RichContent.tsx +3 -2
  42. package/skills/wix-headless-templates/blog/rest/authors.ts +5 -5
  43. package/skills/wix-headless-templates/blog/rest/comments.ts +4 -4
  44. package/skills/wix-headless-templates/blog/rest/post-likes.ts +3 -3
  45. package/skills/wix-headless-templates/blog/rest/posts.ts +4 -4
  46. package/skills/wix-headless-templates/blog/rest/taxonomy.ts +5 -5
  47. package/skills/wix-headless-templates/blog/seed/seed-blog.mjs +2 -1
  48. package/skills/wix-headless-templates/bookings/rest/booking.ts +4 -4
  49. package/skills/wix-headless-templates/bookings/rest/services.ts +4 -4
  50. package/skills/wix-headless-templates/bookings/seed/seed-bookings.mjs +2 -1
  51. package/skills/wix-headless-templates/cms/rest/collections.ts +3 -3
  52. package/skills/wix-headless-templates/cms/rest/items.ts +4 -4
  53. package/skills/wix-headless-templates/cms/seed/seed-cms.mjs +2 -1
  54. package/skills/wix-headless-templates/donations/rest/campaigns.ts +4 -4
  55. package/skills/wix-headless-templates/donations/rest/donate.ts +3 -3
  56. package/skills/wix-headless-templates/donations/seed/seed-donations.mjs +2 -1
  57. package/skills/wix-headless-templates/events/rest/events.ts +4 -4
  58. package/skills/wix-headless-templates/events/rest/orders.ts +4 -4
  59. package/skills/wix-headless-templates/events/rest/registration.ts +4 -4
  60. package/skills/wix-headless-templates/events/seed/seed-events.mjs +2 -1
  61. package/skills/wix-headless-templates/faq/rest/faq.ts +4 -4
  62. package/skills/wix-headless-templates/faq/seed/seed-faq.mjs +2 -1
  63. package/skills/wix-headless-templates/forms/rest/forms.ts +4 -4
  64. package/skills/wix-headless-templates/forms/rest/submissions.ts +4 -4
  65. package/skills/wix-headless-templates/forms/seed/seed-forms.mjs +2 -1
  66. package/skills/wix-headless-templates/members/rest/auth.ts +3 -3
  67. package/skills/wix-headless-templates/members/rest/members.ts +4 -4
  68. package/skills/wix-headless-templates/members/seed/seed-members.mjs +2 -1
  69. package/skills/wix-headless-templates/portfolio/rest/portfolio.ts +4 -4
  70. package/skills/wix-headless-templates/portfolio/seed/seed-portfolio.mjs +2 -1
  71. package/skills/wix-headless-templates/pricing-plans/rest/plan-access.ts +3 -3
  72. package/skills/wix-headless-templates/pricing-plans/rest/plans.ts +4 -4
  73. package/skills/wix-headless-templates/pricing-plans/rest/purchase.ts +3 -3
  74. package/skills/wix-headless-templates/pricing-plans/seed/seed-pricing-plans.mjs +2 -1
  75. package/skills/wix-headless-templates/rentals/rest/rentals.ts +4 -4
  76. package/skills/wix-headless-templates/rentals/seed/seed-rentals.mjs +2 -1
  77. package/skills/wix-headless-templates/restaurants/rest/menu.ts +4 -4
  78. package/skills/wix-headless-templates/restaurants/rest/ordering.ts +5 -5
  79. package/skills/wix-headless-templates/restaurants/rest/reservations.ts +4 -4
  80. package/skills/wix-headless-templates/restaurants/seed/seed-restaurants.mjs +2 -1
  81. package/skills/wix-headless-templates/shared/capabilities/site-search/rest/search.ts +4 -4
  82. package/skills/wix-headless-templates/shared/capabilities/site-search/seed/install.mjs +6 -4
  83. package/skills/wix-headless-templates/shared/seed/main.mjs +15 -0
  84. package/skills/wix-headless-templates/shared/seed/wix-cli.mjs +43 -11
  85. package/skills/wix-headless-templates/storefront/INSTRUCTIONS.md +1 -1
  86. package/skills/wix-headless-templates/storefront/rest/cart.ts +5 -5
  87. package/skills/wix-headless-templates/storefront/rest/catalog.ts +5 -5
  88. package/skills/wix-headless-templates/storefront/seed/SEED.md +27 -9
  89. package/skills/wix-headless-templates/storefront/seed/seed-store.mjs +2 -1
  90. package/skills/wix-manage/SKILL.md +31 -9
  91. package/skills/wix-manage/references/app-installation/install-wix-apps.md +5 -1
  92. package/skills/wix-manage/references/app-installation/list-installed-apps.md +19 -9
  93. package/skills/wix-manage/references/automations/actions.md +134 -0
  94. package/skills/wix-manage/references/automations/activation-status.md +64 -48
  95. package/skills/wix-manage/references/automations/api-catalog.md +213 -77
  96. package/skills/wix-manage/references/automations/automation-model.md +199 -0
  97. package/skills/wix-manage/references/automations/bracket-expressions.md +132 -0
  98. package/skills/wix-manage/references/automations/build-and-manage-automations.md +305 -26
  99. package/skills/wix-manage/references/automations/triggers.md +190 -0
  100. package/skills/wix-manage/references/bookings/booking-service-policy-setup.md +3 -0
  101. package/skills/wix-manage/references/bookings/external-calendar-integration.md +3 -0
  102. package/skills/wix-manage/references/bookings/multi-resource-service-creation.md +3 -0
  103. package/skills/wix-manage/references/cms/cms-data-items-crud.md +11 -2
  104. package/skills/wix-manage/references/cms/cms-publishing-flow.md +63 -16
  105. package/skills/wix-manage/references/collaborators/manage-site-collaborators.md +128 -0
  106. package/skills/wix-manage/references/forms/forms-dashboard-navigation.md +3 -1
  107. package/skills/wix-manage/references/forms/read-forms-and-submissions.md +277 -0
  108. package/skills/wix-manage/references/forms/update-form.md +8 -0
  109. package/skills/wix-manage/references/pricing-plans/create-and-update-pricing-plans.md +3 -0
  110. package/skills/wix-manage/references/restaurants/wix-restaurants-setup.md +3 -0
  111. package/skills/wix-manage/references/seo/manage-url-redirects.md +33 -4
  112. package/skills/wix-manage/references/sites/create-headless-site.md +16 -50
  113. package/skills/wix-manage/references/sites/manage-oauth-apps.md +4 -4
  114. package/skills/wix-manage/references/sites/read-site-context.md +4 -0
  115. package/skills/wix-manage/references/sites/upload-static-site.md +368 -261
  116. package/skills/wix-manage/references/stores/add-store-pages-to-site.md +3 -0
  117. package/skills/wix-manage/references/stores/change-store-currency.md +3 -0
  118. package/skills/wix-manage/references/stores/create-product-catalog-v3.md +3 -0
  119. package/skills/wix-manage/references/stores/find-products-query-and-search-catalog-v3.md +3 -0
  120. package/skills/wix-manage/references/stores/update-product-with-options.md +192 -22
  121. package/skills/wix-vibe-headless/references/shared/app/components/WixManageBanner.jsx +3 -3
@@ -111,7 +111,7 @@ const wx = require(require("path").resolve(P));
111
111
  - `wx.bash(cmd)` — shell over saved files (GNU grep/sed; awk is mawk; no rg)
112
112
  - `wx.spec(docsUrl | code)` — a method's exact schema, plus the titles of the docs' own request examples saved at `examplesPath`; pass a hit's docsUrl (direct load), or raw code to query the index yourself
113
113
  - `wx.mgmtRecipes(q?)` — management-recipe index; no arg → categories, a word → matching recipes
114
- - `wx.ensureOAuthApp(token, { name, redirectUris, redirectDomains })` — the headless OAuth app, found by name or created, with the redirect lists merged into whatever it already allows; returns `{ clientId, created }`. `clientId` is what the browser mints visitor tokens from. Run it whenever `context` reports no OAuth app, or before any Wix-hosted flow that returns to this app — see Visitor authentication and Wix-hosted flows
114
+ - `wx.ensureOAuthApp(token, { name, redirectUris, redirectDomains })` — the headless OAuth app, found by name or created with `origin: "base44-app"`, with the redirect lists merged into whatever it already allows; returns `{ clientId, created }`. `clientId` is what the browser mints visitor tokens from. Run it whenever `context` reports no OAuth app, or before any Wix-hosted flow that returns to this app — see Visitor authentication and Wix-hosted flows
115
115
  - `wx.installApp(appDefId, siteId, token)` — install a Wix app on the site (Apps Installer). If discovery finds an API whose app isn't installed on the site, install it first — that's a one-call prerequisite, **not** a reason to fall back to a hand-built alternative. `appDefId` from `search` or the Apps-Created-by-Wix table; `siteId` from `context` (the site report)
116
116
 
117
117
  Search makes one combined request across REST, management recipes, and Headless by default.
@@ -363,7 +363,9 @@ creating the OAuth app when the site has none and adding your URLs to its lists
363
363
  has one. Returns only complete for URLs in those lists, and a missing one breaks silently —
364
364
  create-checkout and the anonymous token still succeed — until a real buyer is redirected and
365
365
  stranded on Wix. It is idempotent by name: never create a second OAuth app for the same Base44
366
- app, or the first `clientId`'s returns stop working.
366
+ app, or the first `clientId`'s returns stop working. The app is created with `origin: "base44-app"`,
367
+ the name of this flow on the OAuth app; `origin` is set at creation only, so an OAuth app created
368
+ by hand carries the same value in its create call.
367
369
 
368
370
  ```js
369
371
  // Register BOTH of this app's own URLs — its preview URL and its published URL — so returns work
@@ -432,7 +432,10 @@ async function installApp(appDefId, siteId, token) {
432
432
  // return needs. Idempotent by name — a site reached by the "connect an existing site" flow
433
433
  // has none, and creating a second one for the same app strands the first app's returns.
434
434
  // Redirect lists are merged, never replaced: an app that already serves a custom domain
435
- // keeps it when a preview URL is added.
435
+ // keeps it when a preview URL is added. `origin` names the flow that created the app; it is
436
+ // written once, at creation, and an update leaves it as it is.
437
+ const OAUTH_APP_ORIGIN = "base44-app";
438
+
436
439
  async function ensureOAuthApp(token, { name, redirectUris = [], redirectDomains = [] }) {
437
440
  const { oAuthApps = [] } = await post(
438
441
  "https://www.wixapis.com/oauth-app/v1/oauth-apps/query", { query: {} }, token);
@@ -440,7 +443,7 @@ async function ensureOAuthApp(token, { name, redirectUris = [], redirectDomains
440
443
  const merge = (a = [], b = []) => [...new Set([...a, ...b])];
441
444
  if (!existing) {
442
445
  const { oAuthApp } = await post("https://www.wixapis.com/oauth-app/v1/oauth-apps", {
443
- oAuthApp: { name, allowedRedirectUris: redirectUris, allowedRedirectDomains: redirectDomains },
446
+ oAuthApp: { name, allowedRedirectUris: redirectUris, allowedRedirectDomains: redirectDomains, origin: OAUTH_APP_ORIGIN },
444
447
  }, token);
445
448
  return { clientId: oAuthApp.id, created: true, oAuthApp };
446
449
  }
@@ -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 supports **Astro 5**: a
147
- project on another major is pinned to 5 first, or connected as a React host.
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 ONE `npm install`; a change is file edits; then release | no |
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 `npm ci --ignore-scripts || npm install
244
- --ignore-scripts` (**never a second npm install concurrently**: two npms in one
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
- (`node_modules/.package-lock.json`) and the seed's (`.seed-exit`) both exist — **verify the
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
@@ -285,7 +294,10 @@ only after an explicit yes, never automatically. Step 5 ends with the same self-
285
294
  `https://manage.wix.com/dashboard/<siteId>` (the `siteId` of the `ready_for_brand_layer`
286
295
  event: on a migration preview that is the migrated site's dashboard, the release URL is the
287
296
  preview's, the original site is unchanged, and completing the migration is the user's next
288
- step in the Wix CLI once they approve — say all three). When the site takes money through
297
+ step in the Wix CLI once they approve — say all three). Tell the user how to put their own
298
+ domain on the site, per the `wix-manage` recipe `references/domains/domain-search-purchase-and-connect.md`
299
+ (installed beside this skill, else `https://www.wix.com/skills/wix-manage/references/domains/domain-search-purchase-and-connect.md`):
300
+ bought through Wix, or one they already own. When the site takes money through
289
301
  hosted checkout (a cart, a paid booking or rental, tickets, plans, donations) and nothing says
290
302
  payments are already set up, say what a visitor meets at checkout until they are — "We
291
303
  can't accept online payments. Contact us for help with your order." — and hand the two links
@@ -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. It walks the run of
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`, or in a plugin's folder. Read them from disk when they are there. Online, they are
29
- published alongside each other under the same names (the registry also serves each without its
30
- `wix-` prefix, `headless-templates` for `wix-headless-templates`, which is the `base` its manifest
31
- shows; both forms resolve), each with a manifest of its files, and in the
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
- when it is connected: a client's web fetch may open only URLs the user gave or a search returned.
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
@@ -57,7 +76,7 @@ A brief that needs no solution (a game, a landing page, a tool) is a run of two
57
76
  ## 2. The site
58
77
 
59
78
  The CLI's `npm create @wix/new` is one call: `POST /headless-business-setup/v1/headless-business/provision`,
60
- with `origin: agent-<your-id>`, a name, and the solutions to install as `seedOptions`. The recipe
79
+ with `origin: "drop"`, a name, and the solutions to install as `seedOptions`. The recipe
61
80
  is `{MANAGE}/references/sites/create-headless-site.md`; it returns the `metaSiteId` and the
62
81
  site's OAuth client `appId`. Do not create a site any other way: a project from Create Project or
63
82
  a template is neither headless nor publishable, and a later run with a machine cannot attach to it.
@@ -75,32 +94,26 @@ 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, and the traps the script
79
- encodes (a Bookings service needs a category to be visible and takes resource ids, not staff ids;
80
- a Forms field is registered by its `validation` block; a product's choice photos are linked after
81
- the gallery holds them; right after a bulk create a product's own stock summary can still read
82
- `OUT_OF_STOCK` while its inventory items are already stocked, so check the items (Query Inventory
83
- Items) and create only the missing ones, as `SEED.md` says, where `ALREADY_EXISTS` means stocked; stock that isn't counted, such as "made to order", is
84
- `inStock: true`, never a missing quantity, which leaves the product unbuyable). Read `SEED.md`
85
- before the first create call: it maps the brief's words to the fields. Those fields are the
86
- script's input, not the request: the body each create sends is built in the script (for products,
87
- `bulkCreateProducts` in `seed-store.mjs`: the description as rich-text nodes from `mkDesc`,
88
- `productType` with `physicalProperties`, the stock as `inventoryItem`), so read that function before
89
- the first create. On a site this run created, the store's currency is `SEED.md`'s `currency` rule,
90
- the one the brief or the user's own pages state (a page priced "$68" is USD), set before the
91
- products and without asking; a confirmation before a currency change is for a store the user
92
- already runs. Then read the script for any call `SEED.md` only names, and make the same
93
- calls through `{MANAGE}`'s recipe for each (services, products, posts, events, collections). Keep
94
- the script's rules: create, never delete, and report what the site already held. A photo already in
95
- the site's Media Manager, such as one the user uploaded, goes in by its file id (`imageMediaId` in
96
- `SEED.md`), as it is: an import by URL would copy it. A photo on a site the user dropped is served
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. Write the seed as your own `ExecuteWixAPI` script, its calls made on the
99
+ new site: `wix.request({ scope: "site", siteId: <the metaSiteId>, … })`. Read `SEED.md` before
100
+ the first create: it maps the brief's words to the fields. Build each request body in your script the
101
+ way the script's builder functions do. A call `SEED.md` only names is made through `{MANAGE}`'s recipe for
102
+ it. Keep the script's rules: create, never delete, and report what the site already held. The closing
103
+ names entities you drafted as placeholders.
104
+
105
+ A photo already in the site's Media Manager, such as one the user uploaded, goes in by its file id
106
+ (`imageMediaId` in `SEED.md`), as it is: an import by URL would copy it. A photo on a site the user
107
+ dropped is served with the drop, not from the Media Manager: it goes in by its live URL, imported
108
+ once through `{MANAGE}/references/media/upload-media-to-wix.md`, and the file id the import returns
109
+ is what the entity takes. An `imagePrompt` is generated through
110
+ `{MANAGE}/references/media/generate-image-with-ai.md` (`POST /runwareschemaless/v1/request`, the
111
+ body `generateImage` in `{TEMPLATES}/shared/seed/images.mjs` builds), then imported through
112
+ `upload-media-to-wix.md`, and the file id goes on the entity, as for a dropped photo; this is the
113
+ seed's default for every entity that shows a picture. An entity stays text-only only when generation
114
+ or the import fails; say which. The scripts read a response body directly; `ExecuteWixAPI`'s
115
+ `wix.request` returns `{ status, data }`, and the body is `data`. A generated image is billed when
116
+ it is generated, so a read of the wrong level discards a paid image.
104
117
 
105
118
  ## 5. The frontend
106
119
 
@@ -108,6 +121,9 @@ The run is not done until the pages use what the backend holds: every control th
108
121
  it (add to cart, the cart, book, submit) works on the live site. A backend with untouched pages is
109
122
  half a run.
110
123
 
124
+ A payment (a cart, a paid booking, a ticket, a plan, a donation) ends in Wix's hosted checkout,
125
+ reached through a redirect session, and is wired on the first pass, payments connected or not.
126
+
111
127
  The Astro pages cannot be released here: that is a run with a machine and a CLI login, which
112
128
  attaches to this site (`guides/existing-site.md`) rather than making a new one. What ships from here
113
129
  is static files, dropped onto the site from step 2 through
@@ -127,49 +143,57 @@ takes the complete file set in one call and a response has a hard size limit; th
127
143
  later" section downloads what the site serves, so a later drop adds files to a live set without
128
144
  resending what is already there.
129
145
 
130
- **When it cannot**, or when its output would all have to be written out by hand, a page that loads
131
- 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
146
+ **When it cannot**, or when its output would all have to be written out by hand, a page that talks
147
+ to the site as a visitor over plain `fetch`. One file per call, a few hundred lines, written in full inside that call: a response has a
132
148
  hard size limit, and a call that carries the whole frontend at once exceeds it. A second file joins
133
149
  the first the recipe's way, "Change it later": the same call downloads the live set and drops the
134
150
  union. The
135
- SDK comes from a pinned package URL, `https://esm.sh/@wix/sdk@{version}` and the solution's package
136
- (`@wix/bookings`, `@wix/stores`, `@wix/blog`, …); the client is
137
- `createClient({ modules: { … }, auth: OAuthStrategy({ clientId }) })` with the `appId` the provision
138
- call returned, and the SDK mints and refreshes the visitor token itself. Markup, a render function
151
+ page calls the Wix REST API with `fetch` and mints the visitor token itself with the `appId` the
152
+ provision call returned, as `{TEMPLATES}/shared/rest/client.ts` does. Markup, a render function
139
153
  per state, the event handlers: that is what you write. Keep what the page shows to what the site
140
154
  holds; nothing invented, as in SKILL.md step 4.
141
155
 
142
- **The calls**, on that second path, take their shape from the kit's own transport for the solution,
143
- `{TEMPLATES}/{solution}/app/wix/{solution}/*.ts` (`services.ts`, `booking.ts`, `catalog.ts`, …,
144
- with `{TEMPLATES}/shared/app/wix/sdk.ts` for the client wiring): those files call the same SDK
145
- modules the page imports, and they are verified. Copy the form of the call, not the file. A page
146
- that talks to Wix over plain `fetch`, with no SDK, takes them from the REST twins instead:
156
+ **The calls**, on that second path, take their shape from the kit's REST twins for the solution:
147
157
  `{TEMPLATES}/shared/rest/client.ts` (the visitor token and the request) and
148
- `{TEMPLATES}/{solution}/rest/*.ts` (the calls; for a store, `storefront/rest/cart.ts` and
149
- `storefront/rest/catalog.ts`). Read those files by name; the manifest and the solution's whole
150
- `INSTRUCTIONS.md` are not needed to find them. The
151
- cores beside them (`*-core.ts`) hold the rules the page needs and nothing else does: which fields
152
- to read, how a price or a duration is formatted, which slot is bookable, how an id is read. Copy
153
- those core functions into the page as they are, with only the types removed (`cart-core.ts`'s
154
- `toLine` and `toCart`, the price formatter, `rawId`), and run every API response through them: a
155
- raw field read by hand is where a page breaks (a product name is a translatable object, a cart
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.
158
+ `{TEMPLATES}/{solution}/rest/*.ts` (the calls), each importing its helpers by their real paths.
159
+ They are verified. Copy the form of the call, not the file.
160
+ The cores,
161
+ `{TEMPLATES}/{solution}/app/wix/{solution}/*-core.ts`, hold the rules the page needs and nothing else
162
+ does: which fields to read, how a price or a duration is formatted, which slot is bookable, how an
163
+ id is read. Copy those core functions into the page as they are, with only the types removed, and
164
+ run every API response through them: a raw field read by hand is where a page breaks (a name can be
165
+ a translatable object, a quantity a nested one). What you write is the markup, the rendering of what
166
+ the cores return, and the event handlers.
158
167
 
159
168
  **A call those files don't cover** is confirmed before it is written, the way `{DOCS}` describes:
160
- the method's SDK page, and when the page and the package disagree, the package's own type
161
- declarations (the `@wix/auto_sdk_{solution}_<module>` package the solution package depends on,
162
- its `index.d.ts`) are the truth. Two shapes that recur, as examples of what the types settle and
163
- the kit's files already encode: SDK query methods return a query builder finished with `.find()`;
164
- 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. A page cannot be
166
- opened here, so a wrong shape fails silently in the browser. Check the data code before the upload:
167
- get a real response through `ExecuteWixAPI` for each call the page makes, run the page's data code
168
- on it in your sandbox's Node, and look for names, prices and quantities in the output, not
169
- `[object Object]` or `undefined`. The cart's calls run as a visitor, which `wix.request` (the
170
- owner) is not: in the script, mint a visitor token with the site's public client id as
171
- `shared/rest/client.ts` does (`POST https://www.wixapis.com/oauth2/token`, `grantType: "anonymous"`),
172
- send the cart calls with it through `fetch`, add one product and read the cart back.
169
+ the method's REST page, and its schema (SearchWixAPISpec on the method's docs URL) for the exact
170
+ request and response. A shape that recurs, as an example of what the kit's files already encode:
171
+ an entity's id arrives as `_id` on some objects and `id` on others (the kit reads both,
172
+ `rawId`). Dates travel as local wall-clock strings in the business time zone.
173
+
174
+ **The check**, before every upload: a page cannot be opened here, so a wrong shape fails silently in
175
+ the browser. In one `ExecuteWixAPI` script, make each control's calls the way the page makes them,
176
+ as a visitor, with the bodies from `{solution}/rest/*.ts`. Then read the solution's cores,
177
+ `{TEMPLATES}/{solution}/app/wix/{solution}/*-core.ts` (online, that folder's `_content` returns them
178
+ in one response), and run each response through them in your sandbox's Node, as the page does:
179
+
180
+ ```js
181
+ async function () {
182
+ const api = "https://www.wixapis.com", clientId = "<appId>";
183
+ const post = (path, body, token) => fetch(api + path, { method: "POST",
184
+ headers: { "Content-Type": "application/json", ...(token && { Authorization: token }) },
185
+ body: JSON.stringify(body) }).then(async (r) => ({ path, status: r.status, data: await r.json() }));
186
+ const { data: { access_token: token } } = await post("/oauth2/token", { clientId, grantType: "anonymous" });
187
+ const results = [];
188
+ // each call the control makes, in order: results.push(await post(path, body, token));
189
+ // a payment's last call is the redirect session the solution's rest/*.ts creates
190
+ return results;
191
+ }
192
+ ```
193
+
194
+ Done when every call returns 200, the cores turn each response into the names, prices, quantities or
195
+ times the page shows, and a payment's redirect session returns a URL. The upload waits for that: a
196
+ failing call is fixed first, on the site or in the page. The closing reports the check.
173
197
 
174
198
  ## 6. Closing
175
199
 
@@ -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/` (by the Wix plugin, or by
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 { spawn, spawnSync, execFileSync } from "node:child_process";
50
- import { cpSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync, readdirSync } from "node:fs";
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 ONE npm install");
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 = spawnSync("npx", ["-y", WIX_CLI, "token", ...(site ? ["--site", site] : [])], { encoding: "utf8", timeout: 120_000 });
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.stderr || r.stdout || `no token — is the Wix CLI logged in? (npx ${WIX_CLI} whoami)`).slice(-400));
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
- const pkgPath = join(projectDir, "package.json");
281
- const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
282
- pkg.name = folderName;
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.stderr || deploy.stdout);
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
- const logFd = openSync(installLog, "a");
327
- const child = spawn("sh", ["-c", existsSync(join(projectDir, "package-lock.json")) ? "npm ci --ignore-scripts || npm install --ignore-scripts" : "npm install --ignore-scripts"], { cwd: projectDir, detached: true, stdio: ["ignore", logFd, logFd] });
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
- const seedChild = spawn(
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
+ }