Files
probably-rendering-one-more…/scripts/publish-studio-library.mjs
codex-bot 04df551f6a
studio CI and deployment / verify-and-deploy (pull_request) Successful in 6m43s
waterfall-wiki CI and deployment / verify-and-deploy (pull_request) Has been cancelled
Harden series validation and synchronize agent delivery contracts
2026-10-28 23:42:18 +08:00

42 lines
4.8 KiB
JavaScript

/** Execute via production auth-cli run; token stays in memory. Uses only the designated origin. */
import {readFile,writeFile,mkdir} from 'node:fs/promises';
const base=process.env.AUTH_CHECK_BASE_URL||'http://8.211.154.134:18131';
if(base!=='http://8.211.154.134:18131')throw Error('Use the designated acceptance origin');
const dir=process.env.STUDIO_LIBRARY_DIR||'/tmp/studio-library';
await mkdir(dir,{recursive:true});
async function api(path,body,method=body?'POST':'GET'){
const response=await fetch(base+path,{method,...(body?{headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}:{})});
if(!response.ok)throw Error(path+' status '+response.status);
return response.json();
}
const capability=await fetch(base+'/api/studio/series?agent_mode=True');if(!capability.ok||!(await capability.text()).includes('schemaVersion 2'))throw Error('Series API is not deployed; refusing to publish future documentation');
const studioCapability=await api('/studio/api/layouts');if(studioCapability.seriesDocumentVersion!==1)throw Error('Studio renderer is not deployed; refusing to publish future documentation');
const canonical='8eb8c76500580f7a4b56ba3c',legacy='9ebd8f78d54b4b9db741b4e8';
const {entry:current,tags}=await api('/api/notes/'+canonical),{entry:old}=await api('/api/notes/'+legacy);
// Snapshot only non-secret editorial state. Retain the first backup across repeat publication.
for(const [id,entry] of [[canonical,current],[legacy,old]]){try{await readFile(dir+'/'+id+'-before.json')}catch{await writeFile(dir+'/'+id+'-before.json',JSON.stringify(entry),{mode:0o600})}}
const catalogue=await api('/studio/api/layouts');
let markdown=(await readFile(new URL('../content/studio-tools/layouts-api.md',import.meta.url),'utf8')).replace('{{LAYOUTS}}',catalogue.layouts.filter(l=>l.family==='fixed').map(l=>`- [${l.id} · ${l.name}](${l.url}) · [参数](${l.api})`).join('\n'));
const appendixMarker='## 兼容工具原说明与代码示例';
const historicalNotice='> 以下保留历史工具示例,不代表当前 API 保存与打开流程。历史文字中关于首页、编辑器和旧结果地址的描述已过时:当前由 API 创建 ID,直接打开 `/studio/template-layout?id={UUID}`,无需选版或编辑器。当前契约以上文为准。';
if(current.editableMarkdown.includes(appendixMarker)){
const appendix=current.editableMarkdown.slice(current.editableMarkdown.indexOf(appendixMarker));
markdown+='\n\n'+(appendix.includes(historicalNotice)?appendix:appendix.replace(appendixMarker,appendixMarker+'\n\n'+historicalNotice));
}else{
if(old.archived)throw Error('Original toolbox appendix is missing; refusing to replace it with the archived redirect');
const legacyOriginal=JSON.parse(await readFile(dir+'/'+legacy+'-before.json','utf8')).editableMarkdown;
markdown+='\n\n'+appendixMarker+'\n\n'+historicalNotice+'\n\n以下保留原工具的说明与 HTML/React/Next.js 调用示例;它们是无状态兼容接口,持久化页面请使用上文 CRUD。\n\n'+legacyOriginal;
}
const tagIds=entry=>[...new Set([...(entry.systemTags||[]).map(t=>t.id),...entry.categories.map(name=>tags.find(t=>t.name===name)?.id).filter(Boolean)])];
const body=(entry,text,title=entry.title)=>({title,description:entry.description,markdown:text,tags:tagIds(entry),assets:entry.assets,coverAssetId:entry.coverAssetId,revision:entry.revision});
await api('/api/notes/'+canonical,{...body(current,markdown,'Studio 图文 API · 多页作品与统一视觉设计'),description:'coding agent 制定主题分镜与统一视觉,生成配图,经 HTTP 保存可修订多页作品;凭稳定 ID 直接打开原生结果并在远程导出高清图片。'},'PUT');
const redirectText=`# Studio 文档已合并\n\n[前往 Studio 排版 API 统一入口](/explore/${canonical})\n\n[可视化 API 库](/studio) · [原 ref-17 工具](/studio/ideas/learning-skills-template) · [原三种构图工具](/studio/ideas/layout-selection)\n\n完整原说明与代码示例已保留在统一入口的兼容工具章节。此记录仅可恢复归档,未删除工具、素材或设计。`;
await api('/api/notes/'+legacy,body(old,redirectText),'PUT');
await api('/api/posts/'+legacy+'/archive',{archived:true});
const entries=await api('/api/explore-feed?category='+encodeURIComponent('文档入口'));
const expected=['3c98946754c33309d73d5284','4ce3283fd98bb03a8fb39a19',canonical].sort();
if(entries.total!==3||entries.nextCursor||JSON.stringify(entries.entries.map(e=>e.id).sort())!==JSON.stringify(expected))throw Error('Document-entry category does not contain exactly the intended three posts');
const after=(await api('/api/notes/'+legacy)).entry;
if(!after.archived||!after.editableMarkdown.includes(canonical))throw Error('Legacy compatibility link missing');
console.log(JSON.stringify({canonical:base+'/explore/'+canonical,legacyArchived:true,documentEntryCount:entries.total}));