| 1234567891011121314151617181920212223242526272829 |
- #!/usr/bin/env node
- import fs from 'node:fs';
- import {createHash} from 'node:crypto';
- import {buildAppointmentDeletionGateway,patchAppointmentDeletionCapability} from './lib/legacy-appointment-deletions.mjs';
- const env=process.env,master=env.XIAOSHU_MASTER_KEY,token=env.XIAOSHU_SYNC_OPERATOR_TOKEN,out=env.XIAOSHU_DIRECT_DEPLOY_REPORT_DIR;
- if(!master||!token||!out)throw Error('Authorized deployment configuration and private backup directory required');
- fs.mkdirSync(out,{recursive:true,mode:0o700});
- const base=(env.XIAOSHU_PARSE_URL||'https://server.xiaoshu.pro/parse').replace(/\/$/,''),app=env.XIAOSHU_PARSE_APP_ID||'7pIbDBJmKx_main',co=env.XIAOSHU_SYNC_COMPANY_ID||'7pIbDBJmKx';
- const hash=value=>createHash('sha256').update(value).digest('hex');
- async function api(path,method='GET',body){const r=await fetch(base+path,{method,headers:{'Content-Type':'application/json','X-Parse-Application-Id':app,'X-Parse-Master-Key':master},body:body?JSON.stringify(body):undefined,signal:AbortSignal.timeout(30000)}),d=await r.json();if(!r.ok||d.error)throw Error('Deployment API HTTP '+r.status);return d;}
- async function find(path){const rows=(await api('/classes/Function?'+new URLSearchParams({where:JSON.stringify({path}),limit:'2'}))).results;if(rows.length>1)throw Error('Ambiguous deployment target');return rows[0];}
- const direct=await find('xiaoshu/sync/direct-v1');if(!direct)throw Error('Direct sync gateway missing');
- const match=direct.code.match(/windows=(\[.*?\]),q=/);if(!match)throw Error('Unknown existing migration windows');
- const code=buildAppointmentDeletionGateway(co,JSON.parse(match[1])),patched=patchAppointmentDeletionCapability(direct.code);new Function(patched);
- const path='xiaoshu/sync/appointment-deletions-v1',previous=await find(path);
- fs.writeFileSync(out+'/deletion-function-before.json',JSON.stringify(previous||null),{mode:0o600});fs.writeFileSync(out+'/direct-function-before.json',JSON.stringify(direct),{mode:0o600});
- const plan={directId:direct.objectId,beforeHash:hash(direct.code),afterHash:hash(patched),deletionHash:hash(code),path};
- fs.writeFileSync(out+'/deployment-plan.json',JSON.stringify(plan,null,2),{mode:0o600});
- if(!process.argv.includes('--apply')){console.log(JSON.stringify({...plan,prepared:true}));process.exit(0);}
- if(hash((await api('/classes/Function/'+direct.objectId)).code)!==plan.beforeHash)throw Error('Concurrent sync gateway change');
- const definition={name:'xiaoshu.sync.appointment-deletions.v1',path,type:'standalone',enabled:true,code,params:[],paramList:[],respType:'json',desc:'确认旧预约删除,保护新系统数据与教学凭据'};
- const saved=await api('/classes/Function'+(previous?'/'+previous.objectId:''),previous?'PUT':'POST',definition),id=previous?.objectId||saved.objectId;
- if(hash((await api('/classes/Function/'+id)).code)!==plan.deletionHash)throw Error('Deletion gateway readback failed');
- const status=await fetch(base.replace(/\/parse$/,'/api/functions/')+path,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({token,params:{action:'status'}}),signal:AbortSignal.timeout(20000)}),result=await status.json();
- if(!status.ok||!result.success||result.data.version!=='confirmed-ids-v1')throw Error('Deletion gateway authorization/status failed');
- if(hash((await api('/classes/Function/'+direct.objectId)).code)!==plan.beforeHash)throw Error('Concurrent sync gateway change');
- if(plan.beforeHash!==plan.afterHash)await api('/classes/Function/'+direct.objectId,'PUT',{code:patched});
- if(hash((await api('/classes/Function/'+direct.objectId)).code)!==plan.afterHash)throw Error('Direct gateway readback failed');
- fs.writeFileSync(out+'/deployment-verified.json',JSON.stringify({...plan,id,verified:true,at:new Date().toISOString()},null,2),{mode:0o600});console.log(JSON.stringify({deployed:true,sourceReadbackVerified:true,existingSyncCodePreserved:true}));
|