@wix/agent-skills 1.23.0 → 1.25.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/README.md +1 -0
- package/package.json +2 -1
- package/skills/wix-app/SKILL.md +4 -1
- package/skills/wix-app/references/BUSINESS_MANAGER_THEME.md +108 -0
- package/skills/wix-app/references/BUSINESS_MANAGER_TOKENS.md +81 -0
- package/skills/wix-app/references/DASHBOARD_MODAL.md +41 -0
- package/skills/wix-app/references/DASHBOARD_PAGE.md +2 -0
- package/skills/wix-app/references/DASHBOARD_PLUGIN.md +19 -4
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +64 -50
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE.md +5 -4
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_ROUTER.md +8 -7
- package/skills/wix-app/references/dashboard-page/DYNAMIC_PARAMETERS.md +21 -38
- 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/SKILL.md +0 -1
- package/skills/wix-headless/references/CAPABILITIES.md +3 -3
- package/skills/wix-headless/references/DISCOVERY.md +1 -1
- package/skills/wix-headless/references/SDK_HANDOFF.md +1 -1
- package/skills/wix-headless/references/SEED.md +1 -1
- package/skills/wix-headless/references/SETUP.md +1 -1
- package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-reservations.md +1 -1
- package/skills/wix-headless/references/inline-recipes/setup-restaurant-experiences.md +2 -2
- package/skills/wix-headless/references/managed/AUTHENTICATION.md +3 -14
- package/skills/wix-headless/scripts/bootstrap.mjs +156 -0
- package/skills/{wix-headless-fast → wix-headless-kit}/SKILL.md +83 -64
- 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 -35
- package/skills/wix-headless-kit/guides/capabilities.md +76 -0
- package/skills/wix-headless-kit/guides/feedback.md +98 -0
- package/skills/{wix-headless-fast → wix-headless-kit}/guides/migration.md +20 -9
- package/skills/{wix-headless-fast → wix-headless-kit}/install/AGENTS.md +5 -2
- package/skills/{wix-headless-fast → wix-headless-kit}/install/attach.mjs +5 -2
- package/skills/wix-headless-kit/install/bootstrap.mjs +156 -0
- package/skills/{wix-headless-fast → wix-headless-kit}/install/check.mjs +2 -2
- package/skills/wix-headless-kit/install/context.mjs +169 -0
- package/skills/{wix-headless-fast → wix-headless-kit}/install/deploy.mjs +17 -4
- package/skills/{wix-headless-fast → wix-headless-kit}/install/setup.mjs +89 -55
- package/skills/{wix-headless-fast → wix-headless-kit}/install/templates.mjs +1 -1
- package/skills/wix-manage/SKILL.md +2 -8
- package/skills/wix-manage/references/cms/cms-dashboard-navigation.md +2 -2
- package/skills/wix-manage/references/cms/cms-data-items-crud.md +263 -31
- package/skills/wix-manage/references/cms/cms-publishing-flow.md +15 -15
- package/skills/wix-manage/references/cms/cms-schema-management.md +58 -7
- package/skills/wix-manage/references/sites/query-sites.md +1 -8
- package/skills/wix-auth/SKILL.md +0 -12
- package/skills/wix-auth/references/device-flow.md +0 -140
- package/skills/wix-headless-fast/install/context.mjs +0 -126
- package/skills/wix-manage/references/cms/cms-data-operations-extended.md +0 -263
- package/skills/wix-manage/references/cms/cms-references-and-relationships.md +0 -162
- /package/skills/{wix-headless-fast → wix-headless-kit}/guides/existing-site.md +0 -0
- /package/skills/{wix-headless-fast → wix-headless-kit}/guides/reference-mode.md +0 -0
- /package/skills/{wix-headless-fast → wix-headless-kit}/install/agents-md.mjs +0 -0
- /package/skills/{wix-headless-fast → wix-headless-kit}/install/lock.mjs +0 -0
|
@@ -17,8 +17,8 @@ call is memory, and an empty or error reply to it says nothing about the site.
|
|
|
17
17
|
|
|
18
18
|
## Skills
|
|
19
19
|
|
|
20
|
-
Installed at `.agents/skills
|
|
21
|
-
`
|
|
20
|
+
Installed at `.agents/skills/` (by the Wix plugin, or by
|
|
21
|
+
`npx skills add wix/skills --skill {{SKILL}} --skill wix-docs --skill wix-manage`).
|
|
22
22
|
|
|
23
23
|
- `{{SKILL}}` — the code in this app and how to extend it. The shipped code sits in
|
|
24
24
|
`.agents/skills/{{SKILL}}/templates/`, fetched once from the skill's repository and kept with this
|
|
@@ -29,6 +29,9 @@ Installed at `.agents/skills/`. If missing, restore with:
|
|
|
29
29
|
site holds for it, with the documentation URL of every call it made. A code change ends with a release; the live URL shows it, the dev server does not count.
|
|
30
30
|
`node .agents/skills/{{SKILL}}/install/check.mjs` says whether the skill or its templates have a newer
|
|
31
31
|
version and prints the update commands; an update touches the skill folder or `templates/`, never `src/`.
|
|
32
|
+
When Wix gets in the way of a change here (a confusing error, a doc gap, a workaround you had to
|
|
33
|
+
invent), offer to relay it to Wix per `.agents/skills/{{SKILL}}/guides/feedback.md`; send only after
|
|
34
|
+
an explicit yes.
|
|
32
35
|
- `wix-docs` — how to discover the Wix APIs and their docs: search first, then read only the page
|
|
33
36
|
you need. Every endpoint, body, field and enum you did not get from the deployed code or a recipe
|
|
34
37
|
is confirmed there before you write it.
|
|
@@ -45,7 +45,7 @@ import { cpSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync }
|
|
|
45
45
|
import { basename, dirname, join, resolve } from "node:path";
|
|
46
46
|
import { fileURLToPath } from "node:url";
|
|
47
47
|
import { writeAgentsMd } from "./agents-md.mjs";
|
|
48
|
-
import { siteContext } from "./context.mjs";
|
|
48
|
+
import { frontendPresent, siteContext } from "./context.mjs";
|
|
49
49
|
import { listVerticals, templatesDir } from "./templates.mjs";
|
|
50
50
|
|
|
51
51
|
const SKILL_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -76,7 +76,10 @@ const subfolder = argv.includes("--subfolder");
|
|
|
76
76
|
const cwd = process.cwd();
|
|
77
77
|
const has = (p) => existsSync(join(cwd, p));
|
|
78
78
|
const cwdConfig = has("wix.config.json") ? JSON.parse(readFileSync(join(cwd, "wix.config.json"), "utf8")) : null;
|
|
79
|
-
|
|
79
|
+
// A frontend here: a package.json, an index.html at the root, or an index.html in the folder the
|
|
80
|
+
// config's site.outputDirectory names (a static site laid out for release) — install/context.mjs.
|
|
81
|
+
const fp = frontendPresent(cwd);
|
|
82
|
+
const hasProject = fp.packageJson || fp.rootIndex || fp.outputIndex;
|
|
80
83
|
// A migration preview (wix.config.json names a deploy-only site, .env.local names the site being
|
|
81
84
|
// migrated) is already provisioned: its app, hosting and credentials came with the folder. Nothing
|
|
82
85
|
// here applies — setup.mjs takes it (SKILL.md step 3).
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
3
|
+
import { existsSync, mkdirSync, openSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
// ── tiny event protocol (one JSON object per line) ───────────────────────────
|
|
8
|
+
const emit = (event, extra = {}) =>
|
|
9
|
+
process.stdout.write(JSON.stringify({ event, ...extra }) + '\n');
|
|
10
|
+
const fail = (event, extra = {}) => {
|
|
11
|
+
emit(event, { ok: false, ...extra });
|
|
12
|
+
process.exit(1);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// ── platform-safe binary names (npm/npx are .cmd on Windows) ─────────────────
|
|
16
|
+
const isWin = process.platform === 'win32';
|
|
17
|
+
const bin = (name) => (isWin ? `${name}.cmd` : name);
|
|
18
|
+
const WIX = [bin('npx'), '-y', '@wix/cli@latest']; // run the CLI via npx — no global install/mutation
|
|
19
|
+
|
|
20
|
+
// Force the CLI into non-interactive "agent" mode. Without an agent signal in
|
|
21
|
+
// the env, `wix login` renders an interactive Ink TUI (device code + keypress)
|
|
22
|
+
// that needs a raw TTY and crashes in an agent sandbox — and it never emits the
|
|
23
|
+
// JSON login events this script forwards. The CLI uses @vercel/detect-agent,
|
|
24
|
+
// whose first check is the AI_AGENT env var, so setting it guarantees agent mode
|
|
25
|
+
// (and the awaiting_user/success/logged_in events) for every child we spawn.
|
|
26
|
+
// Respect an existing value so a known runner (claude, cursor, …) keeps its name.
|
|
27
|
+
const AGENT_ENV = { ...process.env, AI_AGENT: process.env.AI_AGENT || 'wix-headless-kit-skill' };
|
|
28
|
+
|
|
29
|
+
// Where the detached login parks its output between runs. It has to outlive this
|
|
30
|
+
// process, so it can't be a pipe and can't live in the project.
|
|
31
|
+
const STATE_DIR = join(tmpdir(), 'wix-headless-kit-login');
|
|
32
|
+
const LOG = join(STATE_DIR, 'login.log');
|
|
33
|
+
const PIDFILE = join(STATE_DIR, 'login.pid');
|
|
34
|
+
|
|
35
|
+
// run a command, capture stdout+stderr (combined), return {status, out}
|
|
36
|
+
function capture(cmd, args, opts = {}) {
|
|
37
|
+
const r = spawnSync(cmd, args, { encoding: 'utf8', shell: isWin, env: AGENT_ENV, ...opts });
|
|
38
|
+
return { status: r.status ?? 1, out: `${r.stdout || ''}${r.stderr || ''}`, error: r.error };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── 1. CLI reachable (via npx — no install) ──────────────────────────────────
|
|
42
|
+
function checkCli() {
|
|
43
|
+
const r = capture(WIX[0], [...WIX.slice(1), '--version']);
|
|
44
|
+
if (r.status !== 0) fail('cli_unreachable', { detail: r.out.trim().slice(0, 400) });
|
|
45
|
+
// npx interleaves "npm notice …" lines with the version, so don't just take the
|
|
46
|
+
// last line — pick the first semver-looking token, falling back to the last line.
|
|
47
|
+
const version = (r.out.match(/\d+\.\d+\.\d+[^\s]*/) || [])[0] || r.out.trim().split('\n').pop();
|
|
48
|
+
emit('cli_ok', { version });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── 2. Reuse an existing session, or start device login ──────────────────────
|
|
52
|
+
const hasExistingSession = () => capture(WIX[0], [...WIX.slice(1), 'whoami']).status === 0;
|
|
53
|
+
|
|
54
|
+
function loginEvents() {
|
|
55
|
+
if (!existsSync(LOG)) return [];
|
|
56
|
+
return readFileSync(LOG, 'utf8')
|
|
57
|
+
.split('\n')
|
|
58
|
+
.map((l) => l.trim())
|
|
59
|
+
.filter(Boolean)
|
|
60
|
+
.map((l) => {
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(l);
|
|
63
|
+
} catch {
|
|
64
|
+
return null; // non-JSON CLI chatter
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
.filter((e) => e && e.event);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const alive = (pid) => {
|
|
71
|
+
try {
|
|
72
|
+
process.kill(pid, 0);
|
|
73
|
+
return true;
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// A login this machine started earlier may still be waiting on the browser. Reuse
|
|
80
|
+
// its code instead of minting a second one — a new login invalidates nothing, but
|
|
81
|
+
// it hands the user a different code than the one already on their screen.
|
|
82
|
+
function pendingLogin() {
|
|
83
|
+
if (!existsSync(PIDFILE)) return null;
|
|
84
|
+
let state;
|
|
85
|
+
try {
|
|
86
|
+
state = JSON.parse(readFileSync(PIDFILE, 'utf8'));
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
if (!state.pid || !alive(state.pid)) return null;
|
|
91
|
+
const ev = loginEvents().find((e) => e.event === 'awaiting_user');
|
|
92
|
+
if (!ev) return null;
|
|
93
|
+
// Don't hand back a code that's about to expire mid-typing.
|
|
94
|
+
const ageSeconds = Math.round((Date.now() - state.startedAt) / 1000);
|
|
95
|
+
if (ageSeconds > (ev.expiresInSeconds ?? 600) - 60) return null;
|
|
96
|
+
return ev;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Detach so the login keeps polling after this process exits. stdio goes to a
|
|
100
|
+
// file, not a pipe: a pipe dies with the parent, and the CLI would get EPIPE.
|
|
101
|
+
function startLogin() {
|
|
102
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
103
|
+
writeFileSync(LOG, '');
|
|
104
|
+
const out = openSync(LOG, 'a');
|
|
105
|
+
const child = spawn(WIX[0], [...WIX.slice(1), 'login'], {
|
|
106
|
+
detached: true,
|
|
107
|
+
stdio: ['ignore', out, out],
|
|
108
|
+
env: AGENT_ENV,
|
|
109
|
+
shell: isWin,
|
|
110
|
+
});
|
|
111
|
+
child.on('error', (e) => fail('login_failed', { detail: String(e) }));
|
|
112
|
+
writeFileSync(PIDFILE, JSON.stringify({ pid: child.pid, startedAt: Date.now() }));
|
|
113
|
+
child.unref();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function waitForCode(timeoutMs = 60000) {
|
|
117
|
+
const deadline = Date.now() + timeoutMs;
|
|
118
|
+
while (Date.now() < deadline) {
|
|
119
|
+
const events = loginEvents();
|
|
120
|
+
const ev = events.find((e) => e.event === 'awaiting_user');
|
|
121
|
+
if (ev) return ev;
|
|
122
|
+
const bad = events.find((e) => e.event === 'login_failed');
|
|
123
|
+
if (bad) fail('login_failed', bad);
|
|
124
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
125
|
+
}
|
|
126
|
+
const detail = existsSync(LOG) ? readFileSync(LOG, 'utf8').trim().slice(-1500) : '';
|
|
127
|
+
fail('login_failed', {
|
|
128
|
+
detail: detail || `wix login produced no device code within ${timeoutMs / 1000}s.`,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// The next move is the user's, so hand the code back and exit rather than holding
|
|
133
|
+
// the caller open for a browser round-trip. `message` is the sentence to relay.
|
|
134
|
+
function surrenderTo(ev) {
|
|
135
|
+
emit('awaiting_user', {
|
|
136
|
+
...ev,
|
|
137
|
+
message:
|
|
138
|
+
`To connect your Wix account, open ${ev.verificationUri} and enter the code ` +
|
|
139
|
+
`${ev.userCode}. Tell me once you're done and I'll continue.`,
|
|
140
|
+
});
|
|
141
|
+
process.exit(0);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ── main ────────────────────────────────────────────────────────────────────
|
|
145
|
+
checkCli();
|
|
146
|
+
|
|
147
|
+
if (hasExistingSession()) {
|
|
148
|
+
emit('logged_in');
|
|
149
|
+
process.exit(0);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const pending = pendingLogin();
|
|
153
|
+
if (pending) surrenderTo(pending);
|
|
154
|
+
|
|
155
|
+
startLogin();
|
|
156
|
+
surrenderTo(await waitForCode());
|
|
@@ -24,7 +24,7 @@ const { repo } = installSource();
|
|
|
24
24
|
const out = { skill: {}, templates: {}, update: {} };
|
|
25
25
|
|
|
26
26
|
// ---- the repository: one clone of its history without file contents (small), read for both layers
|
|
27
|
-
const tmp = mkdtempSync(join(tmpdir(), "wix-headless-
|
|
27
|
+
const tmp = mkdtempSync(join(tmpdir(), "wix-headless-kit-check-"));
|
|
28
28
|
const clone = git(["clone", "--quiet", "--filter=blob:none", "--no-checkout", "--single-branch", repo, tmp]);
|
|
29
29
|
if (clone.status !== 0) {
|
|
30
30
|
rmSync(tmp, { recursive: true, force: true });
|
|
@@ -80,7 +80,7 @@ rmSync(tmp, { recursive: true, force: true });
|
|
|
80
80
|
// ---- what would act on each ------------------------------------------------------------------
|
|
81
81
|
const rel = relative(process.cwd(), SKILL_ROOT) || ".";
|
|
82
82
|
out.update = {
|
|
83
|
-
skill: `
|
|
83
|
+
skill: `update ${skillName} the way it was installed (the plugin's update, or the skills CLI's)`,
|
|
84
84
|
templates: `node ${rel}/install/templates.mjs --refresh (then commit the changed files under ${rel}/templates)`,
|
|
85
85
|
note: "neither touches src/; a shipped fix reaches the project by comparing its copy with the template",
|
|
86
86
|
};
|
|
@@ -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
|
+
}
|
|
@@ -111,6 +111,18 @@ const VERTICAL_DEPS = {
|
|
|
111
111
|
"@wix/essentials": "^1.0.6",
|
|
112
112
|
},
|
|
113
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
|
+
},
|
|
114
126
|
blog: {
|
|
115
127
|
core: {
|
|
116
128
|
"@wix/blog": "^1.0.645",
|
|
@@ -247,7 +259,7 @@ if (siteSearch) {
|
|
|
247
259
|
}
|
|
248
260
|
// The generated file is data only; the shipped core resolves defaults (types, routes, labels) from it.
|
|
249
261
|
const siteSearchConfigSource = () =>
|
|
250
|
-
`// Generated by wix-headless-
|
|
262
|
+
`// Generated by wix-headless-kit install/deploy.mjs from plan.capabilities.siteSearch.\n` +
|
|
251
263
|
`// Edit the plan and redeploy. Defaults for anything omitted are in search-core.ts (resolveConfig).\n` +
|
|
252
264
|
`export const siteSearchPlan = ${JSON.stringify(siteSearch, null, 2)};\n`;
|
|
253
265
|
// ---- static stack: the REST layer, composed flat and stripped ------------------------------------
|
|
@@ -401,7 +413,7 @@ if (uploadPolicies.length) {
|
|
|
401
413
|
// The generated file is data only. The shipped endpoint owns validation and elevation.
|
|
402
414
|
writeFileSync(
|
|
403
415
|
configFile,
|
|
404
|
-
`// Generated by wix-headless-
|
|
416
|
+
`// Generated by wix-headless-kit install/deploy.mjs from plan.capabilities.mediaUpload.\n` +
|
|
405
417
|
`// Edit the plan and redeploy; do not add browser-controlled policy selection here.\n` +
|
|
406
418
|
`export const mediaUploadPolicies = ${JSON.stringify(uploadPolicies, null, 2)} as const;\n`,
|
|
407
419
|
);
|
|
@@ -506,8 +518,9 @@ if (!requested.length) {
|
|
|
506
518
|
}
|
|
507
519
|
|
|
508
520
|
// ---- client ids ---------------------------------------------------------------------------------
|
|
509
|
-
// The shared data client is ambient on managed Astro
|
|
510
|
-
//
|
|
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.
|
|
511
524
|
// --client-id, else the CONTENT app: .env.local's WIX_CLIENT_ID (what `wix env pull` writes — on a
|
|
512
525
|
// migration preview the parent site's app, the one the Astro integration itself runs as), else the
|
|
513
526
|
// config's appId. Never the config first: on a preview that is the deploy-only child app.
|