| 123456789101112131415161718192021222324252627282930313233 |
- #!/usr/bin/env node
- // --prepare is read-only. --deploy / --rollback require an explicit release decision.
- import {readFileSync,writeFileSync,mkdirSync,chmodSync} from 'node:fs';
- import {createHash} from 'node:crypto';
- import {homedir} from 'node:os';
- import {join} from 'node:path';
- import {patchAdminReadHelpers,optimizedReadHelpers} from './admin-read-performance-patch.mjs';
- const mode=process.argv.includes('--deploy')?'deploy':process.argv.includes('--rollback')?'rollback':'prepare';
- const dir=process.env.XIAOSHU_READ_RELEASE_DIR||join(homedir(),'.local/state/xiaoshu/cloud-releases/2026-10-15-admin-read-performance');
- const base=process.env.XIAOSHU_PARSE_URL||'https://server.xiaoshu.pro/parse',app=process.env.XIAOSHU_PARSE_APP_ID,master=process.env.XIAOSHU_MASTER_KEY,token=process.env.XIAOSHU_SYNC_OPERATOR_TOKEN;
- if(!app||!master||!token)throw Error('Missing authorized release configuration');
- const hash=s=>createHash('sha256').update(s).digest('hex'),headers={'Content-Type':'application/json','X-Parse-Application-Id':app,'X-Parse-Master-Key':master};
- async function parse(path,method='GET',body){const r=await fetch(base+path,{method,headers,body:body?JSON.stringify(body):undefined,signal:AbortSignal.timeout(30000)}),d=await r.json();if(!r.ok||d.error)throw Error('Release API HTTP '+r.status);return d;}
- mkdirSync(dir,{recursive:true,mode:0o700});chmodSync(dir,0o700);
- if(mode==='prepare'){
- const rows=(await parse('/classes/Function?'+new URLSearchParams({where:JSON.stringify({path:'xiaoshu/ops/gateway-v3'}),limit:'2'}))).results;
- if(rows.length!==1)throw Error('Ambiguous operations gateway');const original=rows[0],code=patchAdminReadHelpers(original.code);
- writeFileSync(join(dir,'original.json'),JSON.stringify(original),{mode:0o600});writeFileSync(join(dir,'candidate.js'),code,{mode:0o600});
- const manifest={preparedAt:new Date().toISOString(),objectId:original.objectId,path:original.path,beforeHash:hash(original.code),afterHash:hash(code),helpers:optimizedReadHelpers};
- writeFileSync(join(dir,'manifest.json'),JSON.stringify(manifest,null,2),{mode:0o600});console.log(JSON.stringify({mode,dir,...manifest}));
- }else{
- const manifest=JSON.parse(readFileSync(join(dir,'manifest.json'),'utf8')),original=JSON.parse(readFileSync(join(dir,'original.json'),'utf8')),candidate=readFileSync(join(dir,'candidate.js'),'utf8');
- if(hash(original.code)!==manifest.beforeHash||hash(candidate)!==manifest.afterHash)throw Error('Prepared release has changed');
- const current=await parse('/classes/Function/'+manifest.objectId),expected=mode==='deploy'?manifest.beforeHash:manifest.afterHash,target=mode==='deploy'?candidate:original.code;
- if(hash(current.code)===hash(target)){console.log(JSON.stringify({mode,unchanged:true}));process.exit(0);}
- if(hash(current.code)!==expected)throw Error('Online gateway changed after preparation; reprepare and revalidate');
- const sync=await fetch(base.replace(/\/parse\/?$/,'/api/functions/xiaoshu/sync/direct-v1'),{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({token,params:{action:'status'}}),signal:AbortSignal.timeout(30000)}),status=await sync.json();
- if(!sync.ok||status.success===false||!Array.isArray(status.data?.runs))throw Error('Unable to verify synchronization is idle');
- if(status.data.runs.some(r=>r.status==='running'))throw Error('Synchronization is running; retry when idle');
- await parse('/classes/Function/'+manifest.objectId,'PUT',{code:target});
- const verified=await parse('/classes/Function/'+manifest.objectId);if(hash(verified.code)!==hash(target))throw Error('Release readback mismatch');
- writeFileSync(join(dir,mode+'-receipt.json'),JSON.stringify({at:new Date().toISOString(),hash:hash(target)}),{mode:0o600});console.log(JSON.stringify({mode,verified:true,hash:hash(target)}));
- }
|