Preflight API capabilities before publishing content data

This commit is contained in:
2026-10-26 17:25:10 +08:00
parent 38ec492ca3
commit f8fde443f5
+3 -1
View File
@@ -25,6 +25,8 @@ for(const name of [...assetNames].sort()){
if(!state.assets[hash]&&!pending.some(a=>a.hash===hash))pending.push({name,bytes,hash});
}
if(!dryRun){
const capabilities=await Promise.all(['/api/upload?agent_mode=True','/api/posts?agent_mode=True'].map(async url=>{const r=await fetch(base+url,{signal:AbortSignal.timeout(30000)});if(!r.ok)throw Error('无法读取 API 能力说明');return r.text()}));
if(!capabilities[0].includes('assetOnly')||!capabilities[1].includes('publicId'))throw Error('目标站点尚未部署所需通用 API,停止发布');
for(let offset=0;offset<pending.length;offset+=16){
const batch=pending.slice(offset,offset+16),form=new FormData();form.set('kind','image');form.set('assetOnly','true');
for(const item of batch)form.append('files',new Blob([item.bytes],{type:'image/png'}),item.name.replaceAll('/','--'));
@@ -51,7 +53,7 @@ for(const l of layouts){
const extra=new Set(['source.png',`${l.id}/background.png`]);for(const e of raw.find(x=>x.id===l.id).elements)for(const key of ['asset','mask','underlay'])if(e[key])extra.add(e[key].slice('asset:'.length));
posts.push({key:l.id,publicId:ids[l.id],slug:'layout-reference-'+l.id,title:l.id+' · '+l.name,description:`${l.id} 原图一一对应:${text.length} 组文字、${images.length} 组图位、${decor.length} 组装饰;含图像、坐标及 HTML/React/Next.js 示例。`,markdown,main:[`${l.id}/source.png`,`${l.id}/annotated.png`,`${l.id}/text-map.png`,`${l.id}/image-map.png`,`${l.id}/decoration-map.png`],extra:[...extra]});
}
for(const p of posts){if(p.markdown.length>100000)throw Error('正文超长 '+p.key);if(p.markdown.includes('/layout-reference/'))throw Error('正文仍有专用静态路径 '+p.key)}
for(const p of posts){p.markdown=p.markdown.trim();if(p.markdown.length>100000)throw Error('正文超长 '+p.key);if(p.markdown.includes('/layout-reference/'))throw Error('正文仍有专用静态路径 '+p.key)}
if(dryRun){console.log(JSON.stringify({posts:posts.length,uniqueFiles:assetNames.size,uniqueHashes:new Set(Object.values(byFile).map(a=>a.hash)).size,maxText:Math.max(...posts.map(p=>p.markdown.length)),cover:'router-cover.png'}));process.exit(0)}
const tag=await api('/api/tags',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:'高级感排版',slug:'premium-layout'})});
for(const p of [...posts.slice(1),posts[0]]){