#!/usr/bin/env node /** * 把领域服务运行时(ant-copilot 的 esbuild 打包产物)同步进插件包。 * * 为什么随插件分发而不是 npm 安装:`@ringoarch/ant-copilot` 发在私有 Gitea registry 上, * 匿名 GET 包元数据返回 401 —— 走 npm 路线等于要求每个同事机器上都配一个 Gitea token。 * 打包产物本身是自包含单文件(依赖全内联),把它放进插件包即可做到「装插件 = 拿到运行时」。 * * 用法: * node scripts/sync-runtime.mjs # studio + writer * node scripts/sync-runtime.mjs studio # 只同步指定插件 * ANT_COPILOT_DIR=~/Dev/ant/ant-copilot node scripts/sync-runtime.mjs * * 前置:源仓库已构建(`npm run build:mcp` → dist/deta-mcp.mjs)。 * 产物:plugins/<插件>/runtime/dist/deta-mcp.mjs + plugins/<插件>/runtime/manifest.json * manifest 记录来源版本/提交/大小/sha256 —— 供启动器与 install-runtime 自校验(防拷错、防陈旧)。 */ import { execFileSync } from 'node:child_process'; import { createHash } from 'node:crypto'; import { copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..'); const SRC_REPO = resolve(process.env.ANT_COPILOT_DIR ?? join(process.env.HOME ?? '', 'Dev/ant/ant-copilot')); const SRC_ENTRY = join(SRC_REPO, 'dist/deta-mcp.mjs'); const plugins = process.argv.slice(2).filter((a) => !a.startsWith('-')); const targets = plugins.length ? plugins : ['studio', 'writer']; function die(msg) { process.stderr.write(`[sync-runtime] ${msg}\n`); process.exit(1); } if (!existsSync(SRC_ENTRY)) { die(`源打包产物不存在:${SRC_ENTRY}\n 请先在 ${SRC_REPO} 执行 npm run build:mcp`); } const bytes = readFileSync(SRC_ENTRY); const sha256 = createHash('sha256').update(bytes).digest('hex'); const version = JSON.parse(readFileSync(join(SRC_REPO, 'package.json'), 'utf8')).version; let gitSha = ''; try { gitSha = execFileSync('git', ['rev-parse', '--short', 'HEAD'], { cwd: SRC_REPO, encoding: 'utf8' }).trim(); } catch { gitSha = 'unknown'; // 允许在非 git 检出(如 CI 解包目录)中运行 } let pluginVersion = {}; const manifest = { source: '@ringoarch/ant-copilot', version, gitSha, builtAt: new Date().toISOString(), entry: 'dist/deta-mcp.mjs', sha256, bytes: bytes.length, note: '由 scripts/sync-runtime.mjs 生成;勿手工编辑。安装到 ~/.deta/runtime 由启动器/install-runtime 完成。', }; for (const name of targets) { const pluginDir = join(ROOT, 'plugins', name); const pj = join(pluginDir, '.codex-plugin', 'plugin.json'); if (!existsSync(pj)) die(`插件不存在或清单缺失:${pj}`); const pv = JSON.parse(readFileSync(pj, 'utf8')).version ?? null; const outDir = join(pluginDir, 'runtime', 'dist'); mkdirSync(outDir, { recursive: true }); const outEntry = join(outDir, 'deta-mcp.mjs'); copyFileSync(SRC_ENTRY, outEntry); const out = { ...manifest, plugin: name, pluginVersion: pv }; writeFileSync(join(pluginDir, 'runtime', 'manifest.json'), `${JSON.stringify(out, null, 2)}\n`); const size = statSync(outEntry).size; if (size !== bytes.length) die(`写入后大小不一致(${size} != ${bytes.length}):${outEntry}`); process.stdout.write( `[sync-runtime] ${name}: ${(size / 1048576).toFixed(2)}MB sha256=${sha256.slice(0, 12)}… ` + `来自 ${manifest.source}@${version} (${gitSha})\n`, ); } process.stdout.write(`[sync-runtime] 完成。提交 runtime/ 后同事装插件即自带运行时。\n`);