| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354 |
- #!/usr/bin/env node
- // Patch the actual online baseline, never deploy unrelated local gateway edits.
- import {readFileSync,writeFileSync,mkdirSync} from 'node:fs';
- import {createHash} from 'node:crypto';
- import {resolve} from 'node:path';
- import {pathToFileURL} from 'node:url';
- const helper=readFileSync(new URL('./cloud/support-requests.js',import.meta.url),'utf8').trim();
- const dispatch=" if(operation==='ops/support-requests/list') return response.json({success:true,data:await supportRequestsPage(context,input)});";
- const anchor=" if(operation==='ops/live/versions')";
- export function patchSupportRequests(code){
- if(code.includes('ops/support-requests/list')||code.includes('async function supportRequestsPage(')){
- if(code.includes(dispatch)&&code.includes(helper))return code;
- throw Error('线上已有不同版本的反馈入口,请先人工核对');
- }
- if(code.split(anchor).length!==2)throw Error('线上网关分发入口与预期不一致,停止发布');
- const next=code.replace(anchor,dispatch+'\n'+anchor)+'\n'+helper+'\n';
- if(new Function(next+'\nreturn typeof handler;')()!=='function')throw Error('网关语法校验失败');
- return next;
- }
- async function main(){
- const master=process.env.XIAOSHU_MASTER_KEY,token=process.env.XIAOSHU_SYNC_OPERATOR_TOKEN;
- if(!master)throw Error('请通过受信任的部署配置设置 XIAOSHU_MASTER_KEY;不要将密钥写入脚本或提交到 Git');
- if(!token)throw Error('缺少用于发布后只读验证的 XIAOSHU_SYNC_OPERATOR_TOKEN');
- const commit=process.argv.includes('--commit'),base=(process.env.XIAOSHU_PARSE_URL||'https://server.xiaoshu.pro/parse').replace(/\/$/,''),app=process.env.XIAOSHU_PARSE_APP_ID||'7pIbDBJmKx_main';
- const hash=s=>createHash('sha256').update(s).digest('hex');
- const root=resolve(process.env.XIAOSHU_RELEASE_DIR||'dist/support-requests-release',new Date().toISOString().replace(/[:.]/g,'-'));
- const 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(20000)});const p=await r.json();if(!r.ok||p.error)throw Error('发布接口失败:'+path.split('?')[0]+' HTTP '+r.status+' '+JSON.stringify(p.error||p.code));return p;}
- 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('未找到唯一的运营 V3 网关');
- const before=rows[0],code=patchSupportRequests(before.code);
- mkdirSync(root,{recursive:true,mode:0o700});
- writeFileSync(root+'/before.json',JSON.stringify(before,null,2),{mode:0o600});
- writeFileSync(root+'/candidate.js',code,{mode:0o600});
- const proof={objectId:before.objectId,beforeHash:hash(before.code),afterHash:hash(code),backup:root+'/before.json'};
- if(!commit){console.log(JSON.stringify({...proof,dryRun:true}));return;}
- const functionBase=(process.env.XIAOSHU_FUNCTION_URL||base.replace(/\/parse$/,'/api/functions')).replace(/\/$/,'');
- const syncResponse=await fetch(functionBase+'/xiaoshu/sync/direct-v1',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({token,params:{action:'status'}}),signal:AbortSignal.timeout(20000)});
- const sync=await syncResponse.json();
- if(!syncResponse.ok||!sync.success||!Array.isArray(sync.data?.runs))throw Error('无法确认同步任务状态,停止发布');
- if(sync.data.runs.some(j=>j.status==='running'))throw Error('同步任务正在运行,请空闲后重试');
- const latest=await parse('/classes/Function/'+before.objectId);
- if(latest.code!==before.code||latest.updatedAt!==before.updatedAt)throw Error('线上网关已被其他发布修改,停止覆盖');
- if(code!==before.code)await parse('/classes/Function/'+before.objectId,'PUT',{code});
- const after=await parse('/classes/Function/'+before.objectId);
- if(after.code!==code)throw Error('发布后代码回读不一致,请检查备份和线上状态');
- const url=(process.env.XIAOSHU_FUNCTION_URL||base.replace(/\/parse$/,'/api/functions'))+'/xiaoshu/ops/gateway-v3';
- const r=await fetch(url,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({token,params:{operation:'ops/support-requests/list',page:1,pageSize:20}}),signal:AbortSignal.timeout(20000)}),result=await r.json();
- const passed=r.ok&&result.success===true&&Array.isArray(result.data?.items);
- writeFileSync(root+'/proof.json',JSON.stringify({...proof,deployed:true,readbackVerified:true,liveQueryVerified:passed,total:result.data?.total,httpStatus:r.status},null,2),{mode:0o600});
- if(!passed)throw Error('代码发布成功,但真实查询未通过:'+String(result.message||result.error||r.status));
- console.log(JSON.stringify({...proof,deployed:true,readbackVerified:true,liveQueryVerified:true,total:result.data.total}));
- }
- if(process.argv[1]&&import.meta.url===pathToFileURL(resolve(process.argv[1])).href)main().catch(e=>{console.error(e.message);process.exitCode=1;});
|