@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.
- package/package.json +1 -1
- package/skills/wix-app/SKILL.md +14 -112
- package/skills/wix-app/references/APP_TOOLS.md +2 -1
- package/skills/wix-app/references/BACKEND_EVENT.md +3 -3
- package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +41 -152
- package/skills/wix-app/references/DASHBOARD_MODAL.md +4 -4
- package/skills/wix-app/references/SDK_MODULE_MAP.md +44 -0
- package/skills/wix-app/references/SITE_PLUGIN.md +36 -190
- package/skills/wix-app/references/app-tools/TOOLS.md +3 -1
- package/skills/wix-app/references/custom-element-widget/SETTINGS_PANEL.md +21 -0
- package/skills/wix-app/references/dashboard-page/ECOM_NAVIGATION.md +17 -0
- package/skills/wix-app/references/dashboard-plugin/SLOTS.md +4 -18
- package/skills/wix-app/references/site-plugin/EXAMPLES.md +1 -1
- package/skills/wix-headless-fast/SKILL.md +302 -51
- package/skills/wix-headless-fast/entry/skill.md +11 -2
- package/skills/wix-headless-fast/install/AGENTS.md +32 -0
- package/skills/wix-headless-fast/install/agents-md.mjs +39 -0
- package/skills/wix-headless-fast/install/attach.mjs +360 -0
- package/skills/wix-headless-fast/install/deploy.mjs +109 -9
- package/skills/wix-headless-fast/install/setup.mjs +239 -0
- package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +226 -60
- package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +2 -3
- package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +2 -3
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +24 -116
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +16 -64
- package/skills/wix-headless-fast/references/blog/app/wix/blog/blog-feed-store.ts +137 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/post-store.ts +78 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts-core.ts +117 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +13 -60
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy-core.ts +47 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +10 -28
- package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +2 -4
- package/skills/wix-headless-fast/references/blog/rest/posts.ts +42 -0
- package/skills/wix-headless-fast/references/blog/rest/taxonomy.ts +62 -0
- package/skills/wix-headless-fast/references/blog/seed/SEED.md +9 -0
- package/skills/wix-headless-fast/references/blog/seed/read-site.mjs +22 -0
- package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +181 -33
- package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +2 -1
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +26 -132
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +18 -49
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-core.ts +218 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-flow-store.ts +169 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +57 -142
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-core.ts +92 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-store.ts +90 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +22 -61
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +3 -1
- package/skills/wix-headless-fast/references/bookings/rest/booking.ts +113 -0
- package/skills/wix-headless-fast/references/bookings/rest/services.ts +55 -0
- package/skills/wix-headless-fast/references/bookings/seed/SEED.md +10 -0
- package/skills/wix-headless-fast/references/bookings/seed/read-site.mjs +28 -0
- package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +238 -62
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +19 -52
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +16 -36
- package/skills/wix-headless-fast/references/cms/app/wix/cms/collection-store.ts +158 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/item-store.ts +93 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items-core.ts +177 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +13 -41
- package/skills/wix-headless-fast/references/cms/rest/items.ts +125 -0
- package/skills/wix-headless-fast/references/cms/seed/SEED.md +9 -0
- package/skills/wix-headless-fast/references/cms/seed/read-site.mjs +20 -0
- package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +241 -83
- package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +1 -1
- package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +7 -5
- package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +2 -2
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +28 -110
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +19 -54
- package/skills/wix-headless-fast/references/events/app/wix/events/events-core.ts +130 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/events-store.ts +97 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +13 -94
- package/skills/wix-headless-fast/references/events/app/wix/events/registration-core.ts +113 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/registration-store.ts +156 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +38 -69
- package/skills/wix-headless-fast/references/events/rest/events.ts +57 -0
- package/skills/wix-headless-fast/references/events/rest/registration.ts +83 -0
- package/skills/wix-headless-fast/references/events/seed/SEED.md +9 -0
- package/skills/wix-headless-fast/references/events/seed/read-site.mjs +26 -0
- package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +216 -26
- package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +35 -267
- package/skills/wix-headless-fast/references/forms/app/wix/forms/form-store.ts +332 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms-core.ts +238 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +28 -243
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions-core.ts +216 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +32 -204
- package/skills/wix-headless-fast/references/forms/rest/forms.ts +34 -0
- package/skills/wix-headless-fast/references/forms/rest/submissions.ts +56 -0
- package/skills/wix-headless-fast/references/forms/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/forms/seed/read-site.mjs +20 -0
- package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +15 -8
- package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +316 -63
- package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +1 -1
- package/skills/wix-headless-fast/references/members/app/wix/members/auth-core.ts +105 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +36 -65
- package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +1 -1
- package/skills/wix-headless-fast/references/members/app/wix/members/members-core.ts +43 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +11 -32
- package/skills/wix-headless-fast/references/members/rest/auth.ts +215 -0
- package/skills/wix-headless-fast/references/members/rest/members.ts +26 -0
- package/skills/wix-headless-fast/references/members/seed/read-site.mjs +15 -0
- package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +240 -59
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +2 -2
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +2 -2
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx +35 -0
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +8 -38
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +23 -50
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +16 -34
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +22 -49
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collection-projects-store.ts +82 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collections-store.ts +58 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio-core.ts +177 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +31 -118
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts +81 -0
- package/skills/wix-headless-fast/references/portfolio/rest/portfolio.ts +75 -0
- package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/portfolio/seed/read-site.mjs +20 -0
- package/skills/wix-headless-fast/references/portfolio/seed/seed-portfolio.mjs +19 -21
- package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +214 -51
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +9 -20
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +7 -32
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/SubscribeButton.tsx +41 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +12 -33
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +17 -34
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts +54 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-core.ts +106 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts +64 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +7 -55
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts +43 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +11 -34
- package/skills/wix-headless-fast/references/pricing-plans/rest/plans.ts +31 -0
- package/skills/wix-headless-fast/references/pricing-plans/rest/purchase.ts +20 -0
- package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/pricing-plans/seed/read-site.mjs +21 -0
- package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +242 -47
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +2 -2
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +2 -2
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +14 -35
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +28 -171
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-core.ts +155 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-store.ts +89 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +23 -117
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering-core.ts +124 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +45 -114
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservation-store.ts +193 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations-core.ts +94 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +15 -87
- package/skills/wix-headless-fast/references/restaurants/rest/menu.ts +60 -0
- package/skills/wix-headless-fast/references/restaurants/rest/ordering.ts +169 -0
- package/skills/wix-headless-fast/references/restaurants/rest/reservations.ts +68 -0
- package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/restaurants/seed/read-site.mjs +25 -0
- package/skills/wix-headless-fast/references/restaurants/seed/seed-restaurants.mjs +25 -5
- package/skills/wix-headless-fast/references/shared/SUPPLIED-CONTENT.md +23 -0
- package/skills/wix-headless-fast/references/shared/app/wix/media.ts +45 -4
- package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +13 -0
- package/skills/wix-headless-fast/references/shared/rest/client.ts +155 -0
- package/skills/wix-headless-fast/references/shared/rest/config.ts +3 -0
- package/skills/wix-headless-fast/references/shared/rest/media.ts +65 -0
- package/skills/wix-headless-fast/references/shared/seed/read-site.mjs +99 -0
- package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +186 -63
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/OptionPicker.tsx +95 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +29 -58
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +29 -163
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +26 -300
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-core.ts +121 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +49 -162
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +314 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +47 -370
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts +147 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/shop-store.ts +289 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +2 -0
- package/skills/wix-headless-fast/references/storefront/rest/cart.ts +132 -0
- package/skills/wix-headless-fast/references/storefront/rest/catalog.ts +123 -0
- package/skills/wix-headless-fast/references/storefront/seed/SEED.md +31 -9
- package/skills/wix-headless-fast/references/storefront/seed/read-site.mjs +38 -0
- package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +57 -50
- package/skills/wix-manage/references/ecommerce/ecom-shipping.md +1 -1
- package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-create-coupon.md +10 -19
- package/skills/wix-manage/references/events/create-wix-event.md +5 -1
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +0 -40
- package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +3 -0
- package/skills/wix-vibe-headless/references/storefront/seed/seed-store.cjs +6 -0
- package/skills/wix-headless-fast/install/fast-path.mjs +0 -186
- 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
|
-
|
|
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
|
-
|
|
507
|
+
`missing — pass --client-id (the ${stack} stack needs the public OAuth client id)`;
|
|
408
508
|
}
|
|
409
509
|
}
|
|
410
510
|
if (requested.includes("members")) {
|