Setting the file. One moment.
Destination Choice · Wix Replatform · wix/skills · Skills Docs
ContentsBack to the top of the page lib/ destination-choice.js
JavaScript · 164 lines · 10 KB
'./orchestration-decisions.js'
);
12 const { loadArtifacts , writeJsonAtomic , updateDecisionObject , nowIso } = require ( './orchestration-state.js' );
13
14 const KNOWN_BUSINESS_TYPES = Object. freeze ([ 'store' , 'bookings' , 'other' ]);
15
16 const CHOICE_LABELS = Object. freeze ({
17 headless: { label: 'Headless' , description: 'A website developed in code (Wix Managed Headless).' },
18 studio: { label: 'Wix Studio' , description: 'A website designed in the Wix Studio editor.' },
19 harmony_store: { label: 'Wix Harmony — store' , description: 'Harmony editor; the business sells products.' },
20 harmony_bookings: { label: 'Wix Harmony — bookings/services' , description: 'Harmony editor; the business sells appointments, classes or services.' },
21 harmony_other: { label: 'Wix Harmony — another kind of business' , description: 'Harmony editor; neither a store nor a bookings business.' },
22 harmony: { label: 'Wix Harmony — not sure, start blank' , description: 'Harmony editor, starting from the blank template.' },
23 });
24
25 const CONTEXT_HARMONY_LABEL = { label: 'Wix Harmony' , description: 'A website designed in the Wix Harmony editor.' };
26
27 // Validates and returns the decision pair a set of inputs describes. Throws on an unknown
28 // value, a business type for a non-Harmony site, or a CLI-style business type with no type.
29 function resolveSiteTypePair ({ siteType , businessType = undefined , businessTypeSource = undefined , evidenceRef = null }) {
30 if (siteType === 'editor' ) throw new Error ( 'site type "editor" is incomplete: choose studio or harmony' );
31 if ( ! SITE_TYPES . includes (siteType)) throw new Error ( `site type must be one of ${ SITE_TYPES . join ( ', ' ) }` );
32 if (siteType !== 'harmony' ) {
33 if (businessType !== undefined && businessType !== null ) throw new Error ( 'a site business type is valid only with site type harmony' );
34 return { siteType, businessType: null , businessTypeSource: null , evidenceRef: null };
35 }
36 const value = businessType === undefined || businessType === null || businessType === '' ? 'unknown' : businessType;
37 if ( ! SITE_BUSINESS_TYPES . includes (value)) throw new Error ( `site business type must be one of ${ SITE_BUSINESS_TYPES . join ( ', ' ) }` );
38 const source = businessTypeSource || 'user' ;
39 if (source === 'context' && ( ! KNOWN_BUSINESS_TYPES . includes (value) || ! evidenceRef)) {
40 throw new Error ( 'a context business type needs a known value and an evidence reference' );
41 }
42 return { siteType, businessType: value, businessTypeSource: source, evidenceRef: source === 'context' ? evidenceRef : null };
43 }
44
45 // Writes the pair into `decisions` (in memory). A different recorded pair is a conflict:
46 // changing the answer never silently retargets the destination. `source` records who chose the
47 // type (default user); the business type keeps its own source.
48 function applySiteTypePair ( decisions , pair , { rationale = null , timestamp = nowIso (), source = 'user' } = {}) {
49 const existingType = getDecisionEntry (decisions, 'targetSiteType' );
50 if (existingType) {
51 const existingBusiness = getDecisionEntry (decisions, 'targetSiteBusinessType' );
52 const same = existingType.value === pair.siteType
53 && (existingBusiness ? existingBusiness.value : null ) === pair.businessType;
54 if (same) return { changed: false };
55 throw new Error ( `site type conflicts with the recorded decision (${ existingType . value }${ existingBusiness ? `/${ existingBusiness . value }` : ''}); a type change needs an explicit recovery decision` );
56 }
57 updateDecisionObject (decisions, 'targetSiteType' , pair.siteType, { source, rationale, timestamp });
58 if (pair.siteType === 'harmony' ) {
59 updateDecisionObject (decisions, 'targetSiteBusinessType' , pair.businessType, {
60 source: pair.businessTypeSource,
61 rationale,
62 timestamp,
63 evidenceRef: pair.evidenceRef,
64 });
65 }
66 return { changed: true };
67 }
68
69 // One atomic decisions.json write for the pair; never two writes the router could observe apart.
70 async function recordSiteTypeDecision ( projectDir , input , options = {}) {
71 const pair = resolveSiteTypePair (input);
72 const artifacts = await loadArtifacts (projectDir);
73 if ( ! artifacts.decisions) throw new Error ( 'orchestration decisions are not initialized' );
74 const result = applySiteTypePair (artifacts.decisions, pair, options);
75 if (result.changed) await writeJsonAtomic (path. join (projectDir, 'orchestration' , 'decisions.json' ), artifacts.decisions);
76 return { ... pair, changed: result.changed };
77 }
78
79 // The durable request for a missing site type. With a context business type, Harmony
80 // collapses to one `harmony` choice that records the context value.
81 function siteTypeRequestInput ({ contextBusinessType = null , evidenceRef = null , siteName = null } = {}) {
82 const fromContext = contextBusinessType && KNOWN_BUSINESS_TYPES . includes (contextBusinessType) && evidenceRef;
83 const ids = fromContext ? [ 'headless' , 'studio' , 'harmony' ] : Object. keys ( SITE_TYPE_CHOICES );
84 const choices = ids. map (( id ) => ({ id, ... (fromContext && id === 'harmony' ? CONTEXT_HARMONY_LABEL : CHOICE_LABELS [id]) }));
85 const context = [
86 'A Studio or Harmony site is created in the Wix account the Wix CLI is logged into; that account is shown for confirmation before anything is created.' ,
87 siteName ? `Its name will be "${ siteName }".` : null ,
88 fromContext ? `The business type is already known from the run (${ contextBusinessType }).` : null ,
89 ]. filter (Boolean). join ( ' ' );
90 return {
91 kind: 'question' ,
92 stage: 'destination' ,
93 title: 'Choose the new site type' ,
94 prompt: `What kind of new Wix site should this migration create? This choice decides where the imported data will live and cannot be changed later as a setting. Headless is for a website developed in code; Studio and Harmony are websites designed in a Wix editor.${ context ? ` ${ context }` : ''}` ,
95 response: { mode: 'choice' , required: true },
96 choices,
97 effect: {
98 kind: 'decision' ,
99 key: 'targetSiteType' ,
100 choices: Object. fromEntries (ids. map (( id ) => [id, id])),
101 ... (fromContext ? { contextBusinessType, evidenceRef } : {}),
102 },
103 };
104 }
105
106 // Maps an answered site-type choice to the pair it records (used by the effect handler).
107 function pairForChoice ( effect , choiceId ) {
108 const mapped = SITE_TYPE_CHOICES [choiceId];
109 if ( ! mapped) throw new Error ( `unknown site-type choice: ${ choiceId }` );
110 if (mapped.siteType === 'harmony' && choiceId === 'harmony' && effect.contextBusinessType) {
111 return resolveSiteTypePair ({ siteType: 'harmony' , businessType: effect.contextBusinessType, businessTypeSource: 'context' , evidenceRef: effect.evidenceRef });
112 }
113 return resolveSiteTypePair ({ siteType: mapped.siteType, businessType: mapped.businessType, businessTypeSource: 'user' });
114 }
115
116 function isValidSiteTypeEffect ( effect , choices ) {
117 if ( ! effect || effect.kind !== 'decision' || effect.key !== 'targetSiteType' ) return false ;
118 if ( ! effect.choices || typeof effect.choices !== 'object' || Array. isArray (effect.choices)) return false ;
119 const entries = Object. entries (effect.choices);
120 if ( ! entries. length ) return false ;
121 if ( ! entries. every (([ choice , value ]) => choice === value && SITE_TYPE_CHOICES [choice] && choices. some (( item ) => item.id === choice))) return false ;
122 if (effect.contextBusinessType !== undefined ) {
123 if ( ! KNOWN_BUSINESS_TYPES . includes (effect.contextBusinessType) || typeof effect.evidenceRef !== 'string' || ! effect.evidenceRef) return false ;
124 if (entries. some (([ choice ]) => choice. startsWith ( 'harmony_' ))) return false ;
125 }
126 return true ;
127 }
128
129 // Scope guard for management_and_website with an Editor choice.
130 const EDITOR_WEBSITE_DECISIONS = Object. freeze ([ 'management_only' , 'separate_editor_handoff' ]);
131
132 function editorWebsiteRequestInput ({ siteType }) {
133 return {
134 kind: 'question' ,
135 stage: 'destination' ,
136 title: 'Editor website is not built automatically' ,
137 prompt: `You asked for both the backend migration and the website, on a ${ siteType === 'studio' ? 'Wix Studio' : 'Wix Harmony'} site. RePlatform has no supported automatic website migration for an Editor site: it creates the site and imports the business data, but the website itself is designed separately in the Wix editor. Choose whether to migrate the backend only, or to continue with the backend now and a separate Editor website handoff (the website stays outstanding and the migration is not reported complete until it is released).` ,
138 response: { mode: 'choice' , required: true },
139 choices: [
140 { id: 'management_only' , label: 'Backend only' , description: 'Change the scope to management; no website work is owed by this run.' },
141 { id: 'separate_editor_handoff' , label: 'Backend now, Editor website separately' , description: 'Keep the website obligation as an outstanding handoff for the site owner/designer.' },
142 ],
143 effect: { kind: 'decision' , key: 'editorWebsiteDecision' , choices: { management_only: 'management_only' , separate_editor_handoff: 'separate_editor_handoff' } },
144 };
145 }
146
147 function isValidEditorWebsiteEffect ( effect , choices ) {
148 if ( ! effect || effect.kind !== 'decision' || effect.key !== 'editorWebsiteDecision' ) return false ;
149 const entries = Object. entries (effect.choices || {});
150 return entries. length > 0 && entries. every (([ choice , value ]) => choice === value && EDITOR_WEBSITE_DECISIONS . includes (choice) && choices. some (( item ) => item.id === choice));
151 }
152
153 module . exports = {
154 EDITOR_WEBSITE_DECISIONS,
155 KNOWN_BUSINESS_TYPES,
156 applySiteTypePair,
157 editorWebsiteRequestInput,
158 isValidEditorWebsiteEffect,
159 isValidSiteTypeEffect,
160 pairForChoice,
161 recordSiteTypeDecision,
162 resolveSiteTypePair,
163 siteTypeRequestInput,
164 };