@wix/agent-skills 1.20.0 → 1.21.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 (183) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/SKILL.md +14 -112
  3. package/skills/wix-app/references/APP_TOOLS.md +2 -1
  4. package/skills/wix-app/references/BACKEND_EVENT.md +3 -3
  5. package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +41 -152
  6. package/skills/wix-app/references/DASHBOARD_MODAL.md +4 -4
  7. package/skills/wix-app/references/SDK_MODULE_MAP.md +44 -0
  8. package/skills/wix-app/references/SITE_PLUGIN.md +36 -190
  9. package/skills/wix-app/references/app-tools/TOOLS.md +3 -1
  10. package/skills/wix-app/references/custom-element-widget/SETTINGS_PANEL.md +21 -0
  11. package/skills/wix-app/references/dashboard-page/ECOM_NAVIGATION.md +17 -0
  12. package/skills/wix-app/references/dashboard-plugin/SLOTS.md +4 -18
  13. package/skills/wix-app/references/site-plugin/EXAMPLES.md +1 -1
  14. package/skills/wix-headless-fast/SKILL.md +302 -51
  15. package/skills/wix-headless-fast/entry/skill.md +11 -2
  16. package/skills/wix-headless-fast/install/AGENTS.md +32 -0
  17. package/skills/wix-headless-fast/install/agents-md.mjs +39 -0
  18. package/skills/wix-headless-fast/install/attach.mjs +360 -0
  19. package/skills/wix-headless-fast/install/deploy.mjs +109 -9
  20. package/skills/wix-headless-fast/install/setup.mjs +239 -0
  21. package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +226 -60
  22. package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +2 -3
  23. package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +2 -3
  24. package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +24 -116
  25. package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +16 -64
  26. package/skills/wix-headless-fast/references/blog/app/wix/blog/blog-feed-store.ts +137 -0
  27. package/skills/wix-headless-fast/references/blog/app/wix/blog/post-store.ts +78 -0
  28. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts-core.ts +117 -0
  29. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +13 -60
  30. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy-core.ts +47 -0
  31. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +10 -28
  32. package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +2 -4
  33. package/skills/wix-headless-fast/references/blog/rest/posts.ts +42 -0
  34. package/skills/wix-headless-fast/references/blog/rest/taxonomy.ts +62 -0
  35. package/skills/wix-headless-fast/references/blog/seed/SEED.md +9 -0
  36. package/skills/wix-headless-fast/references/blog/seed/read-site.mjs +22 -0
  37. package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +181 -33
  38. package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +2 -1
  39. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +26 -132
  40. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +18 -49
  41. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-core.ts +218 -0
  42. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-flow-store.ts +169 -0
  43. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +57 -142
  44. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-core.ts +92 -0
  45. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-store.ts +90 -0
  46. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +22 -61
  47. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +3 -1
  48. package/skills/wix-headless-fast/references/bookings/rest/booking.ts +113 -0
  49. package/skills/wix-headless-fast/references/bookings/rest/services.ts +55 -0
  50. package/skills/wix-headless-fast/references/bookings/seed/SEED.md +10 -0
  51. package/skills/wix-headless-fast/references/bookings/seed/read-site.mjs +28 -0
  52. package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +238 -62
  53. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +19 -52
  54. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +16 -36
  55. package/skills/wix-headless-fast/references/cms/app/wix/cms/collection-store.ts +158 -0
  56. package/skills/wix-headless-fast/references/cms/app/wix/cms/item-store.ts +93 -0
  57. package/skills/wix-headless-fast/references/cms/app/wix/cms/items-core.ts +177 -0
  58. package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +13 -41
  59. package/skills/wix-headless-fast/references/cms/rest/items.ts +125 -0
  60. package/skills/wix-headless-fast/references/cms/seed/SEED.md +9 -0
  61. package/skills/wix-headless-fast/references/cms/seed/read-site.mjs +20 -0
  62. package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +241 -83
  63. package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +1 -1
  64. package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +7 -5
  65. package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +2 -2
  66. package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +28 -110
  67. package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +19 -54
  68. package/skills/wix-headless-fast/references/events/app/wix/events/events-core.ts +130 -0
  69. package/skills/wix-headless-fast/references/events/app/wix/events/events-store.ts +97 -0
  70. package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +13 -94
  71. package/skills/wix-headless-fast/references/events/app/wix/events/registration-core.ts +113 -0
  72. package/skills/wix-headless-fast/references/events/app/wix/events/registration-store.ts +156 -0
  73. package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +38 -69
  74. package/skills/wix-headless-fast/references/events/rest/events.ts +57 -0
  75. package/skills/wix-headless-fast/references/events/rest/registration.ts +83 -0
  76. package/skills/wix-headless-fast/references/events/seed/SEED.md +9 -0
  77. package/skills/wix-headless-fast/references/events/seed/read-site.mjs +26 -0
  78. package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +216 -26
  79. package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +35 -267
  80. package/skills/wix-headless-fast/references/forms/app/wix/forms/form-store.ts +332 -0
  81. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms-core.ts +238 -0
  82. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +28 -243
  83. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions-core.ts +216 -0
  84. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +32 -204
  85. package/skills/wix-headless-fast/references/forms/rest/forms.ts +34 -0
  86. package/skills/wix-headless-fast/references/forms/rest/submissions.ts +56 -0
  87. package/skills/wix-headless-fast/references/forms/seed/SEED.md +8 -0
  88. package/skills/wix-headless-fast/references/forms/seed/read-site.mjs +20 -0
  89. package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +15 -8
  90. package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +316 -63
  91. package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +1 -1
  92. package/skills/wix-headless-fast/references/members/app/wix/members/auth-core.ts +105 -0
  93. package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +36 -65
  94. package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +1 -1
  95. package/skills/wix-headless-fast/references/members/app/wix/members/members-core.ts +43 -0
  96. package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +11 -32
  97. package/skills/wix-headless-fast/references/members/rest/auth.ts +215 -0
  98. package/skills/wix-headless-fast/references/members/rest/members.ts +26 -0
  99. package/skills/wix-headless-fast/references/members/seed/read-site.mjs +15 -0
  100. package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +240 -59
  101. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +2 -2
  102. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +2 -2
  103. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx +35 -0
  104. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +8 -38
  105. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +23 -50
  106. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +16 -34
  107. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +22 -49
  108. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collection-projects-store.ts +82 -0
  109. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collections-store.ts +58 -0
  110. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio-core.ts +177 -0
  111. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +31 -118
  112. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts +81 -0
  113. package/skills/wix-headless-fast/references/portfolio/rest/portfolio.ts +75 -0
  114. package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +8 -0
  115. package/skills/wix-headless-fast/references/portfolio/seed/read-site.mjs +20 -0
  116. package/skills/wix-headless-fast/references/portfolio/seed/seed-portfolio.mjs +19 -21
  117. package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +214 -51
  118. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +9 -20
  119. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +7 -32
  120. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/SubscribeButton.tsx +41 -0
  121. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +12 -33
  122. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +17 -34
  123. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts +54 -0
  124. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-core.ts +106 -0
  125. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts +64 -0
  126. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +7 -55
  127. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts +43 -0
  128. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +11 -34
  129. package/skills/wix-headless-fast/references/pricing-plans/rest/plans.ts +31 -0
  130. package/skills/wix-headless-fast/references/pricing-plans/rest/purchase.ts +20 -0
  131. package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +8 -0
  132. package/skills/wix-headless-fast/references/pricing-plans/seed/read-site.mjs +21 -0
  133. package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +242 -47
  134. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +2 -2
  135. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +2 -2
  136. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +14 -35
  137. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +28 -171
  138. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-core.ts +155 -0
  139. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-store.ts +89 -0
  140. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +23 -117
  141. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering-core.ts +124 -0
  142. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +45 -114
  143. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservation-store.ts +193 -0
  144. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations-core.ts +94 -0
  145. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +15 -87
  146. package/skills/wix-headless-fast/references/restaurants/rest/menu.ts +60 -0
  147. package/skills/wix-headless-fast/references/restaurants/rest/ordering.ts +169 -0
  148. package/skills/wix-headless-fast/references/restaurants/rest/reservations.ts +68 -0
  149. package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +8 -0
  150. package/skills/wix-headless-fast/references/restaurants/seed/read-site.mjs +25 -0
  151. package/skills/wix-headless-fast/references/restaurants/seed/seed-restaurants.mjs +25 -5
  152. package/skills/wix-headless-fast/references/shared/SUPPLIED-CONTENT.md +23 -0
  153. package/skills/wix-headless-fast/references/shared/app/wix/media.ts +45 -4
  154. package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +13 -0
  155. package/skills/wix-headless-fast/references/shared/rest/client.ts +155 -0
  156. package/skills/wix-headless-fast/references/shared/rest/config.ts +3 -0
  157. package/skills/wix-headless-fast/references/shared/rest/media.ts +65 -0
  158. package/skills/wix-headless-fast/references/shared/seed/read-site.mjs +99 -0
  159. package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +186 -63
  160. package/skills/wix-headless-fast/references/storefront/app/components/storefront/OptionPicker.tsx +95 -0
  161. package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +29 -58
  162. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +29 -163
  163. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +26 -300
  164. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-core.ts +121 -0
  165. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +49 -162
  166. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +314 -0
  167. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +47 -370
  168. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts +147 -0
  169. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/shop-store.ts +289 -0
  170. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +2 -0
  171. package/skills/wix-headless-fast/references/storefront/rest/cart.ts +132 -0
  172. package/skills/wix-headless-fast/references/storefront/rest/catalog.ts +123 -0
  173. package/skills/wix-headless-fast/references/storefront/seed/SEED.md +31 -9
  174. package/skills/wix-headless-fast/references/storefront/seed/read-site.mjs +38 -0
  175. package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +57 -50
  176. package/skills/wix-manage/references/ecommerce/ecom-shipping.md +1 -1
  177. package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-create-coupon.md +10 -19
  178. package/skills/wix-manage/references/events/create-wix-event.md +5 -1
  179. package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +0 -40
  180. package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +3 -0
  181. package/skills/wix-vibe-headless/references/storefront/seed/seed-store.cjs +6 -0
  182. package/skills/wix-headless-fast/install/fast-path.mjs +0 -186
  183. package/skills/wix-headless-fast/install/pin-agents-md.mjs +0 -57
@@ -0,0 +1,360 @@
1
+ // Attach a NEW headless frontend to an EXISTING Wix site — setup's sibling for a site that already
2
+ // exists (named in the prompt by its id). `init`/`wix create` cannot do this: they always create
3
+ // a site. This script does what they do AFTER creating one — OAuth app, managed hosting, env
4
+ // vars, `wix.config.json` — against the site id given, then scaffolds and deploys like
5
+ // setup.mjs. Nothing on the site is created, changed or deleted; there is no seed step.
6
+ //
7
+ // node <SKILL_ROOT>/install/attach.mjs [--site <metaSiteId>] --business-name "<Brand>" \
8
+ // --vertical <a>[,<b>…] [--stack astro|react|lib|static] [--hosting wix|self [--origin <url>[,<url>]]] \
9
+ // [--subfolder [--folder-name <name>]]
10
+ //
11
+ // --hosting wix (default): the frontend releases to Wix hosting — the site's app project is
12
+ // created or reused and `wix release` uploads to its *.wix-site-host.com address.
13
+ // self: the frontend is hosted elsewhere (Vercel, a Flask server, …): no app
14
+ // project, no Wix address, no Astro template; the OAuth app, wix.config.json and
15
+ // .env.local are still set up, the code deploys for --stack, and the origins given
16
+ // in --origin go on the OAuth app's redirect allow-list, which is what a Wix-hosted
17
+ // flow (checkout) needs to return to a frontend on another host.
18
+ //
19
+ // --site the site. May be omitted when the folder holds a wix.config.json: then it is
20
+ // the site in that config (what `init` left behind in an empty folder).
21
+ // --business-name the site's name (names the folder, the hosting slug and the app project).
22
+ // --vertical which shipped code deploys (no seed runs — the site owns its content). Both are
23
+ // the caller's decision, read off the site before calling this (SKILL.md step 3).
24
+ // --stack astro (default) in a folder without a project: scaffolds the CLI's blank Astro
25
+ // template with the hosting adapter. react|static there: writes wix.config.json
26
+ // and stops — the caller scaffolds (Vite / plain HTML) per SKILL.md. In a folder
27
+ // that holds a project, --stack is required and nothing is scaffolded.
28
+ //
29
+ // The FOLDER decides, on file markers only:
30
+ // - a project (package.json or index.html) AND wix.config.json → refuses: a Wix project with a
31
+ // frontend already; deploy.mjs adds a solution to it.
32
+ // - a project, no config → LINK: the hosting calls, then wix.config.json and .env.local are
33
+ // written into the project as it is, the shipped code deploys for --stack, the install starts.
34
+ // - no project (empty, or only a config for this same site) → the scaffold path above.
35
+ // - a config naming a different site → refuses: never re-point a folder; use --subfolder.
36
+ // The project lives in the CURRENT DIRECTORY — the folder that already holds the installed skills —
37
+ // so it is self-contained. `--subfolder` (opt-in) creates it in a new folder named after the
38
+ // business instead.
39
+ //
40
+ // Emits ONE JSON event per line (attached, scaffolded, deployed, install_started,
41
+ // ready_for_brand_layer, or error). Requires a logged-in Wix CLI (`npx @wix/cli@latest whoami`)
42
+ // whose account owns or co-manages the site.
43
+ import { spawn, spawnSync, execFileSync } from "node:child_process";
44
+ import { cpSync, existsSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
45
+ import { tmpdir } from "node:os";
46
+ import { basename, dirname, join, resolve } from "node:path";
47
+ import { fileURLToPath } from "node:url";
48
+ import { writeAgentsMd } from "./agents-md.mjs";
49
+
50
+ const SKILL_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
51
+ const MANAGE = "https://manage.wix.com";
52
+ const API = "https://www.wixapis.com";
53
+ const HEADLESS_PROJECT_TYPE_ID = "eb363dea-85a0-4159-9b05-949542be5079";
54
+ const TEMPLATES_REPO = "https://github.com/wix/headless-templates.git";
55
+ const TEMPLATE_PATH = "astro/blank";
56
+
57
+ const emit = (event, extra = {}) => console.log(JSON.stringify({ event, ...extra }));
58
+ const fail = (step, detail) => {
59
+ emit("error", { step, detail: String(detail).slice(0, 600) });
60
+ process.exit(1);
61
+ };
62
+
63
+ // ---- args ---------------------------------------------------------------------------------------
64
+ const argv = process.argv.slice(2);
65
+ const flag = (name) => {
66
+ const i = argv.indexOf(`--${name}`);
67
+ return i !== -1 && argv[i + 1] && !argv[i + 1].startsWith("--") ? argv[i + 1] : null;
68
+ };
69
+ const stackFlag = flag("stack");
70
+ const hosting0 = flag("hosting") ?? "wix";
71
+ const origins = (flag("origin") ?? "").split(",").map((o) => o.trim().replace(/\/$/, "")).filter(Boolean);
72
+ const subfolder = argv.includes("--subfolder");
73
+ // ---- the folder ---------------------------------------------------------------------------------
74
+ const cwd = process.cwd();
75
+ const has = (p) => existsSync(join(cwd, p));
76
+ const cwdConfig = has("wix.config.json") ? JSON.parse(readFileSync(join(cwd, "wix.config.json"), "utf8")) : null;
77
+ const hasProject = has("package.json") || has("index.html");
78
+ const siteId = flag("site") ?? cwdConfig?.siteId ?? cwdConfig?.projectId ?? null;
79
+ const stack = stackFlag ?? "astro";
80
+ const knownVerticals = readdirSync(join(SKILL_ROOT, "references"), { withFileTypes: true })
81
+ .filter((d) => d.isDirectory() && d.name !== "shared" && existsSync(join(SKILL_ROOT, "references", d.name, "app")))
82
+ .map((d) => d.name);
83
+ const verticals = argv
84
+ .flatMap((a, i) => (a === "--vertical" && argv[i + 1] ? argv[i + 1].split(",") : []))
85
+ .map((v) => v.trim())
86
+ .filter(Boolean);
87
+ const businessName = flag("business-name");
88
+ if (!siteId || !/^[0-9a-f-]{36}$/i.test(siteId) || !businessName || !verticals.length) {
89
+ fail("args", `usage: attach.mjs [--site <metaSiteId>] --business-name "<Brand>" --vertical <${knownVerticals.join("|")}>[,…] [--stack astro|react|lib|static] (--site may be omitted when wix.config.json is here)`);
90
+ }
91
+ for (const v of verticals) if (!knownVerticals.includes(v)) fail("args", `unknown vertical "${v}" — shipped verticals: ${knownVerticals.join(", ")}`);
92
+ if (!["astro", "react", "lib", "static"].includes(stack)) fail("args", `unknown stack "${stack}" — astro, react, lib or static`);
93
+ if (!["wix", "self"].includes(hosting0)) fail("args", `unknown --hosting "${hosting0}" — wix or self`);
94
+ for (const o of origins) if (!/^https?:\/\/[^/\s]+$/.test(o)) fail("args", `--origin must be a scheme and host such as https://shop.example.com or http://localhost:8000, got "${o}"`);
95
+ if (hosting0 === "self" && stack === "astro" && !hasProject && !stackFlag) {
96
+ 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");
97
+ }
98
+ if (!subfolder) {
99
+ if (hasProject && cwdConfig) {
100
+ 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");
101
+ }
102
+ if (cwdConfig && (cwdConfig.siteId ?? cwdConfig.projectId) !== siteId) {
103
+ 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`);
104
+ }
105
+ if (hasProject && !stackFlag) {
106
+ 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");
107
+ }
108
+ }
109
+ const mode = !subfolder && hasProject ? "link" : hosting0 === "self" ? "config-only" : "scaffold";
110
+ emit("folder", { mode, stack, hosting: hosting0, project: hasProject, config: cwdConfig ? "same site" : null });
111
+
112
+ // ---- http ---------------------------------------------------------------------------------------
113
+ const cliToken = (site) => {
114
+ const r = spawnSync("npx", ["-y", "@wix/cli@latest", "token", ...(site ? ["--site", site] : [])], { encoding: "utf8", timeout: 120_000 });
115
+ const t = (r.stdout || "").trim();
116
+ if (r.status !== 0 || !t) fail("auth", (r.stderr || r.stdout || "no token — is the Wix CLI logged in? (npx @wix/cli@latest whoami)").slice(-400));
117
+ return t;
118
+ };
119
+ async function call(base, path, { method = "POST", token, site, body, query } = {}) {
120
+ const url = new URL(base + path);
121
+ for (const [k, v] of Object.entries(query ?? {})) url.searchParams.set(k, String(v));
122
+ const res = await fetch(url, {
123
+ method,
124
+ headers: {
125
+ Authorization: token,
126
+ "Content-Type": "application/json",
127
+ "X-XSRF-TOKEN": "nocheck",
128
+ Cookie: "XSRF-TOKEN=nocheck",
129
+ "User-Agent": "wix-cli",
130
+ ...(site ? { "wix-site-id": site } : {}),
131
+ },
132
+ body: body === undefined ? undefined : JSON.stringify(body),
133
+ });
134
+ const text = await res.text();
135
+ if (!res.ok) throw new Error(`${method} ${url.pathname} ${res.status}: ${text.slice(0, 300)}`);
136
+ try { return JSON.parse(text); } catch { return {}; }
137
+ }
138
+
139
+ const folderName = flag("folder-name") ?? (businessName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "site");
140
+ const projectDir = subfolder ? resolve(cwd, folderName) : cwd;
141
+ if (subfolder && existsSync(join(projectDir, "wix.config.json"))) fail("args", `${folderName}/ is already a Wix project — pick --folder-name, or run deploy.mjs there`);
142
+
143
+ // ---- 1 · attach: OAuth app + hosting + env, on the manage host with a site token -----------------
144
+ // The same calls `init` makes after it has created a site, in the same order.
145
+ const siteToken = cliToken(siteId);
146
+ const opts = { token: siteToken, site: siteId };
147
+ let appId, appProject = null, instanceId, secrets, hosting = hosting0 === "self" ? "self" : "created";
148
+ try {
149
+ const comp = await call(MANAGE, "/_api/companion-apps/v1/companion-apps/get-or-create", { ...opts, body: {} });
150
+ appId = comp.companionApp?.id;
151
+ if (!appId) throw new Error("get-or-create returned no companion app id");
152
+ const slug = folderName;
153
+ if (hosting0 === "wix") {
154
+ try { appProject = (await call(MANAGE, `/_api/wix-code-app-projects/v1/app-projects/${appId}`, { ...opts, method: "GET" })).appProject; } catch { appProject = null; }
155
+ if (appProject) {
156
+ hosting = "reused";
157
+ } else {
158
+ try { await call(MANAGE, `/apps-service/v1/apps/${appId}/set-namespace`, { ...opts, method: "PATCH", body: { appId, appName: businessName, namespace: slug } }); }
159
+ catch (e) { emit("note", { step: "set-namespace", detail: String(e.message).slice(0, 200) }); }
160
+ }
161
+ }
162
+ try {
163
+ const inst = await call(MANAGE, "/apps-installer-service/v1/app-instance/install", { ...opts, body: { tenant: { id: siteId, tenantType: "SITE" }, appInstance: { appDefId: appId, version: "latest" } } });
164
+ instanceId = inst.appInstance?.id;
165
+ } catch (e) { emit("note", { step: "install", detail: String(e.message).slice(0, 200) }); }
166
+ secrets = (await call(MANAGE, `/apps-service/v1/apps/${appId}`, { ...opts, method: "GET", query: { withSecrets: true } })).app?.appSecrets;
167
+ if (!secrets?.appSecret) throw new Error("app secrets unavailable for this app");
168
+ if (hosting0 === "self") {
169
+ // No Wix hosting. The one thing a frontend on another host needs from the site: its origins on
170
+ // the OAuth app's redirect allow-list, so checkout and the other Wix-hosted flows can return.
171
+ // The public OAuth Apps API (wix-manage: Manage OAuth Apps), merged with what is already there.
172
+ if (origins.length) {
173
+ const current = (await call(API, `/oauth-app/v1/oauth-apps/${appId}`, { ...opts, method: "GET" })).oAuthApp ?? {};
174
+ const merged = [...new Set([...(current.allowedRedirectDomains ?? []), ...origins])];
175
+ await call(API, `/oauth-app/v1/oauth-apps/${appId}`, { ...opts, method: "PATCH", body: { oAuthApp: { allowedRedirectDomains: merged }, mask: { paths: ["allowedRedirectDomains"] } } });
176
+ emit("origins_allowed", { appId, allowedRedirectDomains: merged });
177
+ } else {
178
+ emit("note", { step: "origins", detail: "no --origin given: add the frontend's origins to the OAuth app's allowedRedirectDomains (wix-manage: Manage OAuth Apps) before the first checkout test" });
179
+ }
180
+ }
181
+ if (hosting0 === "wix" && !appProject) {
182
+ appProject = (await call(MANAGE, "/_api/wix-code-app-projects/v1/app-projects", { ...opts, body: { appProject: { id: appId, displayName: businessName.slice(0, 50), slug, appProjectTypeId: HEADLESS_PROJECT_TYPE_ID } } })).appProject;
183
+ }
184
+ if (hosting0 === "wix") {
185
+ if (!appProject?.baseUrl) throw new Error("app project has no baseUrl");
186
+ const prod = String(appProject.baseUrl).replace(/\/$/, "");
187
+ const host = new URL(appProject.baseUrl).hostname;
188
+ const local = "http://localhost:4321";
189
+ await call(MANAGE, `/oauth-app-service/v1/oauth-apps/${appId}`, { ...opts, method: "PATCH", body: {
190
+ oAuthApp: {
191
+ id: appId,
192
+ allowedDomains: [local, `https://(.*)-${host}`, prod],
193
+ allowedRedirectUris: [`${local}/api/auth/callback`, `${local}/api/auth/logout-callback`, `https://*-${host}/api/auth/callback`, `https://*-${host}/api/auth/logout-callback`, `${prod}/api/auth/callback`, `${prod}/api/auth/logout-callback`],
194
+ redirectUrlWixPages: prod,
195
+ origin: "other",
196
+ },
197
+ mask: { paths: ["allowedDomains", "allowedRedirectUris", "redirectUrlWixPages", "origin"] },
198
+ } });
199
+ if (!instanceId) {
200
+ // reused app: the instance id is on the existing env, keep it
201
+ try {
202
+ const env = await call(MANAGE, `/_api/wix-code-app-environments/v2/app-projects/${appProject.id}/app-environment-variables`, { ...opts, method: "GET", query: { environment: "system_global" } });
203
+ instanceId = (env.appEnvironmentVariables ?? []).find((v) => v.key === "WIX_CLIENT_INSTANCE_ID")?.value ?? null;
204
+ } catch { /* fall through */ }
205
+ }
206
+ const variables = { WIX_CLIENT_ID: appId, WIX_CLIENT_SECRET: secrets.appSecret, WIX_CLIENT_PUBLIC_KEY: secrets.webhookPublicKey, ...(instanceId ? { WIX_CLIENT_INSTANCE_ID: instanceId } : {}) };
207
+ await call(MANAGE, `/_api/wix-code-app-environments/v2/bulk/app-projects/${appProject.id}/app-environment-variables/upsert`, { ...opts, body: {
208
+ appProjectId: appProject.id, environment: "system_global", mutability: "STATIC", returnEntity: true, returnAllEnvironment: true, variables,
209
+ } });
210
+ }
211
+ } catch (e) {
212
+ fail("attach", e?.message || e);
213
+ }
214
+ const baseUrl = appProject ? String(appProject.baseUrl).replace(/\/$/, "") : null;
215
+ // Is a frontend serving at that address? The hosting project says nothing about it (init and the
216
+ // provisioning call create one with nothing behind it). The site's release slots do: the release
217
+ // with slug `prod` is production traffic and names the deployment it serves — the same read the
218
+ // headless back office makes for its frontend status. Absent means nothing was ever released.
219
+ // Deployments keep their own addresses, so a replaced one is not lost; prod can be pointed back.
220
+ // A folder that reaches this point is not the one that released (a linked project never runs
221
+ // attach), so a `prod` release here means a NEW project is about to take over a live frontend.
222
+ let frontend = null;
223
+ if (appProject) {
224
+ try {
225
+ const r = await call(MANAGE, `/_api/wix-code-app-releases/v1/app-projects/${appProject.id}/app-releases/query`, { ...opts, body: { query: { cursorPaging: { limit: 20 } } } });
226
+ const prod = (r.appReleases ?? []).find((x) => x.releaseSlug === "prod");
227
+ frontend = prod
228
+ ? { serving: true, releasedAt: prod.updatedDate ?? prod.createdDate ?? null, deploymentId: prod.appDeploymentId ?? null }
229
+ : { serving: false };
230
+ } catch (e) {
231
+ emit("note", { step: "releases", detail: String(e.message).slice(0, 200) });
232
+ frontend = { serving: null };
233
+ }
234
+ }
235
+ const note =
236
+ hosting === "self" ? "no Wix hosting: the frontend is yours to host; its origins must be on the OAuth app's allow-list"
237
+ : frontend?.serving === true
238
+ ? `a frontend is SERVING at ${baseUrl}, released ${frontend.releasedAt}. This is a new project: a \`wix release\` from here replaces it (the current deployment keeps its own address and prod can be pointed back). Tell the user before releasing; unless the brief asked for a new frontend, ask.`
239
+ : frontend?.serving === false
240
+ ? `${hosting === "created" ? "hosting created" : "hosting exists"} at ${baseUrl}; nothing served yet — the first release from this project fills it`
241
+ : frontend
242
+ ? `could not read whether a frontend is serving at ${baseUrl}; check the address before releasing`
243
+ : undefined;
244
+ emit("attached", { siteId, appId, baseUrl, hosting, frontend, origins: hosting0 === "self" ? origins : undefined, note });
245
+
246
+ // ---- 2 · scaffold (only where there is no project) -------------------------------------------------
247
+ mkdirSync(projectDir, { recursive: true });
248
+ if (stack === "astro" && mode === "scaffold") {
249
+ // The CLI's own blank template (what `wix create` copies), then what its extender adds: the
250
+ // hosting adapter, React islands, the `wix` scripts. Pinned like a freshly created project.
251
+ const tmp = mkdtempSync(join(tmpdir(), "wix-template-"));
252
+ const clone = spawnSync("git", ["clone", "--quiet", "--depth", "1", "--filter=blob:none", "--sparse", TEMPLATES_REPO, tmp], { encoding: "utf8", timeout: 120_000 });
253
+ if (clone.status !== 0) fail("scaffold", clone.stderr || "git clone of the template repo failed");
254
+ const sparse = spawnSync("git", ["-C", tmp, "sparse-checkout", "set", TEMPLATE_PATH], { encoding: "utf8", timeout: 60_000 });
255
+ if (sparse.status !== 0 || !existsSync(join(tmp, TEMPLATE_PATH, "package.json"))) fail("scaffold", sparse.stderr || `template ${TEMPLATE_PATH} not found in ${TEMPLATES_REPO}`);
256
+ cpSync(join(tmp, TEMPLATE_PATH), projectDir, { recursive: true, force: false, errorOnExist: false });
257
+ rmSync(tmp, { recursive: true, force: true });
258
+ const pkgPath = join(projectDir, "package.json");
259
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
260
+ pkg.name = folderName;
261
+ pkg.scripts = { astro: "astro", dev: "wix dev", build: "wix build", wix: "wix", preview: "wix preview", release: "wix release", generate: "wix generate", env: "wix env", skills: "wix skills" };
262
+ pkg.devDependencies = {
263
+ ...(pkg.devDependencies ?? {}),
264
+ "@astrojs/react": "^4.3.0",
265
+ "@types/react": "^18.3.1",
266
+ "@types/react-dom": "^18.3.1",
267
+ "@wix/cli": "^1.1.92",
268
+ "@wix/astro-wix-hosting-adapter": "^2.0.0",
269
+ react: "18.3.1",
270
+ "react-dom": "18.3.1",
271
+ };
272
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
273
+ writeFileSync(join(projectDir, "astro.config.mjs"), `// @ts-check
274
+ import { defineConfig } from "astro/config";
275
+ import wix from "@wix/astro";
276
+ import wixPages from "@wix/astro-pages";
277
+ import react from "@astrojs/react";
278
+ import wixHostingAdapter from "@wix/astro-wix-hosting-adapter";
279
+
280
+ // https://astro.build/config
281
+ export default defineConfig({
282
+ integrations: [wix(), wixPages(), react()],
283
+ security: { checkOrigin: false },
284
+ adapter: wixHostingAdapter(),
285
+ image: { domains: ["static.wixstatic.com"] },
286
+ output: "server",
287
+ });
288
+ `);
289
+ const gi = join(projectDir, ".gitignore");
290
+ const cur = existsSync(gi) ? readFileSync(gi, "utf8") : "";
291
+ if (!/\.env/.test(cur)) writeFileSync(gi, cur + "\n# local env (pulled from Wix)\n.env.local\n.env\n");
292
+ }
293
+ writeFileSync(join(projectDir, "wix.config.json"), JSON.stringify({ appId, siteId }, null, 2) + "\n");
294
+ // The env the CLI's build reads. Same content `wix env pull` writes; written here so the build
295
+ // needs no extra call.
296
+ const quote = (v) => `"${String(v ?? "").replace(/"/g, '\\"')}"`;
297
+ writeFileSync(join(projectDir, ".env.local"), [
298
+ `WIX_CLOUD_PROVIDER=${quote("wix")}`,
299
+ `WIX_CLIENT_ID=${quote(appId)}`,
300
+ ...(instanceId ? [`WIX_CLIENT_INSTANCE_ID=${quote(instanceId)}`] : []),
301
+ `WIX_CLIENT_PUBLIC_KEY=${quote(secrets.webhookPublicKey)}`,
302
+ `WIX_CLIENT_SECRET=${quote(secrets.appSecret)}`,
303
+ "",
304
+ ].join("\n"));
305
+ emit(mode === "link" ? "linked" : mode === "config-only" ? "configured" : "scaffolded", { folder: folderName, stack, template: mode === "scaffold" && stack === "astro" ? `${TEMPLATES_REPO}#${TEMPLATE_PATH}` : null });
306
+ // the agent config files `wix create` writes (attach never runs the CLI's scaffold at all); fill-only
307
+ emit("agent_configs", writeAgentsMd(projectDir, { skill: basename(SKILL_ROOT), stack }));
308
+
309
+ if (mode !== "link" && (stack !== "astro" || mode === "config-only")) {
310
+ emit("ready", { projectDir, siteId, appId, baseUrl, hosting, frontend, stack, dashboardUrl: `https://manage.wix.com/dashboard/${siteId}`,
311
+ next: `scaffold the ${stack} project in this folder per SKILL.md, then deploy.mjs <vertical…> --stack ${stack} (the client id is read from wix.config.json); no seed — the site owns its content` +
312
+ (hosting === "self" ? `; you host it: origins on the OAuth app allow-list now: ${origins.join(", ") || "none — add them before the first checkout test"}` : "") });
313
+ process.exit(0);
314
+ }
315
+
316
+ // ---- 3 · deploy shipped code + deps + lockfile ---------------------------------------------------
317
+ let deployResult = {};
318
+ {
319
+ const deploy = spawnSync("node", [join(SKILL_ROOT, "install", "deploy.mjs"), ...verticals, "--stack", stack], { cwd: projectDir, encoding: "utf8", timeout: 60_000 });
320
+ if (deploy.status !== 0) fail("deploy", deploy.stderr || deploy.stdout);
321
+ try { deployResult = JSON.parse(deploy.stdout); } catch { /* keep going */ }
322
+ if (deployResult.error) fail("deploy", deployResult.error);
323
+ emit("deployed", deployResult);
324
+ }
325
+
326
+ // ---- 4 · dependency install, detached (any project with a package.json) --------------------------
327
+ let install = null;
328
+ if (existsSync(join(projectDir, "package.json"))) {
329
+ const installLog = join(projectDir, "npm-install.log");
330
+ const logFd = openSync(installLog, "a");
331
+ 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] });
332
+ child.unref();
333
+ install = { log: installLog, doneMarker: "node_modules/.package-lock.json" };
334
+ emit("install_started", install);
335
+ }
336
+
337
+ // ---- done ----------------------------------------------------------------------------------------
338
+ emit("ready_for_brand_layer", {
339
+ projectDir,
340
+ siteId,
341
+ appId,
342
+ baseUrl,
343
+ hosting,
344
+ frontend,
345
+ verticals,
346
+ dashboardUrl: `https://manage.wix.com/dashboard/${siteId}`,
347
+ productsUrl: deployResult.productsUrl,
348
+ categoriesUrl: deployResult.categoriesUrl,
349
+ mode,
350
+ stack,
351
+ install,
352
+ seed: null,
353
+ next:
354
+ "the site's content is live already — nothing to seed; get the measure of the site (SKILL.md step 3), theme + write the pages" +
355
+ (mode === "link" && hosting !== "self" ? "; make the project what its stack needs on Wix hosting (SKILL.md step 1)" : "") +
356
+ (install ? "; wait for the install marker" : "") +
357
+ (hosting === "self"
358
+ ? `; you host it — no wix release; origins on the OAuth app allow-list now: ${origins.join(", ") || "none — add them before the first checkout test"}; add the public one when it goes live`
359
+ : "; then release as step 5 says for the stack"),
360
+ });
@@ -8,6 +8,21 @@
8
8
  // --stack react — copies only the core (app/) into src/, and writes the public OAuth
9
9
  // client id into src/wix/config.ts (pass --client-id, or it's read from
10
10
  // wix.config.json's appId when present).
11
+ // --stack lib — a bundled JS project that isn't React (Vue, Svelte, Solid, plain Vite): copies
12
+ // ONLY the data layer — shared app/wix/ (sdk, media, money, config) and each
13
+ // vertical's app/wix/<vertical>/ (data layer, cart store, types, *-core rules) —
14
+ // none of which imports React; writes the client id like react. No hooks, no
15
+ // components, no global.css, no Tailwind deps. The agent writes its framework's
16
+ // stores and components against the same DTO contracts.
17
+ // --stack static — a site with NO bundler (plain HTML/CSS/JS): composes the REST layer flat into
18
+ // <out>/js/wix/ — shared/rest/ (client, media, config) + each vertical's rest/ + the
19
+ // vertical's transport-agnostic core files (types, *-core) from its app/ — writes
20
+ // the client id, and strips it to browser-ready ESM with tsc (comments kept, the
21
+ // .ts sources kept beside the .js). No package.json is touched. Storefront only
22
+ // until other verticals ship a rest/. `--out <dir>` (default: the project root)
23
+ // is the folder the site is served from — the one wix.config.json's
24
+ // site.outputDirectory points at — so the modules land where the pages import
25
+ // them and nothing else in the project gets uploaded.
11
26
  //
12
27
  // TWO mechanisms, driven by the same vertical arguments:
13
28
  // 1. Recursive file copy with force:false — only files that AREN'T there yet are written, so
@@ -158,17 +173,18 @@ const flag = (name) => {
158
173
  const stack = flag("stack") ?? "astro";
159
174
  const clientIdFlag = flag("client-id");
160
175
  const planPath = flag("plan");
176
+ const outDir = flag("out");
161
177
  const flagArgs = new Set(
162
- ["--stack", "--client-id", "--plan", stack, clientIdFlag, planPath].filter(Boolean),
178
+ ["--stack", "--client-id", "--plan", "--out", stack, clientIdFlag, planPath, outDir].filter(Boolean),
163
179
  );
164
180
  const requested = [...new Set(argv.filter((a) => !flagArgs.has(a)))];
165
181
 
166
182
  const result = { stack, verticals: [], skillRoot: SKILL_ROOT };
167
183
 
168
- if (!["astro", "react"].includes(stack)) {
184
+ if (!["astro", "react", "lib", "static"].includes(stack)) {
169
185
  console.log(
170
186
  JSON.stringify({
171
- error: `unknown --stack "${stack}" — expected astro|react`,
187
+ error: `unknown --stack "${stack}" — expected astro|react|lib|static`,
172
188
  }),
173
189
  );
174
190
  process.exit(1);
@@ -193,6 +209,87 @@ if (!Array.isArray(uploadPolicies)) {
193
209
  console.log(JSON.stringify({ error: "plan.capabilities.mediaUpload.policies must be an array" }));
194
210
  process.exit(1);
195
211
  }
212
+ // ---- static stack: the REST layer, composed flat and stripped ------------------------------------
213
+ if (stack === "static") {
214
+ const { spawnSync } = await import("node:child_process");
215
+ const { rmSync } = await import("node:fs");
216
+ const OUT = outDir ? resolve(PROJECT, outDir) : PROJECT;
217
+ const JS = join(OUT, "js", "wix");
218
+ result.out = outDir ?? ".";
219
+ let clientId = clientIdFlag;
220
+ if (!clientId && existsSync(join(PROJECT, "wix.config.json"))) {
221
+ clientId = JSON.parse(readFileSync(join(PROJECT, "wix.config.json"), "utf8")).appId ?? null;
222
+ }
223
+ if (!clientId) {
224
+ console.log(JSON.stringify({ error: "static stack needs the public OAuth client id — run `npm create @wix/new@latest init` here first, or pass --client-id" }));
225
+ process.exit(1);
226
+ }
227
+ const missing = requested.filter((v) => !existsSync(join(REF, v, "rest")));
228
+ if (missing.length) {
229
+ console.log(JSON.stringify({ error: `no rest/ layer yet for ${missing.map((v) => `"${v}"`).join(", ")} — the static stack ships: ${VERTICALS.filter((v) => existsSync(join(REF, v, "rest"))).join(", ")}` }));
230
+ process.exit(1);
231
+ }
232
+ // Only files that aren't there yet — a re-run restores, never clobbers (same as the copy below).
233
+ cpSync(join(REF, "shared", "rest"), JS, COPY);
234
+ for (const vertical of requested) {
235
+ cpSync(join(REF, vertical, "rest"), JS, COPY);
236
+ // The vertical's transport-agnostic core: types, every *-core.ts, and every *-store.ts under its
237
+ // app/wix/<vertical>/ — the stores are the hooks' logic without React, over the same function
238
+ // names the REST twin exports, so they run against it unchanged.
239
+ const appWix = join(REF, vertical, "app", "wix", vertical);
240
+ for (const f of readdirSync(appWix)) {
241
+ // Every vertical has a types.ts; flat in one folder they would collide, so each lands as
242
+ // <vertical>-types.ts and the vertical's own files are pointed at it below.
243
+ if (f === "types.ts") cpSync(join(appWix, f), join(JS, `${vertical}-types.ts`), COPY);
244
+ else if (f.endsWith("-core.ts") || f.endsWith("-store.ts")) cpSync(join(appWix, f), join(JS, f), COPY);
245
+ }
246
+ const own = new Set([...readdirSync(join(REF, vertical, "rest")), ...readdirSync(appWix)].filter((f) => f.endsWith(".ts") && f !== "types.ts"));
247
+ for (const f of own) {
248
+ const fp = join(JS, f);
249
+ if (!existsSync(fp)) continue;
250
+ const src = readFileSync(fp, "utf8");
251
+ const fixed = src.replace(/from\s+"\.\/types(?:\.js)?"/g, `from "./${vertical}-types.js"`);
252
+ if (fixed !== src) writeFileSync(fp, fixed);
253
+ }
254
+ result.verticals.push(vertical);
255
+ }
256
+ // Browser ESM resolves nothing: every relative import needs its .js. The rest/ files are written
257
+ // that way; the files borrowed from app/wix/ are bundler-style and get the suffix here.
258
+ for (const f of readdirSync(JS).filter((f) => f.endsWith(".ts"))) {
259
+ const src = readFileSync(join(JS, f), "utf8");
260
+ const fixed = src.replace(/(from\s+"\.\/[^"]+?)(?<!\.js)"/g, '$1.js"');
261
+ if (fixed !== src) writeFileSync(join(JS, f), fixed);
262
+ }
263
+ const cfg = join(JS, "config.ts");
264
+ const current = readFileSync(cfg, "utf8");
265
+ if (/WIX_CLIENT_ID: string = ""/.test(current)) {
266
+ writeFileSync(cfg, current.replace(/WIX_CLIENT_ID: string = ""/, `WIX_CLIENT_ID: string = "${clientId}"`));
267
+ result.clientId = "written";
268
+ } else {
269
+ result.clientId = "already_set";
270
+ }
271
+ // Strip to ESM with tsc — comments are the spec for whoever reads js/wix/, so they stay.
272
+ const sources = readdirSync(JS).filter((f) => f.endsWith(".ts")).map((f) => join(JS, f));
273
+ const tsc = spawnSync(
274
+ "npx",
275
+ ["-y", "-p", "typescript@5", "tsc", ...sources, "--outDir", JS, "--module", "esnext", "--target", "es2022",
276
+ "--moduleResolution", "bundler", "--lib", "es2022,dom", "--strict", "--skipLibCheck", "--removeComments", "false"],
277
+ { encoding: "utf8", timeout: 180_000 },
278
+ );
279
+ if (tsc.status !== 0) {
280
+ console.log(JSON.stringify({ ...result, error: `tsc strip failed: ${(tsc.stdout || tsc.stderr || "").slice(-800)}` }));
281
+ process.exit(1);
282
+ }
283
+ rmSync(join(JS, "tsconfig.json"), { force: true });
284
+ result.js = `${outDir ? outDir.replace(/\/$/, "") + "/" : ""}js/wix/*.js`;
285
+ // The note lists what actually landed, so it holds for every vertical (and several at once).
286
+ const stores = readdirSync(JS).filter((f) => f.endsWith("-store.js")).map((f) => `./js/wix/${f}`);
287
+ const dataFiles = readdirSync(JS).filter((f) => f.endsWith(".js") && !f.endsWith("-store.js") && !f.endsWith("-core.js") && !f.endsWith("-types.js") && !["client.js", "config.js", "media.js"].includes(f)).map((f) => `./js/wix/${f}`);
288
+ result.note = `import the data layer (${dataFiles.join(", ")})${stores.length ? ` and the stores (${stores.join(", ")})` : ""} relative to ${outDir ?? "the project root"} in a <script type="module">; the .ts beside them are the same files with types, for reading; point wix.config.json site.outputDirectory at "./${outDir ?? "."}"`;
289
+ console.log(JSON.stringify(result));
290
+ process.exit(0);
291
+ }
292
+
196
293
  if (uploadPolicies.length && stack !== "astro") {
197
294
  console.log(JSON.stringify({ error: "mediaUpload currently requires --stack astro because it ships a validated server endpoint" }));
198
295
  process.exit(1);
@@ -212,8 +309,9 @@ for (const policy of uploadPolicies) {
212
309
  }
213
310
 
214
311
  // ---- copy ---------------------------------------------------------------------------------------
215
- // Shared core — always.
216
- cpSync(join(REF, "shared", "app"), SRC, COPY);
312
+ // Shared core — always. The lib stack takes only the framework-free wix/ part (no styles).
313
+ if (stack === "lib") cpSync(join(REF, "shared", "app", "wix"), join(SRC, "wix"), COPY);
314
+ else cpSync(join(REF, "shared", "app"), SRC, COPY);
217
315
 
218
316
  if (uploadPolicies.length) {
219
317
  cpSync(join(REF, "shared", "capabilities", "media-upload", "app"), SRC, COPY);
@@ -221,10 +319,12 @@ if (uploadPolicies.length) {
221
319
  }
222
320
 
223
321
  const unknown = requested.filter((v) => !VERTICALS.includes(v));
224
- const wantedDeps = { ...SHARED_DEPS };
322
+ // lib: only @wix/sdk from the shared deps — the Tailwind pair serves the components, which don't ship there.
323
+ const wantedDeps = stack === "lib" ? { "@wix/sdk": SHARED_DEPS["@wix/sdk"] } : { ...SHARED_DEPS };
225
324
  if (uploadPolicies.length) Object.assign(wantedDeps, CAPABILITY_DEPS["media-upload"]);
226
325
  for (const vertical of requested.filter((v) => VERTICALS.includes(v))) {
227
- cpSync(join(REF, vertical, "app"), SRC, COPY);
326
+ if (stack === "lib") cpSync(join(REF, vertical, "app", "wix"), join(SRC, "wix"), COPY);
327
+ else cpSync(join(REF, vertical, "app"), SRC, COPY);
228
328
  if (stack === "astro" && existsSync(join(REF, vertical, "app-astro"))) {
229
329
  cpSync(join(REF, vertical, "app-astro"), SRC, COPY);
230
330
  }
@@ -386,7 +486,7 @@ if (!clientId && existsSync(join(PROJECT, "wix.config.json"))) {
386
486
  JSON.parse(readFileSync(join(PROJECT, "wix.config.json"), "utf8")).appId ??
387
487
  null;
388
488
  }
389
- if (stack === "react") {
489
+ if (stack === "react" || stack === "lib") {
390
490
  if (clientId) {
391
491
  const current = readFileSync(CONFIG_TS, "utf8");
392
492
  // A non-null id already set wins; only fill the shipped null placeholder.
@@ -404,7 +504,7 @@ if (stack === "react") {
404
504
  }
405
505
  } else {
406
506
  result.clientId =
407
- "missing — pass --client-id (react stack needs the public OAuth client id)";
507
+ `missing — pass --client-id (the ${stack} stack needs the public OAuth client id)`;
408
508
  }
409
509
  }
410
510
  if (requested.includes("members")) {