Setting the file. One moment.
Finalize Migration Website · Wix Headless Replatform · wix/skills · Skills Docs
ContentsBack to the top of the page 28.10
Workflow
scripts/finalize-migration-website.mjs
scripts/ finalize-migration-website.mjs
JavaScript · 103 lines · 6 KB
8
const
tempPath
=
`${
filePath
}.${
process
.
pid
}.${
Date
.
now
()
}.tmp`
;
9 await writeFile (tempPath, `${ JSON . stringify ( value , null , 2 ) } \n ` , "utf8" );
10 await rename (tempPath, filePath);
11 }
12
13 export async function createMigrationWebsiteCompletion ({ handoffPath = null , projectDir = null , outputDir , releaseUrl , releaseNotApplicable = false , doneWithGaps = false , iteration = null }) {
14 if ( ! handoffPath && ! projectDir) throw new Error ( "--handoff or --project-dir is required" );
15 if ( ! outputDir) throw new Error ( "A migration frontend output directory is required" );
16 const handoff = handoffPath ? await readJson (handoffPath) : null ;
17 if (handoffPath && ! handoff?.handoffFingerprint) throw new Error ( "website/handoff.json is missing handoffFingerprint; refresh the handoff first" );
18 const gapPath = iteration
19 ? path. join (outputDir, "docs" , "site-clone" , "gap-analysis" , "iterations" , String (iteration). padStart ( 3 , "0" ), "gap-analysis.json" )
20 : path. join (outputDir, "docs" , "site-clone" , "gap-analysis" , "latest.json" );
21 const gap = await readJson (gapPath);
22 const finalReportPath = path. join (outputDir, "docs" , "site-clone" , "final-report.json" );
23 const finalReport = await readOptionalJson (finalReportPath);
24 if (finalReport?.status === "blocked" ) throw new Error ( "The frozen extraction final report contains a global blocker" );
25 const effectiveDoneWithGaps = doneWithGaps || finalReport?.status === "done_with_gaps" ;
26 if (gap?.visualReview?.status !== "reviewed" ) {
27 throw new Error ( "Post-build gap review has not passed; run/finalize the gap loop before finalizing the website" );
28 }
29 const findings = Array. isArray (gap.findings) ? gap.findings : [];
30 const unresolved = findings. filter (( finding ) => finding.status !== "resolved" && finding.status !== "accepted" );
31 const unresolvedCritical = unresolved. filter (( finding ) => finding.severity === "critical" ). length ;
32 const unresolvedHigh = unresolved. filter (( finding ) => finding.severity === "high" ). length ;
33 if ( ! effectiveDoneWithGaps && ( ! gap?.acceptance?.passed || unresolvedCritical || unresolvedHigh)) throw new Error ( "Post-build gap review still has unresolved critical/high findings" );
34 if (doneWithGaps && ! iteration && ! finalReport) throw new Error ( "--done-with-gaps requires --iteration or a frozen final report so the terminal evidence is explicit" );
35 const automationState = await readJson (path. join (outputDir, "docs" , "site-clone" , "frontend-automation-state.json" ));
36 const facelift = automationState?.checkpoints?.facelift;
37 if ( ! effectiveDoneWithGaps && facelift?.requested && facelift.status !== "accepted" ) {
38 throw new Error ( "Optional facelift was requested but has not been accepted; complete the separate facelift review first" );
39 }
40 if ( ! releaseUrl && ! releaseNotApplicable) throw new Error ( "--release-url is required unless --release-not-applicable is explicitly set" );
41
42 const receipt = {
43 schemaVersion: 1 ,
44 status: effectiveDoneWithGaps ? "done_with_gaps" : unresolved. length ? "complete_with_warnings" : "complete" ,
45 handoffFingerprint: handoff?.handoffFingerprint || null ,
46 extractionManifestHash: finalReport?.manifestHash || null ,
47 frontendProjectDir: path. relative (projectDir || path. dirname (path. dirname (handoffPath)), outputDir) || "." ,
48 phase: effectiveDoneWithGaps ? finalReport?.status === "done_with_gaps" ? "provisional_handoff" : "gap_cycle_cap" : "release" ,
49 gapAnalysis: {
50 latestIteration: gap.iteration,
51 cycleCap: doneWithGaps ? 5 : null ,
52 screenshotReview: "complete" ,
53 unresolvedCritical,
54 unresolvedHigh,
55 residualFindings: unresolved. map (( finding ) => ({ id: finding.id, severity: finding.severity, message: finding.message })),
56 },
57 facelift: effectiveDoneWithGaps ? { status: "not_run" , reason: finalReport?.status === "done_with_gaps" ? "provisional_units_remain" : "maximum_gap_cycles_reached" } : facelift?.requested ? { status: "accepted" } : { status: "not_requested" },
58 release: releaseNotApplicable ? { status: "not_applicable" , url: null } : { status: "released" , url: releaseUrl },
59 artifactRefs: [path. relative (projectDir || path. dirname (path. dirname (handoffPath)), gapPath), ... (finalReport ? [path. relative (projectDir || path. dirname (path. dirname (handoffPath)), finalReportPath)] : []), ... (gap.paths?.visualProgress ? [gap.paths.visualProgress] : []), ... (gap.visualReview?.artifactPath ? [gap.visualReview.artifactPath] : [])],
60 updatedAt: new Date (). toISOString (),
61 };
62 const receiptPath = handoffPath
63 ? path. join (path. dirname (handoffPath), "completion.json" )
64 : path. join (projectDir, "website" , "completion.json" );
65 await writeJsonAtomic (receiptPath, receipt);
66 return { receiptPath, receipt };
67 }
68
69 async function readOptionalJson ( filePath ) {
70 try { return await readJson (filePath); } catch (error) {
71 if (error?.code === "ENOENT" ) return null ;
72 throw error;
73 }
74 }
75
76 async function main () {
77 const args = parseArgs ();
78 const handoffPath = args.handoff ? path. resolve ( String (args.handoff)) : null ;
79 const projectDir = args[ "project-dir" ] ? path. resolve ( String (args[ "project-dir" ])) : null ;
80 const handoff = handoffPath ? await readJson (handoffPath) : null ;
81 const outputDir = args.out
82 ? path. resolve ( String (args.out))
83 : handoff?.destination?.frontendProjectDir
84 ? path. resolve ( String (handoff.destination.frontendProjectDir))
85 : null ;
86 const result = await createMigrationWebsiteCompletion ({
87 handoffPath,
88 projectDir,
89 outputDir,
90 releaseUrl: args[ "release-url" ] ? String (args[ "release-url" ]) : null ,
91 releaseNotApplicable: args[ "release-not-applicable" ] === true || args[ "release-not-applicable" ] === "true" ,
92 doneWithGaps: args[ "done-with-gaps" ] === true || args[ "done-with-gaps" ] === "true" ,
93 iteration: args.iteration ? Number (args.iteration) : null ,
94 });
95 console. log ( JSON . stringify ({ ok: true , ... result }, null , 2 ));
96 }
97
98 if ( import . meta .url === `file://${ process . argv [ 1 ] }` ) {
99 main (). catch (( error ) => {
100 console. error (error.stack || error.message);
101 process. exit ( 1 );
102 });
103 }