deploy-workshop.mjs 3.2 KB

123456789101112131415161718192021222324252627282930313233343536
  1. import fs from 'node:fs';
  2. import path from 'node:path';
  3. import crypto from 'node:crypto';
  4. import {ROOT,request,classRows} from './platform-client.mjs';
  5. const names=['ksfGetPublicBoard','ksfStartWorkshopSession','ksfSaveWorkshopTask','ksfCreateProjectFromOpportunity','ksfTransitionProject','ksfSaveWorkshopConcept','ksfGetWorkshopProject','ksfGetIngestContract'];
  6. const manifest=JSON.parse(fs.readFileSync(path.join(ROOT,'dist/manifest.json')));
  7. const dry=!process.argv.includes('--apply');fs.mkdirSync(path.join(ROOT,'evidence'),{recursive:true});
  8. const existing=await classRows('Function');const schemas=await request('GET','/schemas');
  9. const journal={at:new Date().toISOString(),dryRun:dry,functions:[],schemaChanges:0,permissionChanges:0,credentialsChanged:false};
  10. // Do not overwrite unrelated changes: existing entrypoints must still be the audited v0.1 source.
  11. for(const name of names){
  12. const item=manifest.functions.find(x=>x.name===name);if(!item)throw Error('构建缺少目标函数');
  13. const code=fs.readFileSync(path.join(ROOT,item.file),'utf8');if(crypto.createHash('sha256').update(code).digest('hex')!==item.sha256)throw Error('构建指纹不匹配');
  14. const matches=existing.filter(x=>x.name===name||x.path===item.path);if(matches.length>1||matches.some(x=>x.path!==item.path||x.name!==name))throw Error('函数名称/路径冲突');
  15. const row=matches[0];
  16. if(row&&row.code!==code&&!row.code.includes("release: 'ksf.cloud.v0.1'")&&!row.code.includes('ksf.cloud.v0.1'))throw Error('发现未知函数版本,停止覆盖');
  17. journal.functions.push({name,path:item.path,sha256:item.sha256,objectId:row?.objectId||null,action:row?(row.code===code?'unchanged':'update'):'create'});
  18. }
  19. fs.writeFileSync(path.join(ROOT,'evidence/workshop-deploy-plan.json'),JSON.stringify(journal,null,2));
  20. if(!dry){
  21. const backupPath=path.join(ROOT,'evidence/workshop-functions-before-'+Date.now()+'.json');
  22. fs.writeFileSync(backupPath,JSON.stringify(existing.filter(x=>names.includes(x.name)),null,2));
  23. for(const item of journal.functions){
  24. if(item.action==='unchanged')continue;
  25. const entry=manifest.functions.find(x=>x.name===item.name),code=fs.readFileSync(path.join(ROOT,entry.file),'utf8');
  26. if(item.objectId)await request('PUT','/classes/Function/'+item.objectId,{code});
  27. else item.objectId=(await request('POST','/classes/Function',{name:item.name,path:item.path,code,ACL:{}})).objectId;
  28. const read=await request('GET','/classes/Function/'+item.objectId);if(read.code!==code)throw Error('函数回读不一致');
  29. item.verified=true;fs.writeFileSync(path.join(ROOT,'evidence/workshop-deployment.json'),JSON.stringify(journal,null,2));
  30. }
  31. const after=await request('GET','/schemas');
  32. for(const before of schemas.results){const now=after.results.find(x=>x.className===before.className);if(JSON.stringify(before.fields)!==JSON.stringify(now.fields)||JSON.stringify(before.classLevelPermissions)!==JSON.stringify(now.classLevelPermissions))throw Error('表结构/权限意外变化');}
  33. journal.finishedAt=new Date().toISOString();journal.schemaAndPermissionsPreserved=true;
  34. fs.writeFileSync(path.join(ROOT,'evidence/workshop-deployment.json'),JSON.stringify(journal,null,2));
  35. }
  36. console.log(JSON.stringify(journal,null,2));