| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647 |
- import fs from 'node:fs';
- import path from 'node:path';
- import crypto from 'node:crypto';
- import {fileURLToPath} from 'node:url';
- const ROOT=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
- const sha=x=>crypto.createHash('sha256').update(x).digest('hex');
- const fail=code=>{throw Object.assign(Error(code),{code});};
- const targets={ksfStartWorkshopSession:'yKpUay6RDG',ksfGetIngestContract:'dWSrHp4FfQ'};
- export async function deployIdentity({root=ROOT,request,apply=false}={}){
- const manifest=JSON.parse(fs.readFileSync(path.join(root,'dist/identity-manifest.json'),'utf8'));
- if(manifest.appId!=='FKieRbIoGt_main'||manifest.version!=='ksf.identity-deployment.v1'||JSON.stringify(manifest.functions.map(x=>x.name).sort())!==JSON.stringify(Object.keys(targets).sort()))fail('INVALID_DEPLOY_MANIFEST');
- const entries=manifest.functions.map(x=>{
- if(x.objectId!==targets[x.name]||x.path!=='/'+x.name||x.file!=='dist/'+x.name+'.js'||!/^[a-f0-9]{64}$/.test(x.beforeSha256))fail('INVALID_DEPLOY_MANIFEST');
- const code=fs.readFileSync(path.join(root,x.file),'utf8');if(sha(code)!==x.sha256)fail('PACKAGE_HASH_MISMATCH');new Function(code);return{...x,code};
- });
- const beforeSchemas=await request('GET','/schemas');const before=[];
- for(const x of entries){
- const row=await request('GET','/classes/Function/'+x.objectId);
- if(row.name!==x.name||row.path!==x.path||typeof row.code!=='string')fail('FUNCTION_IDENTITY_MISMATCH');
- const current=sha(row.code);if(![x.beforeSha256,x.sha256].includes(current))fail('UNKNOWN_FUNCTION_VERSION');before.push(row);
- }
- const journal={ok:true,at:new Date().toISOString(),readOnly:!apply,operation:apply?'identity-functions-published':'read-only-plan',functions:entries.map((x,i)=>({name:x.name,objectId:x.objectId,beforeSha256:sha(before[i].code),afterSha256:x.sha256,action:sha(before[i].code)===x.sha256?'unchanged':'update'})),schemaChanges:0,permissionChanges:0,businessWrites:0,frontendChanged:false,gatewayModified:false};
- if(!apply)return journal;
- const evidence=path.join(root,'evidence');fs.mkdirSync(evidence,{recursive:true});const backup=path.join(evidence,'identity-functions-before-'+crypto.randomUUID()+'.json');
- fs.writeFileSync(backup,JSON.stringify(before,null,2),{flag:'wx',mode:0o600});journal.backup=backup;
- const attempted=[];
- try{
- for(const [i,x]of entries.entries()){
- if(journal.functions[i].action==='unchanged')continue;
- const row=await request('GET','/classes/Function/'+x.objectId);if(sha(row.code)!==sha(before[i].code))fail('FUNCTION_CHANGED_CONCURRENTLY');
- attempted.push({x,prior:before[i]});await request('PUT','/classes/Function/'+x.objectId,{code:x.code});
- const read=await request('GET','/classes/Function/'+x.objectId);if(read.code!==x.code)fail('FUNCTION_READBACK_MISMATCH');journal.functions[i].verified=true;
- }
- if(JSON.stringify((await request('GET','/schemas')).results)!==JSON.stringify(beforeSchemas.results))fail('SCHEMA_OR_PERMISSIONS_CHANGED');
- journal.schemaAndPermissionsPreserved=true;journal.finishedAt=new Date().toISOString();fs.writeFileSync(path.join(evidence,'identity-deployment.json'),JSON.stringify(journal,null,2));return journal;
- }catch(error){
- let complete=true;
- for(const {x,prior}of attempted.reverse())try{const row=await request('GET','/classes/Function/'+x.objectId);if(row.code===prior.code)continue;if(row.code!==x.code){complete=false;continue;}await request('PUT','/classes/Function/'+x.objectId,{code:prior.code});if((await request('GET','/classes/Function/'+x.objectId)).code!==prior.code)complete=false;}catch{complete=false;}
- const failure={...journal,ok:false,error:{code:error.code||'DEPLOY_FAILED'},rollbackComplete:complete};fs.writeFileSync(path.join(evidence,'identity-deployment.json'),JSON.stringify(failure,null,2));error.receipt=failure;throw error;
- }
- }
- if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){
- try{
- if(process.argv.slice(2).some(x=>x!=='--apply')||process.argv.length>3)fail('USAGE');
- const {request}=await import('./platform-client.mjs');console.log(JSON.stringify(await deployIdentity({request,apply:process.argv.includes('--apply')}),null,2));
- }catch(e){console.error(JSON.stringify(e.receipt||{ok:false,error:{code:/^[A-Z][A-Z0-9_]+$/.test(e.code||'')?e.code:'DEPLOY_STOPPED',message:'停止部署;核对配置、版本和备份,未输出凭据或原始异常。'}}));process.exitCode=1;}
- }
|