Setting the file. One moment.
Wix Destination · Wix Replatform · wix/skills · Skills Docs
ContentsBack to the top of the page scripts/ wix-destination.js
JavaScript · 158 lines · 7 KB
10 const path = require ( 'node:path' );
11 const { execFile , spawn } = require ( 'node:child_process' );
12 const { promisify } = require ( 'node:util' );
13
14 const { ensureDestination } = require ( '../lib/destination.js' );
15 const { realClock } = require ( '../lib/destination-http.js' );
16 const { egressFetch } = require ( '../lib/egress-proxy.js' );
17 const { buildWixDashboardUrl } = require ( '../lib/wix-dashboard-url.js' );
18
19 const execFileAsync = promisify (execFile);
20 // Use the operator's installed, logged-in CLI. Resolving @wix/cli@latest on every call can
21 // select a newer Node-incompatible package and hide a valid local login behind a mint failure.
22 const CLI = [ '--no-install' , 'wix' ];
23
24 function usage () {
25 return [
26 'Usage:' ,
27 ' node scripts/wix-destination.js ensure --project-dir <dir>' ,
28 '' ,
29 'Resolves the one destination for a new-site migration: asks for the site type when it is' ,
30 'missing, delegates Headless to wix-headless-scaffold.js, creates a Studio/Harmony site' ,
31 'through the API, verifies its editor and that it is unpublished, and records' ,
32 'orchestration/destination.json. Never publishes, unpublishes or deletes a site, and never' ,
33 'repeats a create whose outcome is unknown.' ,
34 ]. join ( ' \n ' );
35 }
36
37 // Credentials come from the operator's existing Wix CLI login. Values stay in memory only.
38 async function cliToken ( args ) {
39 const { stdout } = await execFileAsync ( 'npx' , [ ... CLI , 'token' , ... args], { timeout: 60_000 , maxBuffer: 1024 * 1024 });
40 const token = String (stdout || '' ). trim (). split ( / \r ? \n / ). pop ();
41 return token || null ;
42 }
43
44 function defaultCredentials () {
45 return {
46 async identity () {
47 try {
48 const { stdout } = await execFileAsync ( 'npx' , [ ... CLI , 'whoami' ], { timeout: 30_000 , maxBuffer: 1024 * 1024 });
49 const identity = String (stdout || '' ). trim ();
50 return { authenticated: Boolean (identity), identity: identity || null };
51 } catch {
52 return { authenticated: false , identity: null };
53 }
54 },
55 // The login's user id, which the creation response's metaSite.ownerId must match. `wix
56 // whoami` prints only an email, so it is read from the CLI's own login record (the same
57 // source rp-telemetry uses); a missing or unreadable record fails before any create.
58 async userId () {
59 try {
60 const raw = await fs. readFile (path. join (os. homedir (), '.wix' , 'auth' , 'account.json' ), 'utf8' );
61 const value = JSON . parse (raw)?.userInfo?.userId;
62 return typeof value === 'string' && value ? value : null ;
63 } catch {
64 return null ;
65 }
66 },
67 accountToken : () => cliToken ([]),
68 siteToken : ( metaSiteId ) => cliToken ([ '--site' , metaSiteId]),
69 };
70 }
71
72 function defaultHeadless ({ projectDir , businessName }) {
73 return new Promise (( resolve ) => {
74 const child = spawn (process.execPath, [path. join (__dirname, 'wix-headless-scaffold.js' ), '--business-name' , businessName, '--project-dir' , projectDir], {
75 stdio: [ 'ignore' , 'inherit' , 'pipe' ],
76 });
77 let stderr = '' ;
78 child.stderr. on ( 'data' , ( chunk ) => { stderr = `${ stderr }${ chunk }` . slice ( - 2000 ); process.stderr. write (chunk); });
79 child. on ( 'close' , ( code ) => resolve ({ ok: code === 0 , message: code === 0 ? null : stderr. trim (). split ( / \r ? \n / ). pop () }));
80 });
81 }
82
83 function defaultDeps () {
84 return {
85 fetch: egressFetch,
86 clock: realClock (),
87 credentials: defaultCredentials (),
88 headless: defaultHeadless,
89 randomUUID : () => crypto. randomUUID (),
90 };
91 }
92
93 function exitCodeFor ( outcome ) {
94 if ([ 'verified' , 'created' , 'existing_destination' , 'not_new_site' ]. includes (outcome)) return 0 ;
95 if ([ 'awaiting_site_type' , 'awaiting_editor_website_decision' , 'awaiting_account_confirmation' , 'awaiting_recovery' , 'failed_before_create' , 'ambiguous' ]. includes (outcome)) return 3 ;
96 if (outcome === 'blocked' || outcome === 'locked' ) return 4 ;
97 return 1 ;
98 }
99
100 // The printed summary omits nothing an operator needs and contains no credential.
101 function summarize ( result ) {
102 const receipt = result.receipt || null ;
103 return {
104 outcome: result.outcome,
105 ... (result.reason ? { reason: result.reason } : {}),
106 ... (result.errors ? { errors: result.errors } : {}),
107 ... (result.requestId ? { requestId: result.requestId } : {}),
108 ... (receipt ? {
109 status: receipt.status,
110 siteType: receipt.requestedSiteType,
111 creationMethod: receipt.creationMethod,
112 metaSiteId: receipt.metaSiteId,
113 observedEditorType: receipt.observedEditorType,
114 editorUrl: receipt.editorUrl,
115 publication: receipt.publication ? receipt.publication.state : null ,
116 error: receipt.error,
117 recoveryRequestId: receipt.recovery ? receipt.recovery.requestId : null ,
118 dashboardUrl: receipt.metaSiteId ? buildWixDashboardUrl (receipt.metaSiteId) : null ,
119 } : {}),
120 };
121 }
122
123 async function main ( argv = process.argv. slice ( 2 ), deps = defaultDeps ()) {
124 const command = argv[ 0 ];
125 if ( ! command || command === '--help' || command === 'help' ) {
126 console. log ( usage ());
127 return command ? 0 : 1 ;
128 }
129 const index = argv. indexOf ( '--project-dir' );
130 if (command !== 'ensure' || index === - 1 || ! argv[index + 1 ]) {
131 console. error ( usage ());
132 return 1 ;
133 }
134 const projectDir = path. resolve (argv[index + 1 ]);
135 const result = await ensureDestination (projectDir, deps);
136 // The destination step is where the operator's account is first confirmed, and the run's
137 // telemetry began before it. Hand over the account it has confirmed (never an unconfirmed or
138 // switched login) so BI sends under the operator's id without anyone doing anything.
139 // Best-effort: never affects the outcome.
140 try {
141 const confirmed = result.receipt && result.receipt.intendedAccount && result.receipt.intendedAccount.userId;
142 if (confirmed) {
143 await require ( '../resources/rp-telemetry/lib/telemetry-recorder.js' )
144 . adoptIdentity (projectDir, { resolveIdentity : () => confirmed });
145 }
146 } catch { /* telemetry is never allowed to fail the destination step */ }
147 process.stdout. write ( `${ JSON . stringify ( summarize ( result ), null , 2 ) } \n ` );
148 return exitCodeFor (result.outcome);
149 }
150
151 if (require.main === module ) {
152 main (). then (( code ) => { process.exitCode = code; }). catch (( error ) => {
153 console. error (error && error.message ? error.message : error);
154 process.exitCode = 1 ;
155 });
156 }
157
158 module . exports = { defaultDeps, exitCodeFor, main, summarize };