From 5e810e3a09736cf066004139a5885994cdd94351 Mon Sep 17 00:00:00 2001 From: Aisland Date: Fri, 4 Sep 2026 00:11:10 +0800 Subject: [PATCH] =?UTF-8?q?fix(release-tooling):=20release-prep=20?= =?UTF-8?q?=E6=8C=89=20Codex=20=E5=A4=8D=E6=A0=B8=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E5=9B=9B=E7=82=B9=E2=80=94=E2=80=94=E6=9E=84=E5=BB=BAcwd?= =?UTF-8?q?=E5=9B=BA=E5=AE=9A01=5Fcontent/=E5=88=A0=E9=99=A4skip-build?= =?UTF-8?q?=E9=80=83=E9=80=B8=E9=80=9A=E9=81=93/package.json=E8=A1=A5pack?= =?UTF-8?q?=E5=85=A5=E5=8F=A3/=E5=AF=BC=E5=85=A5=E6=96=AD=E8=A8=80?= =?UTF-8?q?=E5=8A=A0=E5=BC=BA(apply=E5=87=BD=E6=95=B0+inject=E6=95=B0?= =?UTF-8?q?=E7=BB=84+name+HEALTH=5FPATH+=E5=8C=85=E5=86=85=E5=8F=8Cbundle?= =?UTF-8?q?=E7=89=88=E6=9C=AC)=EF=BC=9BAGENTS=20=E8=A1=A5=20=E5=8F=91?= =?UTF-8?q?=E5=B8=83=E6=89=93=E5=8C=85=E5=94=AF=E4=B8=80=E5=85=A5=E5=8F=A3?= =?UTF-8?q?=20npm=20run=20pack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 01_content/package.json | 3 ++- 01_content/release-prep.mjs | 34 ++++++++++++++++++++++++++-------- AGENTS.md | 5 +++++ 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/01_content/package.json b/01_content/package.json index a361cbb..86245e7 100644 --- a/01_content/package.json +++ b/01_content/package.json @@ -33,7 +33,8 @@ }, "scripts": { "build": "node build.mjs", - "check": "node --check lib/index.js && node --check lib/client.js" + "check": "node --check lib/index.js && node --check lib/client.js", + "pack": "node release-prep.mjs" }, "peerDependencies": { "@deepseek-ai/cordis": "*", diff --git a/01_content/release-prep.mjs b/01_content/release-prep.mjs index eb8b5f7..72c7a48 100644 --- a/01_content/release-prep.mjs +++ b/01_content/release-prep.mjs @@ -8,9 +8,8 @@ * - 绝不执行:git 操作 / push / tag / gh release / 任何对外发布动作。 * - 任何断言失败 = 进程以非零码退出,绝不输出"验收通过"。 * - * 用法(脚本以自身位置解析 01_content,不依赖执行 cwd): + * 用法(脚本以自身位置解析 01_content,不依赖执行 cwd;任何目录都可调用): * node release-prep.mjs - * node release-prep.mjs --skip-build # 仅重新验收现有 lib(排障用) */ import { spawnSync } from 'node:child_process' import { createHash } from 'node:crypto' @@ -20,7 +19,6 @@ import { fileURLToPath } from 'node:url' import { tmpdir } from 'node:os' const HERE = dirname(fileURLToPath(import.meta.url)) // 固定 = 01_content,绝不依赖执行 cwd -const SKIP_BUILD = process.argv.includes('--skip-build') // Windows 下 spawnSync('npm') 有 ENOENT、spawnSync('npm.cmd') 有 EINVAL 问题。 // 统一通过 node 的 npm-cli.js 调用(node 自带 npm 前缀,跨平台稳定)。 const { execPath } = await import('node:process') @@ -50,10 +48,9 @@ const VERSION = pkg.version console.log('[release-prep] version = ' + VERSION) // ---------- 2. 构建 + 语法检查 ---------- -if (!SKIP_BUILD) { - const build = spawnSync(process.execPath, [join(HERE, 'build.mjs')], { stdio: 'inherit' }) - if (build.status !== 0) { console.error('[release-prep] FAIL: build failed'); process.exit(1) } -} +// 构建必带 cwd: HERE(脚本自身位置=01_content),否则从仓库根调用会输出到根 lib/ 造成旧包 +const build = spawnSync(process.execPath, [join(HERE, 'build.mjs')], { stdio: 'inherit', cwd: HERE }) +if (build.status !== 0) { console.error('[release-prep] FAIL: build failed'); process.exit(1) } for (const f of ['lib/index.js', 'lib/client.js']) { const chk = spawnSync(process.execPath, ['--check', join(HERE, f)], { stdio: 'pipe' }) if (chk.status !== 0) { console.error('[release-prep] FAIL: node --check ' + f); process.exit(1) } @@ -102,7 +99,28 @@ mkdirSync(inst, { recursive: true }) writeFileSync(join(inst, 'package.json'), JSON.stringify({ name: 'wt-prep-test', version: '0.0.0', private: true })) const ins = runNpm(['install', tgz, '--no-audit', '--no-fund', '--loglevel=error'], { cwd: inst }) if (ins.status !== 0) { console.error('[release-prep] FAIL: npm install'); console.error(ins.stderr); process.exit(1) } -const imp = spawnSync(process.execPath, ['--input-type=module', '-e', "const m = await import('dsh-worktable'); const k = Object.keys(m); if (!['apply','inject','name','HEALTH_PATH'].every(x => k.includes(x))) { console.error('missing exports: ' + k.join(',')); process.exit(3) }"], { cwd: inst, stdio: 'pipe', encoding: 'utf8' }) +// import 断言脚本(写临时文件,避免 -e 多行转义):导出名称 + 类型 + 内容 + 包内版本文件 +const assertJs = join(inst, 'assert.mjs') +writeFileSync(assertJs, [ + "import { readFileSync } from 'node:fs'", + "const TARGET_VERSION = " + JSON.stringify(VERSION), + "const m = await import('dsh-worktable')", + "const fails = []", + "if (typeof m.apply !== 'function') fails.push('apply not function: ' + typeof m.apply)", + "if (!Array.isArray(m.inject)) fails.push('inject not array')", + "if (m.name !== 'dsh-worktable') fails.push('name mismatch: ' + m.name)", + "if (m.HEALTH_PATH !== '/api/worktable/health') fails.push('HEALTH_PATH mismatch: ' + m.HEALTH_PATH)", + "const pkg = JSON.parse(readFileSync('./node_modules/dsh-worktable/package.json', 'utf8'))", + "if (pkg.version !== TARGET_VERSION) fails.push('installed version mismatch: ' + pkg.version)", + "const idx = readFileSync('./node_modules/dsh-worktable/lib/index.js', 'utf8')", + "const cli = readFileSync('./node_modules/dsh-worktable/lib/client.js', 'utf8')", + "if (!idx.includes(JSON.stringify(TARGET_VERSION))) fails.push('index.js missing target version')", + "if (!cli.includes(JSON.stringify(TARGET_VERSION))) fails.push('client.js missing target version')", + "if (fails.length) { console.error(fails.join(String.fromCharCode(10))); process.exit(3) }", + "console.log('assert ok: apply/inject/name/HEALTH_PATH + versions(' + TARGET_VERSION + ')')", +].join('\n')) +const assertUrl = 'file:///' + assertJs.replace(/\\/g, '/') +const imp = spawnSync(process.execPath, ['--input-type=module', '--eval', 'await import(' + JSON.stringify(assertUrl) + ')'], { cwd: inst, stdio: 'pipe', encoding: 'utf8' }) if (imp.status !== 0) { console.error('[release-prep] FAIL: import() assertion'); console.error(imp.stderr); process.exit(1) } console.log('[release-prep] install + import assertion passed') diff --git a/AGENTS.md b/AGENTS.md index ab98bf8..d96e96c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -50,6 +50,11 @@ node --check lib/index.js - 变更视图状态结构时同步更新 PRD 的持久化说明。 - **构建必须 `cd 01_content` 后执行**:误在仓库根跑会把 lib 写到仓库根 `lib/`,宿主仍加载 `01_content/lib` 旧 bundle,出现「改完不生效」假象(已有教训,见工作日志)。 +- **发布打包唯一入口 = `npm run pack`(01_content/release-prep.mjs)**:版本一致性 → 构建(cwd 固定 01_content)→ + node --check → npm pack → 结构清单断言(package/ 前缀 + 精确 7 文件,无 src/、无 .map)→ + 独立临时目录 npm install + import() 断言(apply 函数/inject 数组/name/HEALTH_PATH/包内双 bundle 版本) + → 双资产同源复制 + SHA-256。脚本零 git/gh 动作,发布上传由 gh 手动完成并下载远端比对。 + **发布禁止裸 npm pack 或手工 tar 生成发布包**;脚本从仓库任意目录调用均安全(以自身位置解析)。 ## 领域约定(会话中必须遵守)