prepare-board-local-release.mjs 2.6 KB

1234567891011121314151617181920212223242526272829303132
  1. import fs from 'node:fs/promises';
  2. import path from 'node:path';
  3. import crypto from 'node:crypto';
  4. import {execFileSync} from 'node:child_process';
  5. const root=path.resolve('.'),out='work/board-all-local-release-20261111';
  6. const sha=b=>crypto.createHash('sha256').update(b).digest('hex');
  7. const normal=b=>b.includes(0)?b:Buffer.from(b.toString('utf8').replaceAll('\r\n','\n'));
  8. const graph=JSON.parse(await fs.readFile(out+'/modules-local.json'));
  9. const paths=[...new Set([...graph.modules.map(u=>new URL(u).pathname.slice(1)),'src/assets/inter-latin.woff2','src/data/development-references.json'])].sort();
  10. const base='https://kangshifu.fmode.cn/';
  11. async function read(p){
  12. const r=await fetch(new URL(p,base),{headers:{'cache-control':'no-cache'},signal:AbortSignal.timeout(45000),redirect:'error'});
  13. const bytes=Buffer.from(await r.arrayBuffer()),type=r.headers.get('content-type')||'';
  14. if(!r.ok&&r.status!==404)throw Error('BASELINE_READ_FAILED:'+p+':'+r.status);
  15. return{status:r.status,bytes,type};
  16. }
  17. const home=await read('index.html');if(home.status!==200||!home.type.includes('html'))throw Error('HOME_READ_FAILED');
  18. await fs.mkdir(out+'/production-before',{recursive:true});await fs.writeFile(out+'/production-index.html',home.bytes);
  19. const files=[];let next=0;
  20. await Promise.all(Array.from({length:6},async()=>{while(next<paths.length){
  21. const p=paths[next++],bytes=normal(await fs.readFile(p)),prior=await read(p);
  22. const absent=prior.status===404||prior.type.includes('html')&&prior.bytes.equals(home.bytes);
  23. if(!absent&&p.endsWith('.js')&&!prior.type.includes('javascript'))throw Error('BASELINE_NOT_JS:'+p);
  24. if(!absent){const target=out+'/production-before/'+p;await fs.mkdir(path.dirname(target),{recursive:true});await fs.writeFile(target,prior.bytes);}
  25. files.push({path:p,sha256:sha(bytes),priorNormalizedSha256:absent?[]:[sha(normal(prior.bytes))],allowMissing:absent,changed:absent||sha(normal(prior.bytes))!==sha(bytes)});
  26. }}));
  27. // Dependencies first, routing/navigation last, to shorten the interval with mixed imports.
  28. const rank=p=>p==='src/layout/kf-nav.js'?4:p==='src/layout/kf-app.js'?3:p.startsWith('src/pages/')?2:1;
  29. files.sort((a,b)=>rank(a.path)-rank(b.path)||a.path.localeCompare(b.path));
  30. const manifest={version:'ksf.board-local.publish.v1',release:'ksf-board-local-20261111-r1',origin:base,homepageSha256:sha(home.bytes),files};
  31. await fs.writeFile('deploy/board-local-20261111.json',JSON.stringify(manifest,null,2)+'\n');
  32. console.log(JSON.stringify({files:files.length,changed:files.filter(f=>f.changed).length,newFiles:files.filter(f=>f.allowMissing).length,homepageSha256:manifest.homepageSha256,productionWrites:0}));