@wix/agent-skills 1.22.0 → 1.24.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 +12 -4
- package/skills/wix-app/references/APP_VALIDATION.md +16 -10
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +64 -50
- package/skills/wix-app/references/SDK_MODULE_MAP.md +2 -0
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE.md +1 -1
- package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +4 -3
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +28 -25
- package/skills/wix-app/references/editor-react-component/BRANDED-COMPONENTS.md +8 -2
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +12 -14
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +4 -3
- package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +66 -9
- package/skills/wix-app/references/editor-react-component/EDITOR-EXTENSION-CONFIGURATION.md +25 -0
- package/skills/wix-app/references/editor-react-component/OVERFLOW.md +146 -0
- package/skills/wix-app/references/editor-react-component/PARTS.md +6 -0
- package/skills/wix-app/scripts/scan-a11y-eslint.cjs +25 -1
- package/skills/{wix-headless-fast → wix-headless-kit}/SKILL.md +96 -50
- package/skills/{wix-headless-fast → wix-headless-kit}/cold-start/bootstrap.mjs +2 -2
- package/skills/{wix-headless-fast → wix-headless-kit}/cold-start/cold-start.md +26 -31
- package/skills/wix-headless-kit/guides/capabilities.md +76 -0
- package/skills/{wix-headless-fast → wix-headless-kit}/guides/existing-site.md +3 -1
- package/skills/wix-headless-kit/guides/feedback.md +98 -0
- package/skills/wix-headless-kit/guides/migration.md +54 -0
- package/skills/{wix-headless-fast → wix-headless-kit}/guides/reference-mode.md +4 -1
- package/skills/{wix-headless-fast → wix-headless-kit}/install/AGENTS.md +6 -1
- package/skills/{wix-headless-fast → wix-headless-kit}/install/agents-md.mjs +5 -1
- package/skills/{wix-headless-fast → wix-headless-kit}/install/attach.mjs +14 -1
- package/skills/wix-headless-kit/install/check.mjs +87 -0
- package/skills/wix-headless-kit/install/context.mjs +169 -0
- package/skills/{wix-headless-fast → wix-headless-kit}/install/deploy.mjs +101 -23
- package/skills/{wix-headless-fast → wix-headless-kit}/install/setup.mjs +115 -41
- package/skills/{wix-headless-fast → wix-headless-kit}/install/templates.mjs +1 -1
- package/skills/wix-manage/SKILL.md +3 -3
- package/skills/wix-manage/references/app-installation/install-wix-apps.md +68 -2
- package/skills/wix-manage/references/cms/cms-dashboard-navigation.md +2 -2
- package/skills/wix-manage/references/cms/cms-data-items-crud.md +16 -16
- package/skills/wix-manage/references/cms/cms-publishing-flow.md +15 -15
- package/skills/wix-manage/references/cms/cms-schema-management.md +6 -6
- package/skills/wix-manage/references/contacts/bulk-label-and-unlabel-contacts.md +37 -4
- package/skills/wix-manage/references/ecommerce/ecom-pricing.md +29 -3
- package/skills/wix-manage/references/sites/query-sites.md +1 -8
- package/skills/wix-manage/references/sites/site-import.md +101 -37
- package/skills/wix-auth/SKILL.md +0 -12
- package/skills/wix-auth/references/device-flow.md +0 -140
- /package/skills/{wix-headless-fast → wix-headless-kit}/install/lock.mjs +0 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// The site context of a project folder — the one place that says which site a call targets.
|
|
2
|
+
//
|
|
3
|
+
// node <SKILL_ROOT>/install/context.mjs [--refresh] [--no-pull] (prints one JSON object)
|
|
4
|
+
//
|
|
5
|
+
// Two identities live in a Wix project and are the same site almost always:
|
|
6
|
+
// deploy — `wix.config.json` (`siteId`, `appId`): where `wix release` uploads. The CLI's business.
|
|
7
|
+
// content — `.env.local` (`WIX_CLIENT_ID`, what `wix env pull` writes): the app the SDK client
|
|
8
|
+
// runs as, on every stack (the Astro integration reads WIX_CLIENT_ID from the env and
|
|
9
|
+
// never the config; the other stacks get it copied into src/wix/config.ts by deploy.mjs).
|
|
10
|
+
// They differ on a MIGRATION PREVIEW: a project whose config points at a fresh site created only to
|
|
11
|
+
// host the deployment, while `.env.local` carries the credentials of the site being migrated (the
|
|
12
|
+
// parent) and says so. Then every admin, discovery and seed call targets the parent, the SDK client
|
|
13
|
+
// is the parent's, and only the release goes to the child. Completing the migration is a CLI step
|
|
14
|
+
// that does not exist yet; this module only reads the state.
|
|
15
|
+
//
|
|
16
|
+
// `env pull` runs here when `.env.local` is missing (or --refresh): it is the source of the content
|
|
17
|
+
// identity, and the Astro build refuses to run without it. Non-interactive (CI=1); a failure is
|
|
18
|
+
// reported in `pullError` and the config's ids stand in, so a caller can still work offline.
|
|
19
|
+
import { spawnSync } from "node:child_process";
|
|
20
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
|
|
23
|
+
// The variables the editor-to-headless migration writes into the child project's `prod`
|
|
24
|
+
// environment (headless-bo, editorMigrations/environmentMetadata.ts), which `wix env pull` copies
|
|
25
|
+
// into `.env.local` unchanged. The names are the platform's and are spelled once, here.
|
|
26
|
+
export const ENV = {
|
|
27
|
+
clientId: "WIX_CLIENT_ID",
|
|
28
|
+
parentSiteId: "EDITOR_MIGRATION_PARENT_SITE_ID",
|
|
29
|
+
/** ACTIVE while the migration is under way; COMPLETED once the parent serves the child's frontend. */
|
|
30
|
+
status: "EDITOR_MIGRATION_STATUS",
|
|
31
|
+
childSiteId: "EDITOR_MIGRATION_CHILD_SITE_ID",
|
|
32
|
+
childAppId: "EDITOR_MIGRATION_CHILD_APP_ID",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** KEY=value lines of a dotenv file, quotes stripped; null when the file is absent. */
|
|
36
|
+
export function readEnvFile(file) {
|
|
37
|
+
if (!existsSync(file)) return null;
|
|
38
|
+
const out = {};
|
|
39
|
+
for (const raw of readFileSync(file, "utf8").split(/\r?\n/)) {
|
|
40
|
+
const line = raw.trim();
|
|
41
|
+
if (!line || line.startsWith("#")) continue;
|
|
42
|
+
const eq = line.indexOf("=");
|
|
43
|
+
if (eq === -1) continue;
|
|
44
|
+
const key = line.slice(0, eq).trim().replace(/^export\s+/, "");
|
|
45
|
+
let value = line.slice(eq + 1).trim();
|
|
46
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
47
|
+
value = value.slice(1, -1).replace(/\\"/g, '"');
|
|
48
|
+
}
|
|
49
|
+
out[key] = value;
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function readWixConfig(cwd) {
|
|
55
|
+
const file = join(cwd, "wix.config.json");
|
|
56
|
+
if (!existsSync(file)) return null;
|
|
57
|
+
try { return JSON.parse(readFileSync(file, "utf8")); } catch { return null; }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const ENV_PULL = ["-y", "@wix/cli@latest", "env", "pull"];
|
|
61
|
+
const runPull = (dir) => spawnSync("npx", ENV_PULL, { cwd: dir, env: { ...process.env, CI: "1" }, encoding: "utf8", timeout: 180_000 });
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* `wix env pull` into `cwd/.env.local`, in place. Every project shape gets the command since Wix CLI
|
|
65
|
+
* 1.1.253 (2026-09-29): before it, a config with `site.outputDirectory` (the static stack, a site
|
|
66
|
+
* published through the drop flow) landed in a limited command set without `env`, and this pulled
|
|
67
|
+
* through a temp copy of the config. Non-interactive; a failure comes back as `error`.
|
|
68
|
+
*/
|
|
69
|
+
export function pullEnv(cwd) {
|
|
70
|
+
const envFile = join(cwd, ".env.local");
|
|
71
|
+
const r = runPull(cwd);
|
|
72
|
+
if (r.status === 0 && existsSync(envFile)) return { ok: true, via: "in place" };
|
|
73
|
+
return { ok: false, error: (r.stderr || r.stdout || "env pull produced no .env.local — is the Wix CLI logged in? (npx @wix/cli@latest whoami)").trim().slice(-400) };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Whether a frontend exists in this folder: a `package.json` (a bundled project), an `index.html` at
|
|
78
|
+
* the root (plain pages), or an `index.html` inside the folder the config's `site.outputDirectory`
|
|
79
|
+
* names (a static site laid out for release, pages under site/). Nothing else counts — not this
|
|
80
|
+
* skill's code, not the skills folder, not AGENTS.md: those say who ran here, not what is here.
|
|
81
|
+
*/
|
|
82
|
+
export function frontendPresent(cwd = process.cwd()) {
|
|
83
|
+
const has = (p) => existsSync(join(cwd, p));
|
|
84
|
+
const outDir = (readWixConfig(cwd)?.site?.outputDirectory ?? "").replace(/^\.\//, "").replace(/\/$/, "");
|
|
85
|
+
return {
|
|
86
|
+
packageJson: has("package.json"),
|
|
87
|
+
rootIndex: has("index.html"),
|
|
88
|
+
outputIndex: !!outDir && outDir !== "." && has(join(outDir, "index.html")),
|
|
89
|
+
outputDirectory: outDir || null,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* What the folder IS, from five file facts — the one classification every script and SKILL.md
|
|
95
|
+
* step 3 share: wix.config.json, its site.outputDirectory, the migration variables in .env.local,
|
|
96
|
+
* package.json, index.html. `migrationActive` comes from siteContext (it needs `.env.local`).
|
|
97
|
+
*
|
|
98
|
+
* empty nothing that reads as a project → setup CREATE: the run makes the site, seeds the plan
|
|
99
|
+
* project a frontend, no wix.config.json → setup ADOPT: `init` gives it a new, empty site, seeds the plan
|
|
100
|
+
* config-only a config, no frontend → attach.mjs on the config's site; nothing seeded
|
|
101
|
+
* migration a config whose .env.local declares an ACTIVE editor migration, with or without the blank
|
|
102
|
+
* Astro starter the download carries → setup MIGRATE; nothing seeded. Decided before the
|
|
103
|
+
* frontend test: the starter's package.json must not read as a project to iterate on
|
|
104
|
+
* wix-project a config AND a frontend (package.json, or index.html in the output folder) → iterate:
|
|
105
|
+
* never scaffold, init or reseed; deploy.mjs adds a solution, edits, release
|
|
106
|
+
* published-static a config, index.html at the ROOT, no package.json, no laid-out output folder (a site
|
|
107
|
+
* published through the drop flow and downloaded) → setup makes site/ the upload and
|
|
108
|
+
* deploys the REST layer; the config's site, no init, nothing seeded
|
|
109
|
+
*/
|
|
110
|
+
export function folderShape(cwd = process.cwd(), { migrationActive = false } = {}) {
|
|
111
|
+
const config = existsSync(join(cwd, "wix.config.json"));
|
|
112
|
+
const f = frontendPresent(cwd);
|
|
113
|
+
const next = {
|
|
114
|
+
empty: "setup.mjs --vertical <v> --business-name <brand> [--plan]: creates the site here and seeds the plan (CREATE)",
|
|
115
|
+
project: "setup.mjs --vertical <v> --stack <stack> [--plan]: init links the folder to a new, empty site, seeds the plan and deploys (ADOPT)",
|
|
116
|
+
"config-only": "attach.mjs: the site exists and has no frontend yet; read what it holds (the vertical's read-site.mjs) — nothing is seeded; the vertical's seed module with a plan only when the brief supplies or describes content",
|
|
117
|
+
migration: "setup.mjs (MIGRATE): the shipped code into the starter the download carries (or the composed template around a bare config); the site being migrated owns its content — guides/migration.md",
|
|
118
|
+
"wix-project": "iterate: never scaffold, init or reseed. deploy.mjs <vertical…> --stack <stack> adds a solution, then ONE npm install; file edits for a change; release. Read the site (read-site.mjs) before any seed module runs",
|
|
119
|
+
"published-static": "setup.mjs --vertical <v>: the config's site, no init; site/ becomes the upload and the REST layer lands in site/js/wix/; move the pages, styles and assets into site/. Nothing is seeded: read the site, then run the vertical's seed module with a plan when the brief gives content; release keeps the URL",
|
|
120
|
+
};
|
|
121
|
+
let shape;
|
|
122
|
+
if (!config) shape = f.packageJson || f.rootIndex ? "project" : "empty";
|
|
123
|
+
else if (migrationActive) shape = "migration";
|
|
124
|
+
else if (f.packageJson || f.outputIndex) shape = "wix-project";
|
|
125
|
+
else if (f.rootIndex) shape = "published-static";
|
|
126
|
+
else shape = "config-only";
|
|
127
|
+
return { shape, next: next[shape], facts: { config, ...f } };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* `{ folder: { shape, next, tells }, deploy: { siteId, appId }, content: { siteId, clientId },
|
|
132
|
+
* migration: { active, parentSiteId }, env: { file, present, pulled }, pullError?, warnings: [] }`.
|
|
133
|
+
* `pull`: "auto" (default) pulls when `.env.local` is missing; true always; false never.
|
|
134
|
+
*/
|
|
135
|
+
export function siteContext({ cwd = process.cwd(), pull = "auto" } = {}) {
|
|
136
|
+
const config = readWixConfig(cwd) ?? {};
|
|
137
|
+
const deploy = { siteId: config.siteId ?? config.projectId ?? null, appId: config.appId ?? null };
|
|
138
|
+
const envFile = join(cwd, ".env.local");
|
|
139
|
+
let pulled = false, pullError;
|
|
140
|
+
if (deploy.siteId && (pull === true || (pull === "auto" && !existsSync(envFile)))) {
|
|
141
|
+
const r = pullEnv(cwd);
|
|
142
|
+
if (r.ok) pulled = r.via;
|
|
143
|
+
else pullError = r.error;
|
|
144
|
+
}
|
|
145
|
+
const env = readEnvFile(envFile) ?? {};
|
|
146
|
+
const parentSiteId = env[ENV.parentSiteId] || null;
|
|
147
|
+
const status = env[ENV.status];
|
|
148
|
+
// ACTIVE (or a parent id with no status yet) is a migration under way; COMPLETED means the parent
|
|
149
|
+
// already serves this frontend and the project is an ordinary one again.
|
|
150
|
+
const active = !!parentSiteId && (status === undefined || status.trim().toUpperCase() === "ACTIVE");
|
|
151
|
+
const migration = { active, parentSiteId: active ? parentSiteId : null, ...(status ? { status: status.trim().toUpperCase() } : {}) };
|
|
152
|
+
const clientId = env[ENV.clientId] || deploy.appId;
|
|
153
|
+
const content = { siteId: active ? parentSiteId : deploy.siteId, clientId };
|
|
154
|
+
const warnings = [];
|
|
155
|
+
if (env[ENV.clientId] && deploy.appId && env[ENV.clientId] !== deploy.appId && !active && migration.status !== "COMPLETED") {
|
|
156
|
+
warnings.push(`.env.local ${ENV.clientId} differs from wix.config.json appId and no migration is declared — the SDK client runs as the env's app, the release goes to the config's site`);
|
|
157
|
+
}
|
|
158
|
+
if (active && migration.parentSiteId === deploy.siteId) {
|
|
159
|
+
warnings.push("the migration's parent site is the deploy site itself — nothing is being migrated");
|
|
160
|
+
}
|
|
161
|
+
return { folder: folderShape(cwd, { migrationActive: active }), deploy, content, migration, env: { file: envFile, present: Object.keys(env).length > 0, pulled }, ...(pullError ? { pullError } : {}), warnings };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ---- CLI ----------------------------------------------------------------------------------------
|
|
165
|
+
if (process.argv[1] && /context\.mjs$/.test(process.argv[1])) {
|
|
166
|
+
const argv = process.argv.slice(2);
|
|
167
|
+
const pull = argv.includes("--no-pull") ? false : argv.includes("--refresh") ? true : "auto";
|
|
168
|
+
console.log(JSON.stringify(siteContext({ pull }), null, 2));
|
|
169
|
+
}
|
|
@@ -45,6 +45,7 @@ import { dirname, join, resolve } from "node:path";
|
|
|
45
45
|
import { fileURLToPath } from "node:url";
|
|
46
46
|
import { templatesDir } from "./templates.mjs";
|
|
47
47
|
import { syncLockRoot } from "./lock.mjs";
|
|
48
|
+
import { siteContext } from "./context.mjs";
|
|
48
49
|
|
|
49
50
|
const SKILL_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
50
51
|
// The shipped code: the repository's templates/ (a checkout, the cache, or fetched now).
|
|
@@ -80,7 +81,11 @@ const CAPABILITY_DEPS = {
|
|
|
80
81
|
"@wix/media": "^1.0.271",
|
|
81
82
|
"@wix/essentials": "^1.0.10",
|
|
82
83
|
},
|
|
84
|
+
"site-search": {
|
|
85
|
+
"@wix/search": "^1.0.90",
|
|
86
|
+
},
|
|
83
87
|
};
|
|
88
|
+
const SITE_SEARCH = join("shared", "capabilities", "site-search");
|
|
84
89
|
const VERTICAL_DEPS = {
|
|
85
90
|
storefront: {
|
|
86
91
|
core: {
|
|
@@ -106,6 +111,18 @@ const VERTICAL_DEPS = {
|
|
|
106
111
|
"@wix/essentials": "^1.0.6",
|
|
107
112
|
},
|
|
108
113
|
},
|
|
114
|
+
rentals: {
|
|
115
|
+
// A rental is a Bookings service with rentals-specific values: the same SDK modules as bookings.
|
|
116
|
+
core: {
|
|
117
|
+
"@wix/bookings": "^1.0.1650",
|
|
118
|
+
"@wix/auto_sdk_ecom_cart-v-2": "^1.0.192",
|
|
119
|
+
"@wix/redirects": "^1.0.125",
|
|
120
|
+
},
|
|
121
|
+
astro: {
|
|
122
|
+
"@wix/seo": "^1.0.79",
|
|
123
|
+
"@wix/essentials": "^1.0.6",
|
|
124
|
+
},
|
|
125
|
+
},
|
|
109
126
|
blog: {
|
|
110
127
|
core: {
|
|
111
128
|
"@wix/blog": "^1.0.645",
|
|
@@ -160,6 +177,19 @@ const VERTICAL_DEPS = {
|
|
|
160
177
|
"@wix/redirects": "^1.0.125",
|
|
161
178
|
},
|
|
162
179
|
},
|
|
180
|
+
faq: {
|
|
181
|
+
core: {
|
|
182
|
+
"@wix/faq": "^1.0.76",
|
|
183
|
+
},
|
|
184
|
+
// no astro block: the FAQ page is a plain page (no wixMetadata item routing); its JSON-LD is inline
|
|
185
|
+
},
|
|
186
|
+
donations: {
|
|
187
|
+
core: {
|
|
188
|
+
"@wix/donations": "^1.0.64",
|
|
189
|
+
"@wix/ecom": "^1.0.2454",
|
|
190
|
+
"@wix/redirects": "^1.0.125",
|
|
191
|
+
},
|
|
192
|
+
},
|
|
163
193
|
};
|
|
164
194
|
|
|
165
195
|
const COPY = { recursive: true, force: false, errorOnExist: false };
|
|
@@ -211,6 +241,27 @@ if (!Array.isArray(uploadPolicies)) {
|
|
|
211
241
|
console.log(JSON.stringify({ error: "plan.capabilities.mediaUpload.policies must be an array" }));
|
|
212
242
|
process.exit(1);
|
|
213
243
|
}
|
|
244
|
+
// Site search is on when the plan carries the entry (`{}` is enough; `true` too). Its fields are
|
|
245
|
+
// data for the generated config; the shipped core drops unknown type names, so only shapes are checked.
|
|
246
|
+
const siteSearchEntry = plan.capabilities?.siteSearch;
|
|
247
|
+
const siteSearch = siteSearchEntry === true ? {} : siteSearchEntry && typeof siteSearchEntry === "object" ? siteSearchEntry : null;
|
|
248
|
+
if (siteSearchEntry != null && siteSearchEntry !== false && !siteSearch) {
|
|
249
|
+
console.log(JSON.stringify({ error: "plan.capabilities.siteSearch must be an object (or true)" }));
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
if (siteSearch) {
|
|
253
|
+
const strings = (v) => Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
254
|
+
const map = (v) => v === undefined || (v && typeof v === "object" && !Array.isArray(v) && Object.values(v).every((x) => typeof x === "string"));
|
|
255
|
+
if ((siteSearch.types !== undefined && !strings(siteSearch.types)) || !map(siteSearch.routes) || !map(siteSearch.labels)) {
|
|
256
|
+
console.log(JSON.stringify({ error: "plan.capabilities.siteSearch: types must be string[]; routes and labels must be objects of strings" }));
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// The generated file is data only; the shipped core resolves defaults (types, routes, labels) from it.
|
|
261
|
+
const siteSearchConfigSource = () =>
|
|
262
|
+
`// Generated by wix-headless-kit install/deploy.mjs from plan.capabilities.siteSearch.\n` +
|
|
263
|
+
`// Edit the plan and redeploy. Defaults for anything omitted are in search-core.ts (resolveConfig).\n` +
|
|
264
|
+
`export const siteSearchPlan = ${JSON.stringify(siteSearch, null, 2)};\n`;
|
|
214
265
|
// ---- static stack: the REST layer, composed flat and stripped ------------------------------------
|
|
215
266
|
if (stack === "static") {
|
|
216
267
|
const { spawnSync } = await import("node:child_process");
|
|
@@ -218,10 +269,9 @@ if (stack === "static") {
|
|
|
218
269
|
const OUT = outDir ? resolve(PROJECT, outDir) : PROJECT;
|
|
219
270
|
const JS = join(OUT, "js", "wix");
|
|
220
271
|
result.out = outDir ?? ".";
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
272
|
+
// The client id the browser mints visitor tokens with: --client-id, else the CONTENT app from
|
|
273
|
+
// .env.local (`wix env pull`; on a migration preview the parent site's app), else the config's appId.
|
|
274
|
+
let clientId = clientIdFlag ?? (existsSync(join(PROJECT, "wix.config.json")) ? siteContext({ cwd: PROJECT }).content.clientId : null);
|
|
225
275
|
if (!clientId) {
|
|
226
276
|
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" }));
|
|
227
277
|
process.exit(1);
|
|
@@ -233,19 +283,23 @@ if (stack === "static") {
|
|
|
233
283
|
}
|
|
234
284
|
// Only files that aren't there yet — a re-run restores, never clobbers (same as the copy below).
|
|
235
285
|
cpSync(join(REF, "shared", "rest"), JS, COPY);
|
|
236
|
-
|
|
237
|
-
|
|
286
|
+
// Every layer with a rest/ twin: the requested verticals, plus the site-search capability when the
|
|
287
|
+
// plan turns it on (same shape — rest/ beside app/wix/<name>/ — at templates/shared/capabilities).
|
|
288
|
+
const layers = requested.map((vertical) => [vertical, join(REF, vertical)]);
|
|
289
|
+
if (siteSearch) layers.push(["site-search", join(REF, SITE_SEARCH)]);
|
|
290
|
+
for (const [vertical, root] of layers) {
|
|
291
|
+
cpSync(join(root, "rest"), JS, COPY);
|
|
238
292
|
// The vertical's transport-agnostic core: types, every *-core.ts, and every *-store.ts under its
|
|
239
293
|
// app/wix/<vertical>/ — the stores are the hooks' logic without React, over the same function
|
|
240
294
|
// names the REST twin exports, so they run against it unchanged.
|
|
241
|
-
const appWix = join(
|
|
295
|
+
const appWix = join(root, "app", "wix", vertical);
|
|
242
296
|
for (const f of readdirSync(appWix)) {
|
|
243
297
|
// Every vertical has a types.ts; flat in one folder they would collide, so each lands as
|
|
244
298
|
// <vertical>-types.ts and the vertical's own files are pointed at it below.
|
|
245
299
|
if (f === "types.ts") cpSync(join(appWix, f), join(JS, `${vertical}-types.ts`), COPY);
|
|
246
300
|
else if (f.endsWith("-core.ts") || f.endsWith("-store.ts")) cpSync(join(appWix, f), join(JS, f), COPY);
|
|
247
301
|
}
|
|
248
|
-
const own = new Set([...readdirSync(join(
|
|
302
|
+
const own = new Set([...readdirSync(join(root, "rest")), ...readdirSync(appWix)].filter((f) => f.endsWith(".ts") && f !== "types.ts"));
|
|
249
303
|
for (const f of own) {
|
|
250
304
|
const fp = join(JS, f);
|
|
251
305
|
if (!existsSync(fp)) continue;
|
|
@@ -253,7 +307,12 @@ if (stack === "static") {
|
|
|
253
307
|
const fixed = src.replace(/from\s+"\.\/types(?:\.js)?"/g, `from "./${vertical}-types.js"`);
|
|
254
308
|
if (fixed !== src) writeFileSync(fp, fixed);
|
|
255
309
|
}
|
|
256
|
-
result.verticals.push(vertical);
|
|
310
|
+
if (VERTICALS.includes(vertical)) result.verticals.push(vertical);
|
|
311
|
+
}
|
|
312
|
+
if (siteSearch) {
|
|
313
|
+
// The REST twin imports the plan as data from beside it (shared/rest already owns config.ts).
|
|
314
|
+
writeFileSync(join(JS, "site-search-config.generated.ts"), siteSearchConfigSource());
|
|
315
|
+
result.capabilities = { ...result.capabilities, siteSearch: true };
|
|
257
316
|
}
|
|
258
317
|
// Browser ESM resolves nothing: every relative import needs its .js. The rest/ files are written
|
|
259
318
|
// that way; the files borrowed from app/wix/ are bundler-style and get the suffix here.
|
|
@@ -286,7 +345,7 @@ if (stack === "static") {
|
|
|
286
345
|
result.js = `${outDir ? outDir.replace(/\/$/, "") + "/" : ""}js/wix/*.js`;
|
|
287
346
|
// The note lists what actually landed, so it holds for every vertical (and several at once).
|
|
288
347
|
const stores = readdirSync(JS).filter((f) => f.endsWith("-store.js")).map((f) => `./js/wix/${f}`);
|
|
289
|
-
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}`);
|
|
348
|
+
const dataFiles = readdirSync(JS).filter((f) => f.endsWith(".js") && !f.endsWith("-store.js") && !f.endsWith("-core.js") && !f.endsWith("-types.js") && !f.endsWith(".generated.js") && !["client.js", "config.js", "media.js"].includes(f)).map((f) => `./js/wix/${f}`);
|
|
290
349
|
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 ?? "."}"`;
|
|
291
350
|
console.log(JSON.stringify(result));
|
|
292
351
|
process.exit(0);
|
|
@@ -319,11 +378,20 @@ if (uploadPolicies.length) {
|
|
|
319
378
|
cpSync(join(REF, "shared", "capabilities", "media-upload", "app"), SRC, COPY);
|
|
320
379
|
cpSync(join(REF, "shared", "capabilities", "media-upload", "app-astro"), SRC, COPY);
|
|
321
380
|
}
|
|
381
|
+
// Site search ships like a vertical (data layer, stores, hooks, components, a search page) at a
|
|
382
|
+
// disjoint path (src/wix/site-search/, src/components/site-search/, …), so the same per-stack rule
|
|
383
|
+
// applies: lib takes only app/wix/, astro adds app-astro/.
|
|
384
|
+
if (siteSearch) {
|
|
385
|
+
if (stack === "lib") cpSync(join(REF, SITE_SEARCH, "app", "wix"), join(SRC, "wix"), COPY);
|
|
386
|
+
else cpSync(join(REF, SITE_SEARCH, "app"), SRC, COPY);
|
|
387
|
+
if (stack === "astro") cpSync(join(REF, SITE_SEARCH, "app-astro"), SRC, COPY);
|
|
388
|
+
}
|
|
322
389
|
|
|
323
390
|
const unknown = requested.filter((v) => !VERTICALS.includes(v));
|
|
324
391
|
// lib: only @wix/sdk from the shared deps — the Tailwind pair serves the components, which don't ship there.
|
|
325
392
|
const wantedDeps = stack === "lib" ? { "@wix/sdk": SHARED_DEPS["@wix/sdk"] } : { ...SHARED_DEPS };
|
|
326
393
|
if (uploadPolicies.length) Object.assign(wantedDeps, CAPABILITY_DEPS["media-upload"]);
|
|
394
|
+
if (siteSearch) Object.assign(wantedDeps, CAPABILITY_DEPS["site-search"]);
|
|
327
395
|
for (const vertical of requested.filter((v) => VERTICALS.includes(v))) {
|
|
328
396
|
if (stack === "lib") cpSync(join(REF, vertical, "app", "wix"), join(SRC, "wix"), COPY);
|
|
329
397
|
else cpSync(join(REF, vertical, "app"), SRC, COPY);
|
|
@@ -345,12 +413,21 @@ if (uploadPolicies.length) {
|
|
|
345
413
|
// The generated file is data only. The shipped endpoint owns validation and elevation.
|
|
346
414
|
writeFileSync(
|
|
347
415
|
configFile,
|
|
348
|
-
`// Generated by wix-headless-
|
|
416
|
+
`// Generated by wix-headless-kit install/deploy.mjs from plan.capabilities.mediaUpload.\n` +
|
|
349
417
|
`// Edit the plan and redeploy; do not add browser-controlled policy selection here.\n` +
|
|
350
418
|
`export const mediaUploadPolicies = ${JSON.stringify(uploadPolicies, null, 2)} as const;\n`,
|
|
351
419
|
);
|
|
352
420
|
result.capabilities = { mediaUpload: uploadPolicies.map((policy) => policy.id) };
|
|
353
421
|
}
|
|
422
|
+
if (siteSearch) {
|
|
423
|
+
// The SDK transport imports the plan as data from beside it; defaults resolve in the shipped core.
|
|
424
|
+
writeFileSync(join(SRC, "wix", "site-search", "config.generated.ts"), siteSearchConfigSource());
|
|
425
|
+
result.capabilities = { ...result.capabilities, siteSearch: true };
|
|
426
|
+
if (siteSearch.install) {
|
|
427
|
+
// Indexing needs the Wix Site Search app on the site — a site mutation the deploy never makes.
|
|
428
|
+
result.note = [result.note, "siteSearch.install: run `node <SKILL_ROOT>/templates/shared/capabilities/site-search/seed/install.mjs` from the project root after the seed (results appear about half a minute later)"].filter(Boolean).join("; ");
|
|
429
|
+
}
|
|
430
|
+
}
|
|
354
431
|
|
|
355
432
|
// ---- dependency patch (fill-only) ----------------------------------------------------------------
|
|
356
433
|
if (result.verticals.length && existsSync(PKG_JSON)) {
|
|
@@ -416,12 +493,15 @@ if (stack === "astro" && result.verticals.length && existsSync(ASTRO_CONFIG)) {
|
|
|
416
493
|
|
|
417
494
|
// ---- ready-made links -----------------------------------------------------------------------------
|
|
418
495
|
// Emit the siteId and the dashboard deep links so nothing downstream re-derives or retypes them.
|
|
496
|
+
// The site the links point at is the CONTENT site — the parent on a migration preview, where the
|
|
497
|
+
// config's site only hosts the deployment (install/context.mjs).
|
|
419
498
|
const WIX_CONFIG = join(PROJECT, "wix.config.json");
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const siteId =
|
|
499
|
+
const ctx = existsSync(WIX_CONFIG) ? siteContext({ cwd: PROJECT }) : null;
|
|
500
|
+
if (ctx) {
|
|
501
|
+
const siteId = ctx.content.siteId;
|
|
423
502
|
if (siteId) {
|
|
424
503
|
result.siteId = siteId;
|
|
504
|
+
if (ctx.migration.active) result.migration = { parentSiteId: ctx.migration.parentSiteId, deploySiteId: ctx.deploy.siteId };
|
|
425
505
|
result.dashboardUrl = `https://manage.wix.com/dashboard/${siteId}`;
|
|
426
506
|
if (result.verticals.includes("storefront")) {
|
|
427
507
|
result.productsUrl = `https://manage.wix.com/dashboard/${siteId}/wix-stores/products`;
|
|
@@ -438,15 +518,13 @@ if (!requested.length) {
|
|
|
438
518
|
}
|
|
439
519
|
|
|
440
520
|
// ---- client ids ---------------------------------------------------------------------------------
|
|
441
|
-
// The shared data client is ambient on managed Astro
|
|
442
|
-
//
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
null;
|
|
449
|
-
}
|
|
521
|
+
// The shared data client is ambient on managed Astro, and so is the members flow there (the
|
|
522
|
+
// integration's built-in /api/auth routes). WIX_MEMBERS_CLIENT_ID is still filled for projects whose
|
|
523
|
+
// members code predates the shared seam; the shipped code reads WIX_CLIENT_ID only.
|
|
524
|
+
// --client-id, else the CONTENT app: .env.local's WIX_CLIENT_ID (what `wix env pull` writes — on a
|
|
525
|
+
// migration preview the parent site's app, the one the Astro integration itself runs as), else the
|
|
526
|
+
// config's appId. Never the config first: on a preview that is the deploy-only child app.
|
|
527
|
+
const clientId = clientIdFlag ?? ctx?.content.clientId ?? null;
|
|
450
528
|
if (stack === "react" || stack === "lib") {
|
|
451
529
|
if (clientId) {
|
|
452
530
|
const current = readFileSync(CONFIG_TS, "utf8");
|