1// Attach a NEW headless frontend to an EXISTING Wix site — setup's sibling for a site that already2// exists (named in the prompt by its id). `init`/`wix create` cannot do this: they always create3// a site. This script does what they do AFTER creating one — OAuth app, managed hosting, env4// vars, `wix.config.json` — against the site id given, then scaffolds and deploys like5// setup.mjs. Nothing on the site is changed or deleted. Seeding is the caller's decision: with6// --plan the first vertical's seed runs with it (additive, idempotent by name; what the site already7// held comes back as preexisting[]); without one nothing is seeded — the site owns its content.8//9// node <SKILL_ROOT>/install/attach.mjs [--site <metaSiteId>] --business-name "<Brand>" \10// --vertical <a>[,<b>…] [--plan plan.json] [--stack astro|react|lib|static] [--hosting wix|self [--origin <url>[,<url>]]] \11// [--subfolder [--folder-name <name>]]12//13
100// A migration preview (wix.config.json names a deploy-only site, .env.local names the site being
101// migrated) is already provisioned: its app, hosting and credentials came with the folder. Nothing
102// here applies — setup.mjs takes it (SKILL.md step 3).
103if (cwdConfig) {
104 const ctx = siteContext({ cwd });
105 if (ctx.migration.active) {
106 fail("place", `this folder deploys a migration preview of site ${ctx.migration.parentSiteId} (deploy site ${ctx.deploy.siteId}): the app and hosting are provisioned already — run setup.mjs --vertical <vertical> [--stack <stack>] here instead; nothing is attached or re-pointed`);
122 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)`);
123}
124for (const v of verticals) if (!knownVerticals.includes(v)) fail("args", `unknown vertical "${v}" — shipped verticals: ${knownVerticals.join(", ")}`);
127for (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}"`);
129 fail("args", "--hosting self scaffolds nothing: pass --stack for the frontend you will host (react|lib|static), or attach from inside the project on disk");
130}
131if (!subfolder) {
132 if (hasProject && cwdConfig && !starter) {
133 fail("place", "this folder is already a Wix project with a frontend (wix.config.json and a project): nothing to attach. deploy.mjs <vertical> adds this skill's code or a solution to it; then node <SKILL_ROOT>/install/install.mjs");
134 }
135 if (cwdConfig && (cwdConfig.siteId ?? cwdConfig.projectId) !== siteId) {
136 fail("place", `this folder is attached to site ${cwdConfig.siteId ?? cwdConfig.projectId}, not ${siteId} — a folder is never re-pointed; attach the other site with --subfolder`);
137 }
138 if (hasProject && !starter && !stackFlag) {
139 fail("args", "attaching a project on disk needs --stack astro|react|lib|static — the stack you resolved in SKILL.md step 1 for this project");
181if (subfolder && existsSync(join(projectDir, "wix.config.json"))) fail("args", `${folderName}/ is already a Wix project — pick --folder-name, or run deploy.mjs there`);
182
183// ---- 1 · attach: OAuth app + hosting + env, on the manage host with a site token -----------------
184// The same calls `init` makes after it has created a site, in the same order.
218 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" });
276 hosting === "self" ? "no Wix hosting: the frontend is yours to host; its origins must be on the OAuth app's allow-list"
277 : frontend?.serving === true
278 ? `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.`
279 : frontend?.serving === false
280 ? `${hosting === "created" ? "hosting created" : "hosting exists"} at ${baseUrl}; nothing served yet — the first release from this project fills it`
281 : frontend
282 ? `could not read whether a frontend is serving at ${baseUrl}; check the address before releasing`
317 next: `scaffold the ${stack} project in this folder per SKILL.md, then deploy.mjs <vertical…> --stack ${stack}${planPath ? ` --plan ${planPath}` : ""} (the client id is read from wix.config.json); ` +
318 (planPath ? `then seed with the plan: node ${join(TEMPLATES, verticals[0], "seed", `seed-${verticals[0]}.mjs`)} ${planPath}` : `no plan given, so nothing seeds — the site owns its content`) +
319 (hosting === "self" ? `; you host it: origins on the OAuth app allow-list now: ${origins.join(", ") || "none — add them before the first checkout test"}` : "") });
320 process.exit(0);
321}
322
323// ---- 3 · deploy shipped code + deps (adds nothing to a scaffold from the template) --------------
354 emit("seeding_started", { vertical: verticals[0], ...seed, ...(verticals.length > 1 ? { note: `the plan seeds ${verticals[0]}; the other verticals' seeds run afterwards, each with its own plan` } : {}) });
355} else {
356 if (siteOrigin === "init") {
357 emit("note", { step: "seed", detail: `this site was made for this run (init) and is empty: draft a plan per ${join(TEMPLATES, verticals[0], "seed", "SEED.md")} — the brief's content, else demo content — and run node <SKILL_ROOT>/templates/${verticals[0]}/seed/seed-<vertical>.mjs plan.json from the project root` });
358 }
359 if (verticals.includes("members")) {
360 emit("note", { step: "seed", detail: `members: the Members Area app (the profile layer, no content) is installed by ${join(TEMPLATES, "members", "seed", "seed-members.mjs")} — run it unless the site already has the app` });
383 ? "the plan is seeding in the background (additive; the result's preexisting[] names what the site already held) — sync on .seed-exit before release; "
384 : siteOrigin === "init"
385 ? "no plan given and this site was made for this run (empty): draft a plan per SEED.md and run the vertical's seed before release; "
386 : "no plan given, so nothing seeds — the site existed before this run and its content is its own; ") +
387 "get the measure of the site (SKILL.md step 3), theme + write the pages" +
388 (mode === "link" && hosting !== "self" ? "; make the project what its stack needs on Wix hosting (SKILL.md step 1)" : "") +
389 (install ? "; wait for the install marker" : "") +
390 (hosting === "self"
391 ? `; 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`
392 : "; then release as step 5 says for the stack"),