check-presenter-runtime-sync.mjs 1.7 KB

123456789101112131415161718192021222324252627282930313233343536373839
  1. #!/usr/bin/env node
  2. import {readFileSync} from 'node:fs';
  3. import {dirname,resolve} from 'node:path';
  4. import {fileURLToPath} from 'node:url';
  5. const root=resolve(dirname(fileURLToPath(import.meta.url)),'..');
  6. const files=['assets/template.html','assets/template-swiss.html'];
  7. const blocks=[
  8. ['presenter CSS','/* ============ 演讲者模式 ============ */','/* ============ /演讲者模式 ============ */'],
  9. ['presenter JavaScript','/* =============== 演讲者模式 / 观众屏同步 =============== */','/* =============== /演讲者模式 / 观众屏同步 =============== */'],
  10. ];
  11. function extract(source,label,startMarker,endMarker,file){
  12. const start=source.indexOf(startMarker),end=source.indexOf(endMarker,start+startMarker.length);
  13. if(start<0||end<0)throw new Error(`${file}: missing ${label} boundary marker.`);
  14. return source.slice(start,end+endMarker.length);
  15. }
  16. function firstDifferentLine(a,b){
  17. const left=a.split('\n'),right=b.split('\n'),count=Math.max(left.length,right.length);
  18. for(let i=0;i<count;i++)if(left[i]!==right[i])return i+1;
  19. return 0;
  20. }
  21. const sources=files.map(file=>[file,readFileSync(resolve(root,file),'utf8')]);
  22. let failed=false;
  23. for(const [label,start,end] of blocks){
  24. const extracted=sources.map(([file,source])=>[file,extract(source,label,start,end,file)]);
  25. const [baseFile,base]=extracted[0];
  26. let blockFailed=false;
  27. for(const [file,value] of extracted.slice(1)){
  28. if(value===base)continue;
  29. failed=true;
  30. blockFailed=true;
  31. console.error(`ERROR ${label} drift: ${baseFile} and ${file} first differ at block line ${firstDifferentLine(base,value)}.`);
  32. }
  33. if(!blockFailed)console.log(`PASS ${label} is byte-identical across ${files.length} templates.`);
  34. }
  35. process.exit(failed?1:0);