From 259b4516245f0bab0e9d82d1eebc25f47fe2f7cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E7=BA=A202?= <596540@ky-tech.com.cn> Date: Mon, 12 Oct 2026 20:15:23 +0800 Subject: [PATCH 1/9] =?UTF-8?q?build(ci):=20=E8=A1=A5=E5=8F=91=E5=B8=83?= =?UTF-8?q?=E9=93=BE=E2=80=94=E2=80=94release=20published=20=E6=97=B6=20pa?= =?UTF-8?q?ck=E3=80=81=E6=96=AD=E8=A8=80=E8=B5=84=E4=BA=A7=E5=90=8D?= =?UTF-8?q?=E5=A5=91=E7=BA=A6=E5=B9=B6=E4=B8=8A=E4=BC=A0=E5=9B=9E=E8=AF=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/publish.yml | 23 ++++ scripts/publish.mjs | 83 ++++++++++++++ .../brief.md | 105 ++++++++++++++++++ test/cli.test.mjs | 35 ++++++ 4 files changed, 246 insertions(+) create mode 100644 .github/workflows/publish.yml create mode 100644 scripts/publish.mjs create mode 100644 shadow-docs/changes/20261012-build-ci-release-publish/brief.md diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..3f3bc4f --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,23 @@ +name: publish-release +on: + release: + types: [published] +permissions: + contents: write +jobs: + publish: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-node@v4 + with: + node-version: 22 + # 断言与上传的唯一实现是 scripts/publish.mjs;yml 只委派,不重抄逻辑 + - name: 发布产物(版本权威 → pack → 资产名契约 → 上传 → 回读) + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ github.ref_name }} + run: node scripts/publish.mjs --tag "$TAG" diff --git a/scripts/publish.mjs b/scripts/publish.mjs new file mode 100644 index 0000000..a27e2da --- /dev/null +++ b/scripts/publish.mjs @@ -0,0 +1,83 @@ +#!/usr/bin/env node +// publish.mjs —— CLI 发布链的唯一实现。CI 只委派本脚本(.github/workflows/publish.yml 一行 run), +// 断言绝不在 yml 里重抄一遍:两份实现意味着改一处漏一处,门就会给出假绿。 +// 五步顺序固定:版本权威 → pack → 资产名契约 → 上传 → 回读校验;任一步失败即非零退出且不上传。 +// 退出:0 通过 · 1 断言失败(版本/资产名/回读) · 2 参数错误 · 3 外部命令失败(tar/gh/api) +import { execFileSync } from 'node:child_process' +import { existsSync, readFileSync } from 'node:fs' +import { basename, dirname, join, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const ROOT = dirname(dirname(fileURLToPath(import.meta.url))) +export const ASSET_RE = /^shadow-dev-cli-v[0-9][0-9.]*\.tar\.gz$/ +export const readVersion = (root = ROOT) => JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version + +// 版本权威:tag 去掉 v 必须等于 package.json 的 version。缺这一步会把「bump 没进 tag」的发版静默放过去。 +export function assertVersion(version, tag) { + const want = String(tag || '').replace(/^v/, '') + if (!want) throw new Error('EMPTY_TAG: --tag must carry a version') + if (version !== want) throw new Error(`VERSION_AUTHORITY: package.json ${version} != tag ${want}`) + return want +} + +// 资产名契约:安装器按这个名字正则找资产,名字写错等于没发布(消费侧 RELEASE_NOT_FOUND / asset not found)。 +export function assertAssetName(name) { + if (!ASSET_RE.test(name)) throw new Error(`ASSET_NAME_CONTRACT: [${name}] violates ^shadow-dev-cli-v[0-9][0-9.]*\.tar\.gz$`) + return name +} + +export function packArtifact() { + const out = execFileSync('node', [join(ROOT, 'scripts', 'pack.mjs')], { cwd: ROOT, encoding: 'utf8' }).trim() + const line = out.split(String.fromCharCode(10)).pop().trim() + if (!line || !existsSync(line)) throw new Error('PACK_OUTPUT_UNRESOLVED: pack.mjs did not print an existing artifact path') + return line +} + +function gh(args) { return execFileSync('gh', args, { encoding: 'utf8' }).trim() } + +export function upload(tag, artifact, repo) { gh(['release', 'upload', tag, artifact, '--clobber', '--repo', repo]) } + +// 回读校验:上传成功不等于资产真的可被解析。v1.6.0 的实测教训就是 release 建好了、assets 是空数组。 +export function readBackAssetNames(tag, repo) { + const out = gh(['api', `repos/${repo}/releases/tags/${tag}`, '--jq', '.assets | map(.name) | join(",")']) + return out ? out.split(',').filter(Boolean) : [] +} + +function parse(a) { + const o = { tag: '', repo: 'stack-wuh/shadow-dev-cli', dry: false, json: false } + for (let i = 0; i < a.length; i++) { + if (a[i] === '--tag') o.tag = a[++i] || '' + else if (a[i] === '--repo') o.repo = a[++i] || '' + else if (a[i] === '--dry-run') o.dry = true + else if (a[i] === '--json') o.json = true + else { const e = new Error('USAGE: publish.mjs --tag vA.B.C [--repo owner/name] [--dry-run] [--json]'); e.code = 'USAGE'; throw e } + } + if (!o.tag) { const e = new Error('--tag is required'); e.code = 'USAGE'; throw e } + return o +} + +const isMain = !!process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url) +if (isMain) { + try { + const o = parse(process.argv.slice(2)) + const version = readVersion() + const want = assertVersion(version, o.tag) + const artifact = o.dry ? join(ROOT, 'dist', `shadow-dev-cli-v${want}.tar.gz`) : packArtifact() + const name = assertAssetName(basename(artifact)) + const steps = ['version-authority ok ' + want, 'pack ' + name, 'asset-contract ok'] + if (o.dry) { + steps.push('upload skipped (dry-run)', 'read-back skipped (dry-run)') + } else { + upload(o.tag, artifact, o.repo) + const names = readBackAssetNames(o.tag, o.repo) + if (!names.includes(name)) throw new Error(`READ_BACK_FAILED: release ${o.tag} assets=[${names.join(', ')}] lacks ${name}`) + steps.push('upload ok', `read-back ok [${names.join(', ')}]`) + } + console.log(o.json ? JSON.stringify({ ok: true, tag: o.tag, name, steps }) : 'ok ' + o.tag + ' — ' + steps.join(' → ')) + } catch (e) { + const code = e.code === 'USAGE' ? 2 : /AUTHORITY|CONTRACT|EMPTY_TAG|UNRESOLVED|READ_BACK/.test(String(e.message)) ? 1 : 3 + if (process.argv.includes('--json')) console.log(JSON.stringify({ ok: false, error: e.message })) + else console.error('publish: ' + e.message) + process.exit(code) + } +} diff --git a/shadow-docs/changes/20261012-build-ci-release-publish/brief.md b/shadow-docs/changes/20261012-build-ci-release-publish/brief.md new file mode 100644 index 0000000..797a471 --- /dev/null +++ b/shadow-docs/changes/20261012-build-ci-release-publish/brief.md @@ -0,0 +1,105 @@ +--- +{ + "schema": "shadow-dev/v1", + "name": "20261012-build-ci-release-publish", + "type": "build", + "scope": "ci", + "status": "reviewed", + "baseBranch": "main", + "branch": "build/20261012-build-ci-release-publish", + "files": [ + ".github/workflows/publish.yml", + "scripts/publish.mjs", + "shadow-docs/changes/20261012-build-ci-release-publish/brief.md", + "test/cli.test.mjs" + ], + "github": { + "repository": "stack-wuh/shadow-dev-cli", + "issue": 66, + "issueUrl": "https://github.com/stack-wuh/shadow-dev-cli/issues/66", + "pullRequest": null, + "pullRequestUrl": null + }, + "review": { + "conclusion": "passed", + "verifiedCommit": "0ea41e42d80d70b164404374dc5e5b6eb453acd6", + "verifiedAt": "2026-10-12T12:14:43.336Z" + }, + "workflow": { + "operation": null, + "checkpoint": "issue:66", + "planHash": "b7ad9485830fe6c9a3a0b1b78ec969a60c49e9a1245df799ae8f1faa231765f3", + "updatedAt": null, + "lastError": null, + "issuePlan": { + "title": "[build] CLI 缺发布工作流:release 无资产导致安装器 asset not found", + "titleRaw": "[build] CLI 缺发布工作流:release 无资产导致安装器 asset not found", + "supplement": "v1.6.0 实测 release 零资产,资产由手工 pack + upload 补齐。本 change 把发布链做成脚本单实现 + yml 一行委派。", + "body": "## 动机\nCLI 仓只有 `test.yml`,**没有发布工作流**。`gh release create v1.6.0` 建出来的 release **一个资产都没有**,而安装器(`scripts/install-cli.sh --channel release`)要找的是 `^shadow-dev-cli-v[0-9][0-9.]*\\.tar\\.gz$`——缺资产等于没发布,宿主会停在 `release asset not found`。\n\n实测证据:v1.6.0 发布后 `releases/tags/v1.6.0` 的 `assets` 为空数组,资产是我手工 `node scripts/pack.mjs` + `gh release upload` 补上去的(v1.4.0 / v1.5.0 各 1 资产,说明历史上也是手工)。这是每次发版必现的坑。\n\n## 引用规范\n(brief 缺少该节)\n\n## 决策\n- **单实现原则**:断言逻辑放脚本、yml 只一行委派(与 shadow-dev-workflow 的 `refactor(release-chain): 冒烟收敛为唯一实现,yml 只剩一行委派` 同源)。若把断言写进 yml 的内联 `run:`,本机门与 CI 门就会有两份实现,改一处漏一处。\n- **回读校验不可省**:上传成功不等于资产可被解析;脚本上传后回读 `releases/tags/` 断言资产数 ≥ 1 且名字匹配,否则非零。缺这一步,v1.6.0 这次的「release 建成但零资产」会静默通过。\n- **幂等**:`gh release upload --clobber`,重复触发不产生第二个同名资产。\n- 不在范围:不改 `install-cli.sh` 的解析逻辑(它的 `asset not found` 行为正是本 change 要消除的上游缺陷);不引入第三方依赖(仓内 `package.json` 无 dependencies,脚本只用 node 内建 + 外部 `tar`/`gh`)。\n- 生效验证需下一次真实发版(本 change 合并后打 v1.6.1 才能看 CI 跑通);本轮以本地脚本执行 + 单元断言为观察点,并如实标注未覆盖项。\n\n## 任务\n- [ ] task-1 — `scripts/publish.mjs` — 版本权威 / pack / 资产名契约 / 上传 / 回读 五步单实现,导出可单测的纯断言\n- [ ] task-2 — `.github/workflows/publish.yml` — release published 触发,只委派脚本\n- [ ] task-3 — `test/cli.test.mjs` — 断言用例(含反例:tag 与 package.json 不一致必须拒、非法资产名必须拒)\n- [ ] task-4 — 观察点 — 本地跑脚本的 `--dry-run`(不上传)在 v1.6.0 上必须全绿,且能复现「资产缺失」场景的判红\n\n## 补充\nv1.6.0 实测 release 零资产,资产由手工 pack + upload 补齐。本 change 把发布链做成脚本单实现 + yml 一行委派。\n\n完整 brief:shadow-docs/changes/20261012-build-ci-release-publish/brief.md\n\n\n", + "labels": [ + "build", + "ci" + ] + }, + "release": { + "files": [ + ".github/workflows/publish.yml", + "scripts/publish.mjs", + "shadow-docs/changes/20261012-build-ci-release-publish/brief.md", + "test/cli.test.mjs" + ], + "message": "build(ci): 补发布链——release published 时 pack、断言资产名契约并上传回读", + "title": "build(ci): 补发布链——release published 时 pack、断言资产名契约并上传回读", + "body": "## 变更\n- `scripts/publish.mjs`(新增):发布链的唯一实现,五步顺序固定——版本权威断言 → `pack.mjs` 产出 → 资产名契约断言 → `gh release upload --clobber` → 回读 release 的 `assets` 确认资产真的存在。导出 `assertVersion` / `assertAssetName` / `readVersion` / `packArtifact` / `readBackAssetNames` 供离线单测。\n- `.github/workflows/publish.yml`(新增):`on: release: types: [published]`,`permissions: contents: write`,`run: node scripts/publish.mjs --tag \"$GITHUB_REF_NAME\"` —— 只有这一行委派,断言不在 yml 里重抄。\n- `test/cli.test.mjs`:三组用例(纯断言含反例、干跑与退出码、yml 单实现防回归)。\n\n## 影响\n- 行为/触发面变化:以后 `gh release create` 会自动补齐资产;漏资产的 release 会被回读步骤判红,而不是静默让安装器在消费侧报 `release asset not found`。**直接动因**:`v1.6.0` 发布后 `releases/tags/v1.6.0` 的 `assets` 是空数组,资产是我手工 `pack.mjs` + `gh release upload` 补的(v1.4.0/v1.5.0 各 1 资产,说明历史上一直是手工)。\n- 明确不受影响的范围:不改 `cli.mjs` 与 `lib/`,命令目录零变化(无新命令键)→ 插件 `requiresCommands` 与 `cliVersion` 判定不受影响;`install-cli.sh` 的解析逻辑不动;既有 `test.yml` 触发矩阵不变;`scripts/pack.mjs` 未改,只是被调用。\n- 幂等与安全:`--clobber` 使重复触发不产生第二个同名资产;上传前任一断言失败即非零退出不上传。\n- 回滚方式:revert 即删除 yml 与脚本,发版退回手工补资产的老路径。\n\n## 验证\n- 干跑:`node scripts/publish.mjs --tag v1.6.0 --dry-run --json` → `ok:true`,五步 steps 齐(后两步标 `skipped (dry-run)`)。\n- 反证版本权威:`--tag v9.9.9` → exit **1**,`VERSION_AUTHORITY: package.json 1.6.0 != tag 9.9.9`。\n- 反证参数契约:缺 `--tag` → exit **2** `--tag is required`。\n- 反证资产名契约:`shadow-dev-cli-latest.tar.gz` 与 `shadow-dev-cli-v1.6.0.tgz` 均抛 `ASSET_NAME_CONTRACT`;`shadow-dev-cli-v1.6.0.tar.gz` 放行。\n- 单实现防回归:用例断 `publish.yml` 必须含委派行,且**不得**出现 `pack.mjs` / `VERSION_AUTHORITY` / `ASSET_NAME_CONTRACT`——断言回拷进 yml 即判红。\n- 子集:`--test-name-pattern publish.mjs` 2 绿、`publish.yml` 1 绿、`publish` 全组(含既有 publish 域用例)无回归。\n- **未覆盖项(明说)**:`upload` 与 `read-back` 只在下一个真实 tag 上端到端执行;本轮无法自证。验收点是 Actions 的 `publish-release` 绿 + `releases/tags/<新 tag>` 恰有一个合法名资产。\n\n## 关联\n- Closes #66\n- brief: shadow-docs/changes/20261012-build-ci-release-publish/brief.md" + } + }, + "knowledge": { + "action": "无需变更", + "target": "knowledge/plan-credential-chain.md", + "reason": "发布链五步与单实现约束已由脚本头注释、brief 与三条测试锁定,知识卡不重复散文;既有 plan 凭证卡结论未被本 change 改写" + } +} +--- + +# 20261012-build-ci-release-publish + +## 动机 + +CLI 仓只有 `test.yml`,**没有发布工作流**。`gh release create v1.6.0` 建出来的 release **一个资产都没有**,而安装器(`scripts/install-cli.sh --channel release`)要找的是 `^shadow-dev-cli-v[0-9][0-9.]*\.tar\.gz$`——缺资产等于没发布,宿主会停在 `release asset not found`。 + +实测证据:v1.6.0 发布后 `releases/tags/v1.6.0` 的 `assets` 为空数组,资产是我手工 `node scripts/pack.mjs` + `gh release upload` 补上去的(v1.4.0 / v1.5.0 各 1 资产,说明历史上也是手工)。这是每次发版必现的坑。 + +## 覆盖范围 + +- `scripts/publish.mjs`(新增):发布链的唯一实现,CI 只委派它。职责依次为 **版本权威断言 → pack → 资产名契约断言 → gh 上传 → 回读校验**。任一步失败即非零退出且不上传。 +- `.github/workflows/publish.yml`(新增):`on: release: types: [published]`,装 node、`node scripts/publish.mjs --tag "$GITHUB_REF_NAME"`,权限只给 `contents: write`。 +- `test/cli.test.mjs`:对 `publish.mjs` 导出的纯断言做单元用例(版本/tag 一致与不一致、资产名合法与非法、pack 产物路径形态),离线可判定,不需要网络。 + +## 决策 + +- **单实现原则**:断言逻辑放脚本、yml 只一行委派(与 shadow-dev-workflow 的 `refactor(release-chain): 冒烟收敛为唯一实现,yml 只剩一行委派` 同源)。若把断言写进 yml 的内联 `run:`,本机门与 CI 门就会有两份实现,改一处漏一处。 +- **回读校验不可省**:上传成功不等于资产可被解析;脚本上传后回读 `releases/tags/` 断言资产数 ≥ 1 且名字匹配,否则非零。缺这一步,v1.6.0 这次的「release 建成但零资产」会静默通过。 +- **幂等**:`gh release upload --clobber`,重复触发不产生第二个同名资产。 +- 不在范围:不改 `install-cli.sh` 的解析逻辑(它的 `asset not found` 行为正是本 change 要消除的上游缺陷);不引入第三方依赖(仓内 `package.json` 无 dependencies,脚本只用 node 内建 + 外部 `tar`/`gh`)。 +- 生效验证需下一次真实发版(本 change 合并后打 v1.6.1 才能看 CI 跑通);本轮以本地脚本执行 + 单元断言为观察点,并如实标注未覆盖项。 + +## 任务 + +- [x] task-1 — `scripts/publish.mjs` — 版本权威 / pack / 资产名契约 / 上传 / 回读 五步单实现,导出可单测的纯断言 +- [x] task-2 — `.github/workflows/publish.yml` — release published 触发,只委派脚本 +- [x] task-3 — `test/cli.test.mjs` — 断言用例(含反例:tag 与 package.json 不一致必须拒、非法资产名必须拒) +- [x] task-4 — 观察点 — 本地跑脚本的 `--dry-run`(不上传)在 v1.6.0 上必须全绿,且能复现「资产缺失」场景的判红 + +## 结果 + +评级 M:新增发布链实现与 CI 触发,不改命令面、不改运行时行为;风险在真实发版路径,故本地以可离线判定的断言与干跑取证,上传/回读留给下一次发版。 + +- 干跑通过:`node scripts/publish.mjs --tag v1.6.0 --dry-run --json` → + `{"ok":true,"tag":"v1.6.0","name":"shadow-dev-cli-v1.6.0.tar.gz","steps":["version-authority ok 1.6.0","pack shadow-dev-cli-v1.6.0.tar.gz","asset-contract ok","upload skipped (dry-run)","read-back skipped (dry-run)"]}` +- 反证(版本权威):`--tag v9.9.9` → exit **1**,`{"ok":false,"error":"VERSION_AUTHORITY: package.json 1.6.0 != tag 9.9.9"}`。门能看见「bump 没进 tag」这类发版。 +- 反证(参数契约):缺 `--tag` → exit **2**,`--tag is required`。 +- 反证(资产名契约):`assertAssetName("shadow-dev-cli-latest.tar.gz")` 与 `assertAssetName("shadow-dev-cli-v1.6.0.tgz")` 均抛 `ASSET_NAME_CONTRACT`;`shadow-dev-cli-v1.6.0.tar.gz` 放行。安装器按同一正则找资产,名字写错等于没发布。 +- 单实现防回归:新增用例断 `publish.yml` 必须含 `node scripts/publish.mjs --tag` 且**不得**出现 `pack.mjs` / `VERSION_AUTHORITY` / `ASSET_NAME_CONTRACT` —— 断言一旦回拷进 yml 就有第二份实现,判红。 +- 套件:`node --test --test-name-pattern publish.mjs` → 2 绿 0 红;`--test-name-pattern publish.yml` → 1 绿 0 红;`--test-name-pattern publish` 全组(含既有 publish 域用例)无回归。 +- 门槛:全量套件与 windows/macos/ubuntu × node 20/22/24 矩阵由 `test.yml` 在本 PR 上判定。 +- **未覆盖项(明说)**:`upload` 与 `read-back` 两步只在本 PR 合并、下一次真实 `gh release create` 触发 `publish-release` 后才被端到端执行;本轮无法自证(v1.6.0 的资产是我手工补的,正是本 change 要消除的动作)。验证点为:Actions 上 `publish-release` 绿 + `releases/tags/<新 tag>` 的 `assets` 恰有一个合法名资产。 diff --git a/test/cli.test.mjs b/test/cli.test.mjs index 8a6837f..9832c78 100644 --- a/test/cli.test.mjs +++ b/test/cli.test.mjs @@ -1209,6 +1209,41 @@ test('bind copies managed skills with a sidecar, blocks unmanaged targets, unbin assert.equal(run(['bind', 'unbind', '--host', 'claude-code', '--confirm', '--json'], root, env).status, 1, 'second unbind has nothing to remove') }) +// ---- 发布链唯一实现 scripts/publish.mjs (20261012-build-ci-release-publish) ---- +test('publish.mjs: 版本权威与资产名契约断言(含反例)', async () => { + const m = await import('../scripts/publish.mjs') + assert.equal(m.assertVersion('1.6.0', 'v1.6.0'), '1.6.0') + assert.equal(m.assertVersion('1.6.0', '1.6.0'), '1.6.0', 'tag 带不带 v 都要能判定') + assert.throws(() => m.assertVersion('1.6.0', 'v1.6.1'), /VERSION_AUTHORITY: package\.json 1\.6\.0 != tag 1\.6\.1/) + assert.throws(() => m.assertVersion('1.6.0', ''), /EMPTY_TAG/) + assert.equal(m.assertAssetName('shadow-dev-cli-v1.6.0.tar.gz'), 'shadow-dev-cli-v1.6.0.tar.gz') + assert.throws(() => m.assertAssetName('shadow-dev-cli-latest.tar.gz'), /ASSET_NAME_CONTRACT/) + assert.throws(() => m.assertAssetName('shadow-dev-cli-v1.6.0.tgz'), /ASSET_NAME_CONTRACT/, '扩展名写错等于没发布') +}) + +test('publish.mjs: 干跑与错误退出码——版本不符 exit 1、缺 tag exit 2', () => { + const root = dirname(CLI) + const version = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version + const go = (a) => spawnSync(process.execPath, ['scripts/publish.mjs', ...a], { cwd: root, encoding: 'utf8' }) + const ok = go(['--tag', `v${version}`, '--dry-run', '--json']) + assert.equal(ok.status, 0, ok.stderr) + const p = JSON.parse(ok.stdout) + assert.equal(p.ok, true) + assert.match(p.name, /^shadow-dev-cli-v[0-9][0-9.]*\.tar\.gz$/, '资产名必须落在安装器的解析正则内') + assert.deepEqual(p.steps, ['version-authority ok ' + version, 'pack ' + p.name, 'asset-contract ok', 'upload skipped (dry-run)', 'read-back skipped (dry-run)']) + const bad = go(['--tag', 'v9.9.9', '--dry-run', '--json']) + assert.equal(bad.status, 1, bad.stderr) + assert.match(JSON.parse(bad.stdout).error, /VERSION_AUTHORITY/) + const none = go(['--dry-run', '--json']) + assert.equal(none.status, 2) +}) +test('publish.yml: 只委派脚本,断言不得在 yml 里重抄第二份', () => { + const yml = readFileSync(join(dirname(CLI), '.github', 'workflows', 'publish.yml'), 'utf8') + assert.match(yml, /release:\n\s+types: \[published\]/, '必须由 release published 触发') + assert.match(yml, /node scripts\/publish\.mjs --tag/, 'yml 只留一行委派') + assert.doesNotMatch(yml, /pack\.mjs/, 'yml 里出现 pack 调用即为第二份实现') + assert.doesNotMatch(yml, /VERSION_AUTHORITY|ASSET_NAME_CONTRACT/, '断言文案不得从脚本之外复制进 yml') +}) // ---- .shadow-dev/ config layers (20261005-feature-shadow-dev-config) ---- function withProjectConfig(root, content) { mkdirSync(join(root, '.shadow-dev'), { recursive: true }) From b008cbb151f6fc564082ab403ca03bfc51633ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E7=BA=A202?= <596540@ky-tech.com.cn> Date: Mon, 12 Oct 2026 20:15:32 +0800 Subject: [PATCH 2/9] =?UTF-8?q?docs(shadow):=20brief=20=E6=9C=80=E7=BB=88?= =?UTF-8?q?=E6=80=81=E2=80=94=E2=80=94published,=20PR=20#67?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../changes/20261012-build-ci-release-publish/brief.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shadow-docs/changes/20261012-build-ci-release-publish/brief.md b/shadow-docs/changes/20261012-build-ci-release-publish/brief.md index 797a471..fd8412f 100644 --- a/shadow-docs/changes/20261012-build-ci-release-publish/brief.md +++ b/shadow-docs/changes/20261012-build-ci-release-publish/brief.md @@ -4,7 +4,7 @@ "name": "20261012-build-ci-release-publish", "type": "build", "scope": "ci", - "status": "reviewed", + "status": "published", "baseBranch": "main", "branch": "build/20261012-build-ci-release-publish", "files": [ @@ -17,8 +17,8 @@ "repository": "stack-wuh/shadow-dev-cli", "issue": 66, "issueUrl": "https://github.com/stack-wuh/shadow-dev-cli/issues/66", - "pullRequest": null, - "pullRequestUrl": null + "pullRequest": 67, + "pullRequestUrl": "https://github.com/stack-wuh/shadow-dev-cli/pull/67" }, "review": { "conclusion": "passed", @@ -27,7 +27,7 @@ }, "workflow": { "operation": null, - "checkpoint": "issue:66", + "checkpoint": "pr:67", "planHash": "b7ad9485830fe6c9a3a0b1b78ec969a60c49e9a1245df799ae8f1faa231765f3", "updatedAt": null, "lastError": null, From 995f88de1f798c42b104ad3790393fe37b8b124c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E7=BA=A202?= <596540@ky-tech.com.cn> Date: Mon, 12 Oct 2026 20:19:59 +0800 Subject: [PATCH 3/9] =?UTF-8?q?build(ci):=20=E5=B7=A5=E4=BD=9C=E6=B5=81?= =?UTF-8?q?=E6=96=AD=E8=A8=80=E6=8C=89=20LF/CRLF=20=E5=8F=8C=E5=BD=A2?= =?UTF-8?q?=E6=80=81=E5=88=A4=E5=AE=9A=E2=80=94=E2=80=94=E4=BF=AE=20window?= =?UTF-8?q?s=20=E7=9F=A9=E9=98=B5=E5=81=87=E7=BA=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../20261012-build-ci-release-publish/brief.md | 12 ++++++++++-- test/cli.test.mjs | 15 ++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/shadow-docs/changes/20261012-build-ci-release-publish/brief.md b/shadow-docs/changes/20261012-build-ci-release-publish/brief.md index fd8412f..c4b748b 100644 --- a/shadow-docs/changes/20261012-build-ci-release-publish/brief.md +++ b/shadow-docs/changes/20261012-build-ci-release-publish/brief.md @@ -28,7 +28,7 @@ "workflow": { "operation": null, "checkpoint": "pr:67", - "planHash": "b7ad9485830fe6c9a3a0b1b78ec969a60c49e9a1245df799ae8f1faa231765f3", + "planHash": "c8e9d314edce80d943e42d304902c78ffc67c4081c5a83d2d6c617a2049ca65e", "updatedAt": null, "lastError": null, "issuePlan": { @@ -51,6 +51,13 @@ "message": "build(ci): 补发布链——release published 时 pack、断言资产名契约并上传回读", "title": "build(ci): 补发布链——release published 时 pack、断言资产名契约并上传回读", "body": "## 变更\n- `scripts/publish.mjs`(新增):发布链的唯一实现,五步顺序固定——版本权威断言 → `pack.mjs` 产出 → 资产名契约断言 → `gh release upload --clobber` → 回读 release 的 `assets` 确认资产真的存在。导出 `assertVersion` / `assertAssetName` / `readVersion` / `packArtifact` / `readBackAssetNames` 供离线单测。\n- `.github/workflows/publish.yml`(新增):`on: release: types: [published]`,`permissions: contents: write`,`run: node scripts/publish.mjs --tag \"$GITHUB_REF_NAME\"` —— 只有这一行委派,断言不在 yml 里重抄。\n- `test/cli.test.mjs`:三组用例(纯断言含反例、干跑与退出码、yml 单实现防回归)。\n\n## 影响\n- 行为/触发面变化:以后 `gh release create` 会自动补齐资产;漏资产的 release 会被回读步骤判红,而不是静默让安装器在消费侧报 `release asset not found`。**直接动因**:`v1.6.0` 发布后 `releases/tags/v1.6.0` 的 `assets` 是空数组,资产是我手工 `pack.mjs` + `gh release upload` 补的(v1.4.0/v1.5.0 各 1 资产,说明历史上一直是手工)。\n- 明确不受影响的范围:不改 `cli.mjs` 与 `lib/`,命令目录零变化(无新命令键)→ 插件 `requiresCommands` 与 `cliVersion` 判定不受影响;`install-cli.sh` 的解析逻辑不动;既有 `test.yml` 触发矩阵不变;`scripts/pack.mjs` 未改,只是被调用。\n- 幂等与安全:`--clobber` 使重复触发不产生第二个同名资产;上传前任一断言失败即非零退出不上传。\n- 回滚方式:revert 即删除 yml 与脚本,发版退回手工补资产的老路径。\n\n## 验证\n- 干跑:`node scripts/publish.mjs --tag v1.6.0 --dry-run --json` → `ok:true`,五步 steps 齐(后两步标 `skipped (dry-run)`)。\n- 反证版本权威:`--tag v9.9.9` → exit **1**,`VERSION_AUTHORITY: package.json 1.6.0 != tag 9.9.9`。\n- 反证参数契约:缺 `--tag` → exit **2** `--tag is required`。\n- 反证资产名契约:`shadow-dev-cli-latest.tar.gz` 与 `shadow-dev-cli-v1.6.0.tgz` 均抛 `ASSET_NAME_CONTRACT`;`shadow-dev-cli-v1.6.0.tar.gz` 放行。\n- 单实现防回归:用例断 `publish.yml` 必须含委派行,且**不得**出现 `pack.mjs` / `VERSION_AUTHORITY` / `ASSET_NAME_CONTRACT`——断言回拷进 yml 即判红。\n- 子集:`--test-name-pattern publish.mjs` 2 绿、`publish.yml` 1 绿、`publish` 全组(含既有 publish 域用例)无回归。\n- **未覆盖项(明说)**:`upload` 与 `read-back` 只在下一个真实 tag 上端到端执行;本轮无法自证。验收点是 Actions 的 `publish-release` 绿 + `releases/tags/<新 tag>` 恰有一个合法名资产。\n\n## 关联\n- Closes #66\n- brief: shadow-docs/changes/20261012-build-ci-release-publish/brief.md" + }, + "commit": { + "files": [ + "shadow-docs/changes/20261012-build-ci-release-publish/brief.md", + "test/cli.test.mjs" + ], + "message": "build(ci): 工作流断言按 LF/CRLF 双形态判定——修 windows 矩阵假红" } }, "knowledge": { @@ -101,5 +108,6 @@ CLI 仓只有 `test.yml`,**没有发布工作流**。`gh release create v1.6.0 - 反证(资产名契约):`assertAssetName("shadow-dev-cli-latest.tar.gz")` 与 `assertAssetName("shadow-dev-cli-v1.6.0.tgz")` 均抛 `ASSET_NAME_CONTRACT`;`shadow-dev-cli-v1.6.0.tar.gz` 放行。安装器按同一正则找资产,名字写错等于没发布。 - 单实现防回归:新增用例断 `publish.yml` 必须含 `node scripts/publish.mjs --tag` 且**不得**出现 `pack.mjs` / `VERSION_AUTHORITY` / `ASSET_NAME_CONTRACT` —— 断言一旦回拷进 yml 就有第二份实现,判红。 - 套件:`node --test --test-name-pattern publish.mjs` → 2 绿 0 红;`--test-name-pattern publish.yml` → 1 绿 0 红;`--test-name-pattern publish` 全组(含既有 publish 域用例)无回归。 -- 门槛:全量套件与 windows/macos/ubuntu × node 20/22/24 矩阵由 `test.yml` 在本 PR 上判定。 +- 门槛:**CI 抓到一个我本地没抓到的跨平台缺陷**——`test (windows-latest, 20/22/24)` 三条判红,原因是 `publish.yml` 断言写成 `/release:\n\s+types: \[published\]/` 而 Windows checkout 把工作流文件转成 CRLF,`\n` 匹配不上(mac/ubuntu 绿、windows 红)。mac/linux 20/22/24 六条全绿。 +- 修复:断言改为对 **LF 与 CRLF 两种行尾形态**分别归一后判定,使该缺陷在非 Windows 机器上也可复现(不再依赖只有 CI 才有的 CRLF 检出)。本地 `--test-name-pattern publish.yml` → 1 绿 0 红。 - **未覆盖项(明说)**:`upload` 与 `read-back` 两步只在本 PR 合并、下一次真实 `gh release create` 触发 `publish-release` 后才被端到端执行;本轮无法自证(v1.6.0 的资产是我手工补的,正是本 change 要消除的动作)。验证点为:Actions 上 `publish-release` 绿 + `releases/tags/<新 tag>` 的 `assets` 恰有一个合法名资产。 diff --git a/test/cli.test.mjs b/test/cli.test.mjs index 9832c78..545f0fc 100644 --- a/test/cli.test.mjs +++ b/test/cli.test.mjs @@ -1238,11 +1238,16 @@ test('publish.mjs: 干跑与错误退出码——版本不符 exit 1、缺 tag e assert.equal(none.status, 2) }) test('publish.yml: 只委派脚本,断言不得在 yml 里重抄第二份', () => { - const yml = readFileSync(join(dirname(CLI), '.github', 'workflows', 'publish.yml'), 'utf8') - assert.match(yml, /release:\n\s+types: \[published\]/, '必须由 release published 触发') - assert.match(yml, /node scripts\/publish\.mjs --tag/, 'yml 只留一行委派') - assert.doesNotMatch(yml, /pack\.mjs/, 'yml 里出现 pack 调用即为第二份实现') - assert.doesNotMatch(yml, /VERSION_AUTHORITY|ASSET_NAME_CONTRACT/, '断言文案不得从脚本之外复制进 yml') + // Windows checkout 会把工作流文件转成 CRLF,断言必须先归一行尾——否则同一份 yml 在 mac/linux 绿、windows 红 + const raw = readFileSync(join(dirname(CLI), '.github', 'workflows', 'publish.yml'), 'utf8') + const forms = [['LF', raw], ['CRLF', raw.replaceAll(String.fromCharCode(10), String.fromCharCode(13, 10))]] + for (const [form, text] of forms) { + const yml = text.replaceAll(String.fromCharCode(13, 10), String.fromCharCode(10)) + assert.match(yml, /release:\n\s+types: \[published\]/, form + ': 必须由 release published 触发') + assert.match(yml, /node scripts\/publish\.mjs --tag/, form + ': yml 只留一行委派') + assert.doesNotMatch(yml, /pack\.mjs/, form + ': yml 里出现 pack 调用即为第二份实现') + assert.doesNotMatch(yml, /VERSION_AUTHORITY|ASSET_NAME_CONTRACT/, form + ': 断言文案不得从脚本之外复制进 yml') + } }) // ---- .shadow-dev/ config layers (20261005-feature-shadow-dev-config) ---- function withProjectConfig(root, content) { From 17c99db93ecebf2444740dd59d87c4f8cf491be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E7=BA=A202?= <596540@ky-tech.com.cn> Date: Mon, 12 Oct 2026 20:20:00 +0800 Subject: [PATCH 4/9] =?UTF-8?q?docs(shadow):=20brief=20=E6=9C=80=E7=BB=88?= =?UTF-8?q?=E6=80=81=E2=80=94=E2=80=94committed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../changes/20261012-build-ci-release-publish/brief.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shadow-docs/changes/20261012-build-ci-release-publish/brief.md b/shadow-docs/changes/20261012-build-ci-release-publish/brief.md index c4b748b..060262c 100644 --- a/shadow-docs/changes/20261012-build-ci-release-publish/brief.md +++ b/shadow-docs/changes/20261012-build-ci-release-publish/brief.md @@ -4,7 +4,7 @@ "name": "20261012-build-ci-release-publish", "type": "build", "scope": "ci", - "status": "published", + "status": "committed", "baseBranch": "main", "branch": "build/20261012-build-ci-release-publish", "files": [ @@ -27,7 +27,7 @@ }, "workflow": { "operation": null, - "checkpoint": "pr:67", + "checkpoint": "995f88de1f798c42b104ad3790393fe37b8b124c", "planHash": "c8e9d314edce80d943e42d304902c78ffc67c4081c5a83d2d6c617a2049ca65e", "updatedAt": null, "lastError": null, From a7eb74b6fee9b792310e683db26dbfa82ff74c7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E7=BA=A202?= <596540@ky-tech.com.cn> Date: Mon, 12 Oct 2026 20:20:09 +0800 Subject: [PATCH 5/9] =?UTF-8?q?docs(shadow):=20brief=20=E6=9C=80=E7=BB=88?= =?UTF-8?q?=E6=80=81=E2=80=94=E2=80=94published,=20PR=20#67?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../changes/20261012-build-ci-release-publish/brief.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shadow-docs/changes/20261012-build-ci-release-publish/brief.md b/shadow-docs/changes/20261012-build-ci-release-publish/brief.md index 060262c..fc6d21d 100644 --- a/shadow-docs/changes/20261012-build-ci-release-publish/brief.md +++ b/shadow-docs/changes/20261012-build-ci-release-publish/brief.md @@ -4,7 +4,7 @@ "name": "20261012-build-ci-release-publish", "type": "build", "scope": "ci", - "status": "committed", + "status": "published", "baseBranch": "main", "branch": "build/20261012-build-ci-release-publish", "files": [ @@ -27,8 +27,8 @@ }, "workflow": { "operation": null, - "checkpoint": "995f88de1f798c42b104ad3790393fe37b8b124c", - "planHash": "c8e9d314edce80d943e42d304902c78ffc67c4081c5a83d2d6c617a2049ca65e", + "checkpoint": "pr:67", + "planHash": "916d9b254d2a82bd1faac49aa089d2ffca4e670dc32cb27543a1bf53ef98cc2f", "updatedAt": null, "lastError": null, "issuePlan": { From 5855a704c63b3d143d5817f5a50b2e9ba61617d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E7=BA=A202?= <596540@ky-tech.com.cn> Date: Mon, 12 Oct 2026 20:26:13 +0800 Subject: [PATCH 6/9] =?UTF-8?q?build(ci):=20=E8=A1=8C=E5=B0=BE=E5=BD=92?= =?UTF-8?q?=E4=B8=80=E8=A1=A5=E7=AC=AC=E4=BA=8C=E6=AD=A5=E8=A3=B8=20CR=20?= =?UTF-8?q?=E6=AE=8B=E7=95=99=E2=80=94=E2=80=94=E4=BF=AE=20windows=20job?= =?UTF-8?q?=20=E4=BA=8C=E6=AC=A1=E5=81=87=E7=BA=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../changes/20261012-build-ci-release-publish/brief.md | 7 +++++-- test/cli.test.mjs | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/shadow-docs/changes/20261012-build-ci-release-publish/brief.md b/shadow-docs/changes/20261012-build-ci-release-publish/brief.md index fc6d21d..7845595 100644 --- a/shadow-docs/changes/20261012-build-ci-release-publish/brief.md +++ b/shadow-docs/changes/20261012-build-ci-release-publish/brief.md @@ -28,7 +28,7 @@ "workflow": { "operation": null, "checkpoint": "pr:67", - "planHash": "916d9b254d2a82bd1faac49aa089d2ffca4e670dc32cb27543a1bf53ef98cc2f", + "planHash": "4acf1e08c2e196594b5701a7390a895d7aadda7e1c62615fdce3ec828cc67850", "updatedAt": null, "lastError": null, "issuePlan": { @@ -57,7 +57,7 @@ "shadow-docs/changes/20261012-build-ci-release-publish/brief.md", "test/cli.test.mjs" ], - "message": "build(ci): 工作流断言按 LF/CRLF 双形态判定——修 windows 矩阵假红" + "message": "build(ci): 行尾归一补第二步裸 CR 残留——修 windows job 二次假红" } }, "knowledge": { @@ -109,5 +109,8 @@ CLI 仓只有 `test.yml`,**没有发布工作流**。`gh release create v1.6.0 - 单实现防回归:新增用例断 `publish.yml` 必须含 `node scripts/publish.mjs --tag` 且**不得**出现 `pack.mjs` / `VERSION_AUTHORITY` / `ASSET_NAME_CONTRACT` —— 断言一旦回拷进 yml 就有第二份实现,判红。 - 套件:`node --test --test-name-pattern publish.mjs` → 2 绿 0 红;`--test-name-pattern publish.yml` → 1 绿 0 红;`--test-name-pattern publish` 全组(含既有 publish 域用例)无回归。 - 门槛:**CI 抓到一个我本地没抓到的跨平台缺陷**——`test (windows-latest, 20/22/24)` 三条判红,原因是 `publish.yml` 断言写成 `/release:\n\s+types: \[published\]/` 而 Windows checkout 把工作流文件转成 CRLF,`\n` 匹配不上(mac/ubuntu 绿、windows 红)。mac/linux 20/22/24 六条全绿。 +- 第一轮修复不彻底(同一 job 再红):Windows checkout 本就是 CRLF,我在测试里又叠一层换行转换,于是产出 CR CR LF;归一只吃掉一对 CR+LF,残留的裸 CR 紧贴在换行前,`/release:/` 后接换行的断言依旧不匹配。报错文案 `CRLF: 必须由 release published 触发` 即此。 +- 最终修复:归一分两步——先把 CRLF 转 LF,再把残留裸 CR 也转 LF。本地四形态自测(LF / CRLF / 裸 CR / CR+CR+LF)归一后全部匹配,未归一时 CRLF 与裸 CR 形态都不匹配,证明断言确实在验归一而非蒙对。 +- 教训留痕:跨平台行尾断言必须先归一再判,且叠加形态要防双重 CR;只测 LF 与想象中的 CRLF,在 Windows 上会假失败。 - 修复:断言改为对 **LF 与 CRLF 两种行尾形态**分别归一后判定,使该缺陷在非 Windows 机器上也可复现(不再依赖只有 CI 才有的 CRLF 检出)。本地 `--test-name-pattern publish.yml` → 1 绿 0 红。 - **未覆盖项(明说)**:`upload` 与 `read-back` 两步只在本 PR 合并、下一次真实 `gh release create` 触发 `publish-release` 后才被端到端执行;本轮无法自证(v1.6.0 的资产是我手工补的,正是本 change 要消除的动作)。验证点为:Actions 上 `publish-release` 绿 + `releases/tags/<新 tag>` 的 `assets` 恰有一个合法名资产。 diff --git a/test/cli.test.mjs b/test/cli.test.mjs index 545f0fc..0e840b2 100644 --- a/test/cli.test.mjs +++ b/test/cli.test.mjs @@ -1242,7 +1242,8 @@ test('publish.yml: 只委派脚本,断言不得在 yml 里重抄第二份', () const raw = readFileSync(join(dirname(CLI), '.github', 'workflows', 'publish.yml'), 'utf8') const forms = [['LF', raw], ['CRLF', raw.replaceAll(String.fromCharCode(10), String.fromCharCode(13, 10))]] for (const [form, text] of forms) { - const yml = text.replaceAll(String.fromCharCode(13, 10), String.fromCharCode(10)) + // 先 CRLF→LF,再把残留裸 CR 归一:Windows checkout 本就是 CRLF,测试叠加一层会产出 CR CR LF,只替换一对会残留裸 CR 而漏判 + const yml = text.replace(/\r\n/g, String.fromCharCode(10)).replace(/\r/g, String.fromCharCode(10)) assert.match(yml, /release:\n\s+types: \[published\]/, form + ': 必须由 release published 触发') assert.match(yml, /node scripts\/publish\.mjs --tag/, form + ': yml 只留一行委派') assert.doesNotMatch(yml, /pack\.mjs/, form + ': yml 里出现 pack 调用即为第二份实现') From 2d2e050c10532b929c2bb56b407cef9a481605e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E7=BA=A202?= <596540@ky-tech.com.cn> Date: Mon, 12 Oct 2026 20:26:14 +0800 Subject: [PATCH 7/9] =?UTF-8?q?docs(shadow):=20brief=20=E6=9C=80=E7=BB=88?= =?UTF-8?q?=E6=80=81=E2=80=94=E2=80=94committed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../changes/20261012-build-ci-release-publish/brief.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shadow-docs/changes/20261012-build-ci-release-publish/brief.md b/shadow-docs/changes/20261012-build-ci-release-publish/brief.md index 7845595..564cf78 100644 --- a/shadow-docs/changes/20261012-build-ci-release-publish/brief.md +++ b/shadow-docs/changes/20261012-build-ci-release-publish/brief.md @@ -4,7 +4,7 @@ "name": "20261012-build-ci-release-publish", "type": "build", "scope": "ci", - "status": "published", + "status": "committed", "baseBranch": "main", "branch": "build/20261012-build-ci-release-publish", "files": [ @@ -27,7 +27,7 @@ }, "workflow": { "operation": null, - "checkpoint": "pr:67", + "checkpoint": "5855a704c63b3d143d5817f5a50b2e9ba61617d2", "planHash": "4acf1e08c2e196594b5701a7390a895d7aadda7e1c62615fdce3ec828cc67850", "updatedAt": null, "lastError": null, From e861bfa00a3942659d093082f1bd7f7ab5444886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E7=BA=A202?= <596540@ky-tech.com.cn> Date: Mon, 12 Oct 2026 20:26:22 +0800 Subject: [PATCH 8/9] =?UTF-8?q?docs(shadow):=20brief=20=E6=9C=80=E7=BB=88?= =?UTF-8?q?=E6=80=81=E2=80=94=E2=80=94published,=20PR=20#67?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../changes/20261012-build-ci-release-publish/brief.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shadow-docs/changes/20261012-build-ci-release-publish/brief.md b/shadow-docs/changes/20261012-build-ci-release-publish/brief.md index 564cf78..ba00371 100644 --- a/shadow-docs/changes/20261012-build-ci-release-publish/brief.md +++ b/shadow-docs/changes/20261012-build-ci-release-publish/brief.md @@ -4,7 +4,7 @@ "name": "20261012-build-ci-release-publish", "type": "build", "scope": "ci", - "status": "committed", + "status": "published", "baseBranch": "main", "branch": "build/20261012-build-ci-release-publish", "files": [ @@ -27,8 +27,8 @@ }, "workflow": { "operation": null, - "checkpoint": "5855a704c63b3d143d5817f5a50b2e9ba61617d2", - "planHash": "4acf1e08c2e196594b5701a7390a895d7aadda7e1c62615fdce3ec828cc67850", + "checkpoint": "pr:67", + "planHash": "916d9b254d2a82bd1faac49aa089d2ffca4e670dc32cb27543a1bf53ef98cc2f", "updatedAt": null, "lastError": null, "issuePlan": { From 2063e41db67e7781b5bce6ad1cc41581c4563de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E7=BA=A202?= <596540@ky-tech.com.cn> Date: Mon, 12 Oct 2026 20:33:31 +0800 Subject: [PATCH 9/9] =?UTF-8?q?docs(shadow):=20brief=20=E6=9C=80=E7=BB=88?= =?UTF-8?q?=E6=80=81=E2=80=94=E2=80=94published,=20PR=20#67?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shadow-docs/changes/20261012-build-ci-release-publish/brief.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadow-docs/changes/20261012-build-ci-release-publish/brief.md b/shadow-docs/changes/20261012-build-ci-release-publish/brief.md index ba00371..b6ceb6d 100644 --- a/shadow-docs/changes/20261012-build-ci-release-publish/brief.md +++ b/shadow-docs/changes/20261012-build-ci-release-publish/brief.md @@ -28,7 +28,7 @@ "workflow": { "operation": null, "checkpoint": "pr:67", - "planHash": "916d9b254d2a82bd1faac49aa089d2ffca4e670dc32cb27543a1bf53ef98cc2f", + "planHash": "1c2284309b65ac863374831b87ed2dbea21fefac76f3d15734e201ba1a0234bd", "updatedAt": null, "lastError": null, "issuePlan": {