verify-release.mjs 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546
  1. import fs from 'node:fs';
  2. import crypto from 'node:crypto';
  3. import assert from 'node:assert/strict';
  4. import { compileDialogue, manualFile, bankFile } from '../../scripts/update-workshop-dialogue-manual.mjs';
  5. const root = 'skills/ksf-workshop-runtime', out = 'docs/report-quality-html-20261111';
  6. const sha = x => crypto.createHash('sha256').update(x).digest('hex');
  7. const before = JSON.parse(fs.readFileSync(out + '/before/manifest.json'));
  8. const protectedFiles = Object.entries(before).filter(([p]) => /scripts\/ksf-(secure|workshop|identity)\.mjs$/.test(p));
  9. for (const [file, hash] of protectedFiles) assert.equal(sha(fs.readFileSync(file)), hash, file);
  10. assert.ok(fs.readFileSync(root + '/final-SKILL.md').equals(fs.readFileSync(root + '/SKILL.md')));
  11. let count = 1;
  12. for (const line of fs.readFileSync(root + '/FILES.sha256', 'utf8').trim().split(/\r?\n/)) {
  13. const [hash, file] = line.split(' ');assert.equal(sha(fs.readFileSync(root + '/' + file)), hash, file);count++;
  14. }
  15. const bank = JSON.parse(fs.readFileSync(bankFile));
  16. const oldBank = JSON.parse(fs.readFileSync(out + '/before/' + bankFile));
  17. assert.deepEqual(Object.keys(bank.steps), Object.keys(oldBank.steps));
  18. let rounds = 0;
  19. for (const id of Object.keys(bank.steps)) {
  20. assert.equal(bank.steps[id].title, oldBank.steps[id].title);
  21. assert.deepEqual(bank.steps[id].references, oldBank.steps[id].references);
  22. assert.equal(bank.steps[id].rounds.length, oldBank.steps[id].rounds.length);
  23. for (let i = 0; i < bank.steps[id].rounds.length; i++) {
  24. for (const key of ['title', 'purpose', 'prerequisite', 'deliver']) assert.deepEqual(bank.steps[id].rounds[i][key], oldBank.steps[id].rounds[i][key]);
  25. rounds++;
  26. }
  27. }
  28. assert.deepEqual(bank.steps.topic.rounds[0], oldBank.steps.topic.rounds[0]);
  29. assert.deepEqual(bank.steps.proposal.rounds.at(-1), oldBank.steps.proposal.rounds.at(-1));
  30. const manual = fs.readFileSync(manualFile, 'utf8');
  31. const data = JSON.parse(manual.match(/window\.WORKSHOP_MANUAL_DATA = (\{[\s\S]*?\n\});\r?\n<\/script>/)[1]);
  32. const oldData = JSON.parse(fs.readFileSync(out + '/before/' + manualFile, 'utf8').match(/window\.WORKSHOP_MANUAL_DATA = (\{[\s\S]*?\n\});\r?\n<\/script>/)[1]);
  33. assert.deepEqual(data.steps, oldData.steps, '课程正文与既定产出不改');
  34. const actual = JSON.parse(manual.match(/ const dialogueMap = (\{[^\r\n]+\});/)[1]);
  35. assert.deepEqual(actual, compileDialogue(bank, data.steps));
  36. const packageReceipt = JSON.parse(fs.readFileSync('docs/workshop-final-files-20261111/skill-package-receipt.json'));
  37. assert.equal(packageReceipt.sha256, sha(fs.readFileSync('docs/workshop-final-files-20261111/final-康师傅工作坊.skill')));
  38. assert.equal(packageReceipt.publicFilesVerified, count);
  39. const localLinks = [...fs.readFileSync(root + '/SKILL.md', 'utf8').matchAll(/\]\((references\/[^)#]+)(?:#[^)]*)?\)/g)].map(m => m[1]);
  40. for (const file of localLinks) assert.ok(fs.existsSync(root + '/' + file), file);
  41. const result = { ok: true, steps: data.steps.length, rounds, courseTextAndOutputsUnchanged: true,
  42. promptBankMatchesManual: true, accountBindingUnchanged: true, imageConfirmationRoundUnchanged: true,
  43. protectedRuntimeUnchanged: protectedFiles.map(([file]) => file), publicFilesVerified: count,
  44. skillPackageSha256: packageReceipt.sha256, browserChecks: 11, productionWrites: 0, deployed: false };
  45. fs.writeFileSync(out + '/release-verification.json', JSON.stringify(result, null, 2) + '\n');
  46. console.log(JSON.stringify(result));