build-workshop-skill.mjs 3.6 KB

12345678910111213141516171819202122232425262728
  1. import fs from 'node:fs';
  2. import crypto from 'node:crypto';
  3. const root='skills/ksf-workshop-runtime',sha=x=>crypto.createHash('sha256').update(x).digest('hex');
  4. // Maintain final-SKILL.md; keep the conventional loader entry synchronized.
  5. const finalEntry=fs.readFileSync(root+'/final-SKILL.md');
  6. if(!fs.existsSync(root+'/SKILL.md')||!fs.readFileSync(root+'/SKILL.md').equals(finalEntry))fs.writeFileSync(root+'/SKILL.md',finalEntry);
  7. // Ship a self-contained browser copy in src/; both renderers use this one maintained source.
  8. const browserStatistics='// Generated by scripts/build-workshop-skill.mjs; maintain the Skill report-statistics.mjs source.\n'+fs.readFileSync(root+'/scripts/report-statistics.mjs','utf8');
  9. if(!fs.existsSync('src/core/report-statistics.js')||fs.readFileSync('src/core/report-statistics.js','utf8')!==browserStatistics)fs.writeFileSync('src/core/report-statistics.js',browserStatistics);
  10. const browserAssessment='// Generated by scripts/build-workshop-skill.mjs; maintain the Skill course-assessment.mjs source.\n'+fs.readFileSync(root+'/scripts/course-assessment.mjs','utf8');
  11. if(!fs.existsSync('src/core/course-assessment.js')||fs.readFileSync('src/core/course-assessment.js','utf8')!==browserAssessment)fs.writeFileSync('src/core/course-assessment.js',browserAssessment);
  12. const hash=sha(fs.readFileSync(root+'/scripts/ksf-workshop.mjs'));
  13. const identityHash=sha(fs.readFileSync(root+'/scripts/ksf-identity.mjs'));
  14. const file=root+'/scripts/ksf-secure.mjs';
  15. fs.writeFileSync(file,fs.readFileSync(file,'utf8')
  16. .replace(/const WORKSHOP_ENTRY_HASH = '[^']+';/,"const WORKSHOP_ENTRY_HASH = '"+hash+"';")
  17. .replace(/const ACCOUNT_ENTRY_HASH = '[^']+';/,"const ACCOUNT_ENTRY_HASH = '"+sha(fs.readFileSync(root+'/scripts/ksf-account.mjs'))+"';")
  18. .replace(/const IDENTITY_MODULE_HASH = '[^']+';/,"const IDENTITY_MODULE_HASH = '"+identityHash+"';"));
  19. // Keep the declared public package complete; never enumerate local identity/credential files.
  20. const declared=fs.existsSync(root+'/FILES.sha256')?fs.readFileSync(root+'/FILES.sha256','utf8').trim().split(/\r?\n/).filter(Boolean).map(line=>{
  21. const match=line.match(/^[a-f0-9]{64}\s+\*?(.+)$/);if(!match||match[1].includes('..')||/^[\\/]|:/.test(match[1]))throw Error('非法公开清单路径');return match[1];
  22. }).filter(file=>!['references/workshop-dialogue-prompts.json','references/workshop-case-evidence.md'].includes(file)):[];
  23. const files=[...new Set([...declared,'SKILL.md','references/identity.md','references/workshop-tasks.md','references/write-tasks.md','references/business-data-dictionary.json','references/full-schema-and-intake.md','runtime-lock.json','scripts/describe-business-schema.mjs','scripts/ksf-identity.mjs','scripts/ksf-identity-bridge.mjs','scripts/ksf-secure.mjs','scripts/ksf-workshop.mjs','scripts/ksf.mjs','scripts/report-contract.mjs','scripts/save-report.mjs','references/workshop-report-parity.md'])].sort();
  24. for (const file of ['references/workshop-dialogue.md','references/workshop-evidence-counting.md','references/workshop-v3-outputs.md','scripts/course-deliverables.mjs','scripts/course-assessment.mjs','scripts/ksf-account.mjs','scripts/render-report-html.mjs','scripts/report-statistics.mjs']) if (!files.includes(file)) files.push(file);
  25. files.sort();
  26. const entries=files.map(file=>sha(fs.readFileSync(root+'/'+file))+' '+file);
  27. fs.writeFileSync(root+'/FILES.sha256',entries.join('\n')+'\n');
  28. console.log(JSON.stringify({skill:root,files:entries.length,workshopEntrySha256:hash,identityModuleSha256:identityHash,legacyEntryPreserved:sha(fs.readFileSync(root+'/scripts/ksf.mjs'))==='7ef128f7d022bfbc15c0ec813bfd87995c6297c6efe407a563c840f014aba477'}));