Setting the file. One moment.
Import Progress · Rp Quick Runtime · wix/skills · Skills Docs
ContentsBack to the top of the page lib/import-progress.js
lib/ import-progress.js
JavaScript · 89 lines · 4 KB
11 const QUICK_IMPORT_STARTED_MARKER = 'state/quick-import-started.json' ;
12 const MARKER_SCHEMA_VERSION = 1 ;
13
14 // The quick manifest written by `quick-mode.js plan`. It is deliberately not the full-mode
15 // `validateManifest` shape (no projectId/planVersion/tasks).
16 function validateQuickExecutionManifest ( manifest ) {
17 const errors = [];
18 if ( ! manifest || typeof manifest !== 'object' || Array. isArray (manifest)) {
19 return { ok: false , errors: [ 'quick execution manifest must be an object' ] };
20 }
21 if (manifest.schemaVersion !== undefined && manifest.schemaVersion !== 1 ) errors. push ( 'schemaVersion must be absent or 1' );
22 if (manifest.managementImportMode !== 'quick' ) errors. push ( "managementImportMode must be 'quick'" );
23 if ( typeof manifest.importCommand !== 'string' || ! manifest.importCommand. trim ()) errors. push ( 'importCommand must be a non-empty string' );
24 if ( ! Array. isArray (manifest.entities)) errors. push ( 'entities must be an array' );
25 return { ok: errors. length === 0 , errors };
26 }
27
28 // Before the marker or any destination write, the loaded plan's adapter must be
29 // exactly the adapter's current contract `{ id, version, fingerprint }`, and the extract
30 // manifest must carry that same adapter. This is what makes the marker's copied `adapter` the
31 // current adapter-contract identity rather than whatever a stale or edited plan says.
32 const ADAPTER_KEYS = [ 'id' , 'version' , 'fingerprint' ];
33 function sameAdapter ( left , right ) {
34 return Boolean (left && typeof left === 'object' && right && typeof right === 'object' )
35 && ADAPTER_KEYS . every (( key ) => typeof left[key] === 'string' && left[key] !== '' && left[key] === right[key]);
36 }
37 function assertQuickImportAdapter ({ plan , manifest , current }) {
38 if ( ! sameAdapter (current, current)) throw new Error ( 'quick adapter contract is missing id, version, or fingerprint' );
39 if ( ! sameAdapter (plan && plan.adapter, current)) throw new Error ( 'quick adapter contract changed; re-run preflight and plan' );
40 if ( ! sameAdapter (manifest && manifest.adapter, current)) throw new Error ( 'extraction does not match quick plan; re-extract' );
41 }
42
43 // Durable pre-write evidence that a live quick import began. Written once, before the first
44 // destination create/update, never on a dry run, and never rewritten on resume: an existing
45 // marker keeps describing the first live attempt. `adapter` is the plan's own
46 // `{ id, version, fingerprint }`, already checked by `assertQuickImportAdapter` to equal the
47 // loaded adapter contract, copied verbatim.
48 //
49 // Publication is atomic and no-replace: the complete JSON is written and fsynced to a
50 // uniquely named temp file, then hard-linked to the marker path. `link` fails with EEXIST
51 // when a marker already exists, so concurrent first attempts cannot replace each other and
52 // a reader never sees a partial marker. The loser is an unchanged resume.
53 async function writeQuickImportStartedMarker ( projectDir , adapter ) {
54 const markerPath = path. join (projectDir, QUICK_IMPORT_STARTED_MARKER );
55 const unchanged = { written: false , path: QUICK_IMPORT_STARTED_MARKER };
56 try {
57 await fs. access (markerPath);
58 return unchanged;
59 } catch (error) {
60 if ( ! error || error.code !== 'ENOENT' ) throw error;
61 }
62 const { id , version , fingerprint } = adapter || {};
63 const marker = { schemaVersion: MARKER_SCHEMA_VERSION , adapter: { id, version, fingerprint }, startedAt: new Date (). toISOString () };
64 await fs. mkdir (path. dirname (markerPath), { recursive: true });
65 const tempPath = `${ markerPath }.${ process . pid }.${ crypto . randomUUID () }.tmp` ;
66 const handle = await fs. open (tempPath, 'wx' );
67 try {
68 await handle. writeFile ( `${ JSON . stringify ( marker , null , 2 ) } \n ` , 'utf8' );
69 await handle. sync ();
70 } finally {
71 await handle. close ();
72 }
73 try {
74 await fs. link (tempPath, markerPath);
75 return { written: true , path: QUICK_IMPORT_STARTED_MARKER };
76 } catch (error) {
77 if (error && error.code === 'EEXIST' ) return unchanged;
78 throw error;
79 } finally {
80 await fs. rm (tempPath, { force: true });
81 }
82 }
83
84 module . exports = {
85 QUICK_IMPORT_STARTED_MARKER,
86 assertQuickImportAdapter,
87 validateQuickExecutionManifest,
88 writeQuickImportStartedMarker,
89 };