Merge pull request #2489 from deepseek-harness/fix/publish-retry-idempotence

fix(release): make publication retry, space out, and skip what already landed
This commit is contained in:
imccyu
2026-08-13 15:42:39 +08:00
committed by GitHub
11 changed files with 269 additions and 25 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-10-npm-release-sequences.md
2026-08-10-npm-release-sequences.md: e8138aef923e201cc0883232f48ce6921452ca00
2026-08-10-npm-release-sequences.zh.md: 7ce9fa01dbe10cbdae5585f64392d0ca6a9fb868
2026-08-10-npm-release-sequences.md: e74a4ac8f2aadd8665ec0db198c6a317a0c201bc
2026-08-10-npm-release-sequences.zh.md: e152163976f945224f2524fccd7f831ba98e8161

View File

@@ -70,6 +70,10 @@ Publication runs only from GitHub Actions; there is no local publication path. P
The third state catches code that changed without a version bump. The first two provide idempotence — re-running publish over one artifact republishes nothing and needs no manual selection of packages. The same rule resolves the tension between one vendor release carrying several tags and a workflow that can only run from one ref: the workflow never infers which packages to publish from the tag it ran from.
All three sequences decide this way, including the native one: it publishes through its own script rather than a shell loop, because a loop of bare `npm publish` calls cannot be retried — the registry answers a repeat of an existing version permanently, so one failure partway through left no way forward.
Two registry behaviours shape how a publish is attempted. Writes are spaced by at least two seconds and retried with a backoff, because publishing several packages back to back outruns the registry's own processing and earns `E409 Failed to save packument`. And every retry re-reads the registry first: a reported failure can answer a write that landed anyway, so a version that now exists with this tarball's integrity counts as published rather than as a version to place again.
### Workspace-internal references use the `workspace:` protocol
Every reference to a workspace member uses `workspace:^`, so `pnpm pack` substitutes a range matching the target version: sibling `peerDependencies` follow the family version, and a reference to a vendored package follows that package's own line. The Landlock platform packages keep `workspace:*`, which publishes the exact version, because a platform package and its entry must agree exactly.

View File

@@ -70,6 +70,10 @@ tag 只是 commit 指针不是发布成功的证明。bump 会向 registry
第三态拦住「改了代码却没 bump 版本」。前两态给出幂等——同一个 artifact 重跑 publish 不会重复发布,也不需要人工挑拣包。同一条规则还解决了「一次 vendor 发布携带多个 tag而 workflow 只能从一个 ref 触发」的矛盾workflow 从不从触发它的 tag 去推断该发哪些包。
三条序列都按这套判定native 也在内:它通过自己的脚本发布,而不是 shell 循环——一串裸 `npm publish` 无法重试registry 对「重发已存在的版本」的回答是永久失败,因此中途失败一次就没有前路了。
registry 的两个行为决定了「怎么尝试一次发布」。写入之间至少间隔两秒并带退避重试,因为连续背靠背发多个包会超出 registry 自身的处理速度,换来 `E409 Failed to save packument`。而每次重试都先重查 registry报出来的失败可能对应一次其实已经落地的写入所以「该版本现在存在且 integrity 与本 tarball 相同」算作已发布,而不是又一个待放置的版本。
### workspace 内部引用走 `workspace:` 协议
所有指向 workspace 成员的引用都用 `workspace:^`,由 `pnpm pack` 替换成匹配目标版本的范围:兄弟包的 `peerDependencies` 跟随族版本,指向 vendored 包的引用跟随那个包自己的版本线。Landlock 平台包保留 `workspace:*`(发布成精确版本),因为平台包与它的入口必须版本完全一致。