Setting the file. One moment. Orchestration Preflight · Wix Replatform · wix/skills · Skills Docs47.10
Website Handoff Generate
lib/orchestration-preflight.js
lib/orchestration-preflight.js
JavaScript·175 lines·8 KB
);
7const { getDecisionValue } = require('./orchestration-decisions.js');
8
9const SCHEMA_VERSION = 1;
10const execFileAsync = promisify(execFile);
11
12const SOURCE_CONFIG_BY_PLATFORM = {
13 wordpress: ['WP_BASE_URL'],
14 woocommerce: ['WP_BASE_URL'],
15 shopify: ['SHOPIFY_STORE_URL'],
16 // CSV is a file-provided flow: source.csv.env holds only optional
17 // delimiter/encoding/vendor/rewrite hints, so no key is required. The
18 // files_only branch above enforces fileInputPaths instead.
19 csv: [],
20};
21
22const PRIVATE_SOURCE_CONFIG_BY_PLATFORM = {
23 wordpress: ['WP_BASE_URL', 'WP_USERNAME', 'WP_APPLICATION_PASSWORD'],
24 woocommerce: ['WP_BASE_URL', 'WP_USERNAME', 'WP_APPLICATION_PASSWORD'],
25};
26
27function makeCheck(id, label, status, message, details = {}) {
28 return { id, label, status, message, ...details };
29}
30
31async function defaultProbeWixCli() {
32 try {
33 const { stdout } = await execFileAsync('npx', ['@wix/cli@latest', 'whoami'], {
34 timeout: 30000,
35 maxBuffer: 1024 * 1024,
36 });
37 const output = String(stdout || '').trim();
38 if (!output) {
39 return { available: true, authState: 'unknown', reason: 'wix whoami returned empty output' };
40 }
41 return {
42 available: true,
43 authState: 'authenticated',
44 email: output,
45 };
46 } catch (error) {
47 return {
48 available: false,
49 authState: 'unknown',
50 reason: error && error.message ? error.message : String(error),
51 };
52 }
53}
54
55function configFileForPlatform(projectDir, platform) {
56 return path.join(projectDir, 'config', `source.${platform}.env`);
57}
58
59async function runPreflight(projectDir, artifacts, options = {}) {
60 const probeWixCli = options.probeWixCli || defaultProbeWixCli;
61 const progress = options.progress || null;
62 const checks = [];
63 const decisions = artifacts.decisions || {};
64 const sourceMode = getDecisionValue(decisions, 'sourceMode');
65 const sourcePlatform = getDecisionValue(decisions, 'sourcePlatform');
66 const deliveryMode = getDecisionValue(decisions, 'deliveryMode');
67 const targetSiteStrategy = getDecisionValue(decisions, 'targetSiteStrategy');
68 const fileInputPaths = getDecisionValue(decisions, 'fileInputPaths');
69 const credentialRef = getDecisionValue(decisions, 'sourceCredentialRef');
70
71 checks.push(
72 sourceMode
73 ? makeCheck('source_mode', 'Source acquisition mode', 'pass', `sourceMode=${sourceMode}`)
74 : makeCheck('source_mode', 'Source acquisition mode', 'blocked', 'sourceMode decision is missing'),
75 );
76 progress?.progress('Checked source acquisition mode', { phase: 'preflight', step: 'source-mode' });
77
78 if ((sourceMode === 'private_data' || sourceMode === 'authenticated_api') && !(sourcePlatform in PRIVATE_SOURCE_CONFIG_BY_PLATFORM)) {
79 checks.push(
80 credentialRef
81 ? makeCheck('source_credentials', 'Source credential reference', 'pass', 'credential reference present')
82 : makeCheck('source_credentials', 'Source credential reference', 'blocked', 'private/authenticated mode requires sourceCredentialRef'),
83 );
84 progress?.progress('Checked source credential reference', { phase: 'preflight', step: 'source-credentials' });
85 }
86
87 if (sourceMode === 'files_only' || getDecisionValue(decisions, 'includeAdditionalFiles') === true) {
88 const files = Array.isArray(fileInputPaths) ? fileInputPaths : [];
89 checks.push(
90 files.length > 0
91 ? makeCheck('file_inputs', 'Input files', 'pass', `${files.length} file input path(s) recorded`)
92 : makeCheck('file_inputs', 'Input files', 'blocked', 'file-based flow requires fileInputPaths'),
93 );
94 progress?.progress('Checked file inputs', { phase: 'preflight', step: 'file-inputs' });
95 }
96
97 checks.push(
98 deliveryMode
99 ? makeCheck('delivery_mode', 'Delivery mode', 'pass', `deliveryMode=${deliveryMode}`)
100 : makeCheck('delivery_mode', 'Delivery mode', 'blocked', 'deliveryMode decision is missing'),
101 );
102 checks.push(
103 targetSiteStrategy
104 ? makeCheck('target_site_strategy', 'Target site strategy', 'pass', `targetSiteStrategy=${targetSiteStrategy}`)
105 : makeCheck('target_site_strategy', 'Target site strategy', 'blocked', 'targetSiteStrategy decision is missing'),
106 );
107 progress?.progress('Checked delivery and target site decisions', { phase: 'preflight', step: 'target-decisions' });
108
109 const wixEnv = await statEnvKeys(path.join(projectDir, 'config', 'wix.env'), ['WIX_SITE_STRATEGY', 'WIX_SITE_ID', 'WIX_AUTH_TOKEN']);
110 checks.push(makeCheck('wix_env', 'Wix config file', wixEnv.exists ? 'pass' : 'blocked', wixEnv.exists ? 'wix.env exists' : 'wix.env is missing', { keyStatus: wixEnv.keys }));
111 progress?.progress('Checked Wix config file', { phase: 'preflight', step: 'wix-env' });
112
113 if (targetSiteStrategy === 'existing_site') {
114 const siteIdStatus = wixEnv.keys.WIX_SITE_ID;
115 checks.push(
116 siteIdStatus === 'present'
117 ? makeCheck('wix_site_id', 'Existing site id', 'pass', 'WIX_SITE_ID present')
118 : makeCheck('wix_site_id', 'Existing site id', 'blocked', `WIX_SITE_ID is ${siteIdStatus}`),
119 );
120 progress?.progress('Checked existing Wix site id', { phase: 'preflight', step: 'wix-site-id' });
121 }
122
123 if (sourcePlatform) {
124 const requiredSourceKeys =
125 (sourceMode === 'private_data' || sourceMode === 'authenticated_api')
126 ? (PRIVATE_SOURCE_CONFIG_BY_PLATFORM[sourcePlatform] || SOURCE_CONFIG_BY_PLATFORM[sourcePlatform] || [])
127 : (SOURCE_CONFIG_BY_PLATFORM[sourcePlatform] || []);
128 if (requiredSourceKeys.length > 0) {
129 const sourceEnv = await statEnvKeys(configFileForPlatform(projectDir, sourcePlatform), requiredSourceKeys);
130 const allGood = Object.values(sourceEnv.keys).every((status) => status === 'present');
131 checks.push(
132 makeCheck(
133 'source_env',
134 'Source config file',
135 allGood ? 'pass' : 'blocked',
136 sourceEnv.exists ? `checked source.${sourcePlatform}.env` : `source.${sourcePlatform}.env is missing`,
137 { keyStatus: sourceEnv.keys },
138 ),
139 );
140 progress?.progress(`Checked source ${sourcePlatform} config file`, { phase: 'preflight', step: 'source-env', entity: sourcePlatform });
141 }
142 }
143
144 progress?.progress('Wix CLI probe started', { phase: 'preflight', step: 'wix-cli' });
145 const wixCli = progress
146 ? await progress.withHeartbeat({ phase: 'preflight', step: 'wix-cli', message: 'Still probing Wix CLI' }, probeWixCli)
147 : await probeWixCli();
148 checks.push(
149 wixCli.available
150 ? makeCheck('wix_cli_available', 'Wix CLI availability', 'pass', 'Wix CLI probe succeeded', { cli: wixCli })
151 : makeCheck('wix_cli_available', 'Wix CLI availability', 'blocked', wixCli.reason || 'Wix CLI probe failed', { cli: wixCli }),
152 );
153 checks.push(
154 wixCli.authState === 'authenticated'
155 ? makeCheck('wix_cli_auth', 'Wix CLI auth state', 'pass', 'Wix CLI is authenticated', { cli: wixCli })
156 : makeCheck('wix_cli_auth', 'Wix CLI auth state', 'blocked', wixCli.reason || 'Wix CLI auth could not be confirmed', { cli: wixCli }),
157 );
158 progress?.progress('Checked Wix CLI availability and auth', { phase: 'preflight', step: 'wix-cli' });
159
160 const blocked = checks.filter((check) => check.status === 'blocked');
161 const failed = checks.filter((check) => check.status === 'failed');
162 const status = failed.length > 0 ? 'failed' : blocked.length > 0 ? 'blocked' : 'pass';
163
164 return {
165 schemaVersion: SCHEMA_VERSION,
166 status,
167 updatedAt: new Date().toISOString(),
168 checks,
169 };
170}
171
172module.exports = {
173 SCHEMA_VERSION,
174 runPreflight,
175};