Setting the file. One moment.
Destination Route · Wix Replatform · wix/skills · Skills Docs
ContentsBack to the top of the page lib/destination-route.js
lib/ destination-route.js
JavaScript · 98 lines · 5 KB
readSiteTypeDecision
}
=
require
(
'./orchestration-decisions.js'
);
10 const { readReceipt , validateReceipt } = require ( './destination.js' );
11
12 const RESOURCE = 'resources/rp-destination/' ;
13
14 async function recoveryAnswered ( projectDir , receipt ) {
15 const requestId = receipt.recovery && receipt.recovery.requestId;
16 if ( ! requestId || ! / ^ uar_ [0-9a-f-] +$ / . test (requestId)) return false ;
17 try {
18 const request = JSON . parse ( await fs. readFile (path. join (projectDir, 'orchestration' , 'user-actions' , `${ requestId }.json` ), 'utf8' ));
19 return request.status === 'answered' ;
20 } catch {
21 return false ;
22 }
23 }
24
25 async function destinationGate ( projectDir , decisions , { targetSiteStrategy , siteIdValue , scaffoldStatus }) {
26 const choice = readSiteTypeDecision (decisions);
27 const receipt = await readReceipt (projectDir);
28 if (receipt) {
29 const errors = validateReceipt (receipt);
30 if (errors. length ) return { ok: false , nextState: 'failed' , nextResource: RESOURCE , reason: 'destination_receipt_invalid' , errors };
31 if (choice && ! choice.errors. length && choice.siteType !== receipt.requestedSiteType) {
32 return { ok: true , nextState: 'blocked' , nextResource: RESOURCE , reason: 'destination_type_conflict' , blocking: true , errors: [{ receipt: receipt.requestedSiteType, decision: choice.siteType }] };
33 }
34 // Headless keeps the wrapper's WIX_SCAFFOLD_STATUS contract unchanged.
35 if ( ! isEditorSiteType (receipt.requestedSiteType)) return null ;
36 switch (receipt.status) {
37 case 'verified' :
38 if ( ! siteIdValue) return { ok: true , nextState: 'destination_running' , nextResource: RESOURCE , reason: 'destination_binding_pending' };
39 if (siteIdValue !== receipt.metaSiteId) return { ok: true , nextState: 'blocked' , nextResource: RESOURCE , reason: 'destination_binding_conflict' , blocking: true };
40 return null ;
41 case 'in_progress' :
42 case 'created' :
43 // The coordinator resumes without a second create (an interrupted attempt becomes ambiguous).
44 return { ok: true , nextState: 'destination_running' , nextResource: RESOURCE , reason: 'destination_creation_resumable' };
45 default :
46 if ( await recoveryAnswered (projectDir, receipt)) {
47 return { ok: true , nextState: 'destination_running' , nextResource: RESOURCE , reason: 'destination_recovery_answered' };
48 }
49 return {
50 ok: true ,
51 nextState: 'blocked' ,
52 nextResource: RESOURCE ,
53 reason: `destination_${ receipt . status }` ,
54 blocking: true ,
55 errors: receipt.error ? [{ code: receipt.error.code, requestId: receipt.recovery ? receipt.recovery.requestId : null }] : [],
56 };
57 }
58 }
59 // Legacy/confirmed destinations and prior Headless wrapper attempts keep the existing checks:
60 // a missing historical choice is never reinterpreted as a new-site request.
61 if (siteIdValue || scaffoldStatus !== undefined ) return null ;
62 if ( ! isNewSiteStrategy (targetSiteStrategy)) return null ;
63 if ( ! choice) {
64 return { ok: true , nextState: 'awaiting_destination_strategy' , nextResource: RESOURCE , reason: 'missing_target_site_type' };
65 }
66 if (choice.errors. length ) {
67 return { ok: false , nextState: 'failed' , nextResource: 'resources/rp-project-intake/' , reason: 'invalid_target_site_type' , errors: choice.errors };
68 }
69 return null ;
70 }
71
72 // A later resume repeats the readback before setup. The router has no network access,
73 // so it requires the receipt's last `unpublished` readback to be recent whenever the next step
74 // would write to a receipt-backed Editor site (setup or import). Otherwise it routes to
75 // rp-destination, whose coordinator runs one fresh readback (never a create) and either
76 // re-verifies or blocks with the publication recovery request.
77 const PUBLICATION_FRESH_MS = 10 * 60_000 ;
78 const DOWNSTREAM_STATES = new Set ([ 'executing_setup' , 'executing_import' ]);
79
80 async function publicationRecheckGate ( projectDir , route , { now = Date. now () } = {}) {
81 if ( ! route || ! DOWNSTREAM_STATES . has (route.nextState)) return route;
82 let receipt;
83 try { receipt = await readReceipt (projectDir); } catch { receipt = null ; }
84 if ( ! receipt || receipt.invalid || ! isEditorSiteType (receipt.requestedSiteType) || receipt.status !== 'verified' ) return route;
85 const publication = receipt.publication || {};
86 const checkedAt = Date. parse (publication.checkedAt);
87 const age = now - checkedAt;
88 if (publication.state === 'unpublished' && Number. isFinite (checkedAt) && age >= 0 && age <= PUBLICATION_FRESH_MS ) return route;
89 return {
90 ok: true ,
91 nextState: 'destination_running' ,
92 nextResource: RESOURCE ,
93 reason: 'destination_publication_recheck' ,
94 deferredState: route.nextState,
95 };
96 }
97
98 module . exports = { PUBLICATION_FRESH_MS, destinationGate, publicationRecheckGate };