import fs from 'node:fs'; import path from 'node:path'; import { createHash } from 'node:crypto'; import { fileURLToPath } from 'node:url'; import { copyArtifactAtomically, writeManifestIfChanged } from '../web/tools/artifact-assets.mjs'; const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..'); const publicRoot=path.join(root,'web/public'), dist=path.join(root,'web/dist'); const modules=[...new Set(process.argv.slice(2))]; if(!modules.length) throw Error('Specify modules to synchronize into an existing production preview'); const source=JSON.parse(fs.readFileSync(path.join(publicRoot,'modules/manifest.json'))); const targetPath=path.join(dist,'modules/manifest.json'); const target=JSON.parse(fs.readFileSync(targetPath)); if(source.schema!==target.schema) throw Error('Preview manifest schema differs; run the full frontend build'); const hash=file=>createHash('sha256').update(fs.readFileSync(file)).digest('hex'); const assets=new Map(); const fields={runtimeUrl:'runtime',wasmUrl:'wasm',dataUrl:'data',pthreadWorkerUrl:'pthreadWorker'}; for(const name of modules) { const entry=source.modules[name]; if(!entry || !target.modules[name]) throw Error(`Module is not present in both manifests: ${name}`); for(const artifact of [entry,...Object.values(entry.workers??{})]) for(const [field,key] of Object.entries(fields)) { if(!artifact[field]) continue; const url=artifact[field]; if(!/^\/kernel\/[A-Za-z0-9_.-]+$/.test(url)) throw Error(`Unsupported local artifact path: ${url}`); const expected=artifact.hashes?.[key]; if(!expected || hash(path.join(publicRoot,url.slice(1)))!==expected) throw Error(`Published artifact hash mismatch: ${url}`); assets.set(url.slice(1),expected); } } let copied=0; for(const [name,expected] of assets) { const destination=path.join(dist,name); if(fs.existsSync(destination) && hash(destination)===expected) continue; fs.mkdirSync(path.dirname(destination),{recursive:true}); copyArtifactAtomically(path.join(publicRoot,name),destination); if(hash(destination)!==expected) throw Error(`Preview copy verification failed: ${name}`); copied++; } // Publish revision metadata only after every selected runtime file is verified. for(const name of modules) target.modules[name]=source.modules[name]; target.generatedAt=new Date().toISOString(); writeManifestIfChanged(targetPath,target); console.log(JSON.stringify({modules,verifiedArtifacts:assets.size,copied,manifest:'web/dist/modules/manifest.json'}));