DSH / Atlas
2026-08-04proposedprocess

Artifact-first NPM baseline publication

以产物为先的 NPM 基线发布

Runnable source in the monorepo does not prove that published packages are runnable. Workspace links, TypeScript paths, tsx source loading, and residual `lib/` files in the working tree can supply files or dependencies that are absent from a published tarball. Existing built-artifact tests still read `lib/` directly from the working tree, so they do not verify what `package.json#files` selects or the layout produced

English

Problem

Runnable source in the monorepo does not prove that published packages are runnable. Workspace links, TypeScript paths, tsx source loading, and residual lib/ files in the working tree can supply files or dependencies that are absent from a published tarball. Existing built-artifact tests still read lib/ directly from the working tree, so they do not verify what package.json#files selects or the layout produced by package-manager installation. An execution that succeeds in development mode can therefore be published without a required bundle chunk, declaration, configuration file, or asset.

Publishing many mutually dependent @deepseek-ai packages also creates a set-consistency problem. If a script publishes each package immediately after packing it, a later pack or validation failure leaves the first part of an unusable baseline in the registry. The npm registry has no cross-package transaction, so “publish once” cannot promise an atomic commit. It can promise that the complete publication set is packed and validated before any remote write, then published from that immutable set by one resumable orchestration command.

The current baseline also requires a person to derive versions, authenticate, pack, publish, and retry from a local machine. A later GitHub Actions workflow must reuse the same release bundle and validation logic. It must not rebuild a different, untested set of tarballs after publication is approved.

Proposal

The publication flow uses an immutable release bundle as its boundary. The pack phase builds every target package from one fixed Git commit, creates every tarball, validates tarball contents, and passes an installed-artifact integration test. The publish phase reads only those tarballs and their manifest and is forbidden from rebuilding or repacking.

The target set contains only @deepseek-ai/* workspace packages discovered from packages/*/*/package.json and apps/*/package.json. The root project, website/, vendor, Python, and native workspaces are outside this NPM baseline. Discovery must reject duplicate names, mixed base versions, an unexpected publication privacy state, and unknown packages in the bundle instead of relying on another hand-maintained package-name list.

The prerelease version consists of the package stable base version, a second-precision UTC timestamp captured when the command starts, and the target commit's 10-character short SHA: <base>-<YYYYMMDDHHmmss>-<short-commit>. The dist-tag is derived as dev-<base>. For example, base 0.0.1, time 2026-08-04T00:32:00Z, and commit 909292dd7b produce version 0.0.1-20260804003200-909292dd7b and tag dev-0.0.1. Retrying one release bundle must retain its version and manifest; repacking creates a version from the new command start time.

The pack phase runs in this order:

  1. Resolve the ref to an immutable commit, capture the UTC timestamp, derive the version from that commit's root manifest, and display the commit, timestamp, version, tag, registry, and output path. Both pack and release wait for Enter at this point before expensive work; --yes skips this confirmation for automation.
  2. Install the frozen lockfile in an isolated detached worktree and run source-manifest publication constraints before staging. Uncommitted files and old build output from the caller's working tree must not affect publication.
  3. Stage every target manifest with the derived version, remove its publication-time private marker, and rewrite internal workspace dependencies in dependencies, devDependencies, optionalDependencies, and peerDependencies to the same exact version.
  4. Build the target commit completely, then run publint and built-package invariants.
  5. Pack every package in the target set without performing a registry write.
  6. Inspect each tarball's package manifest, file inventory, internal dependency versions, name, and version, rejecting missing, duplicate, or extra tarballs.
  7. Generate a release manifest and checksums containing the commit, version, tag, registry, and each package's tarball path, SHA-256, and npm integrity.
  8. Install an isolated consumer from the local tarballs and run the installed-artifact probes available in the current implementation; expand those probes to the complete artifact-plane integration matrix defined below.
  9. Print one directly executable publish command only after the complete set passes. The pack command itself always remains free of remote writes.

The local release command composes pack and publish. It first uses the pack confirmation above to fix the expected timestamp and version, then waits for Enter again after a successful pack before publishing the same manifest; release --yes skips both confirmations. Separate pack and publish --manifest operations remain the primitives used by split CI jobs and recovery.

Current implementation boundary

The checked-in pack command implements fixed-commit staging, exact internal dependency pins, static and tarball payload checks, the immutable manifest, and an isolated npm installation with every release tarball as a local top-level dependency. It runs the installed dsh --version and dsh --dump-default-config entries under plain Node, then starts the installed default TUI in a POSIX PTY, waits for its main-session- ready signal, and exits through /exit before printing the publish command. Publish supports integrity-based resumption, separates read-only registry verification from the authenticated identity check, and finishes with a complete remote integrity and dist-tag verification pass.

Pull-request CI does not invoke the pack command; the installed-entry probes are local release checks rather than merge gates. Credential-free CI execution, package-owned probes for every other bin and public runtime entry, workflow-artifact transfer, and the protected publication job remain proposal scope.

Publication payload contract

Published packages contain only build artifacts required by consumers. package.json#files must not include src or lib/types/**/*.d.ts.map; an independent tarball-content gate must also confirm that no package/src/** or package/**/*.d.ts.map entry exists, preventing manifest patterns or pack behavior from bypassing the static constraint. Runtime JavaScript, .d.ts declarations, configuration, assets, worker files, and dynamic bundle chunks must cover the actual entrypoint closure.

Source manifests may retain exports["./src/*"] for this repository's source-plane resolution. That export does not place source in the publication payload and is not a consumer contract of the published package. Static gates must check the source plane and publication payload separately: deleting the source export must not hide broken workspace resolution, and publishing src must not repair missing build artifacts.

Every tarball must be free of workspace: specifiers, and every internal dependency and peer dependency that points into the publication set must equal the exact derived version; ^, ~, and other semver ranges must not cross commit baselines. Except for exports["./src/*"], which is explicitly source-plane-only, every consumer entry declared by the package manifest must point to a file present in the tarball. Dynamic imports, runtime-computed paths, and non-exported assets cannot be validated solely from the manifest and require installed execution.

Artifact-plane integration test

The integration test runs after all tarballs exist and before any publish. It creates a fresh temporary project outside the monorepo, installs the declared dependency closure through local .tgz files from the release manifest, and executes from that installation. The test must use plain Node and package-manager-produced node_modules; tsx, tsconfig paths, workspace links, repository source paths, working-tree lib/, and the same version from the published registry are forbidden resolution inputs. The test also asserts that critical modules and bins resolve to real paths inside the temporary consumer.

Installation uses the client behavior selected for this publication. Registry uploads must use the npm CLI because the private registry accepts only the npm client; pnpm may still orchestrate builds. Tarball tests must neither publish these packages to the real registry first nor repack after testing.

The test covers at least these execution surfaces:

  • Installed @deepseek-ai/dsh runs dsh --version and dsh --dump-default-config successfully under plain Node, covering the static CLI entry and one dynamic mode entry.
  • Installed default dsh completes one keyless TUI startup in a PTY and exits under test control after reaching a defined ready signal. This path must load the real TUI dynamic chunk, so a missing publication file such as lib/tui-*.js fails the gate.
  • Every other published bin defines a package-owned smoke command that neither reaches a real service nor modifies user state. Different CLIs are not forced to share --help; the test runs the actual installed entry and checks its agreed exit or ready signal.
  • Node-compatible public runtime entries load from the installation. Browser, worker, or host-protocol-only entries use matching isolated fixtures, but their inputs must still be the current tarballs exclusively.

These tests prove executability; they do not replace unit tests, snapshots, real-API e2e, or publint. Test fixtures should reuse behavior assertions from existing built-bin and PTY scenarios while changing the entry to the tarball installation. A test that runs working-tree lib/bin.js directly does not satisfy this gate.

Publication and recovery

The publish command validates the release manifest, every local checksum, the target registry, npm ping, and npm whoami before uploading tarballs in a deterministic order. It accepts only a manifest created by the pack phase, never workspace directories. The default registry is https://registry.npm.harnessment.com/; every publish passes the registry and derived tag explicitly so a user-level .npmrc cannot redirect the operation.

npm provides no multi-package atomic transaction, so uploads still occur package by package. The orchestrator reduces the failure surface through idempotent recovery: upload when remote <name>@<version> does not exist; skip when it exists with the release manifest's integrity; fail immediately when it exists with different content. Dist-tag inspection reads tag assignments without resolving a default tag's target, so an unrelated dangling tag cannot block recovery. At completion it must confirm every package version's integrity and every dist-tag against the release version. The workflow reports success only when the complete set passes final verification.

If pack, tarball inspection, or installed-artifact integration fails, the registry must receive zero writes. If publish fails after a partial upload, the operator reruns publish with the same release manifest and must not repack into another timestamped version instead of recovering. Only a code or build-input change that requires different tarballs reruns the complete pack and test flow.

GitHub Actions integration

GitHub Actions separates a credential-free pack-and-test job from a protected publish job. The first checks out the exact commit, invokes the same pack entry used locally, runs tarball-consumer tests, and uploads the complete release bundle as a workflow artifact. The second depends on the first, downloads that workflow artifact, revalidates the manifest and checksums, and invokes the same publish entry. It cannot rebuild after checkout.

Pull requests and ordinary pushes may run the credential-free pack-and-test signal so payload regressions surface before merge. Actual private-registry publication starts as workflow_dispatch with only a target ref as input; the pack job creates the UTC timestamp, while the base version, short SHA, tag, registry, and package inventory derive from repository state or version-controlled configuration. Stable-release triggering is outside this baseline proposal.

The registry token is injected only into the publish job, which uses a protected GitHub Environment to govern human approval, allowed branches or tags, and concurrency. The pack-and-test job cannot read publication credentials. The workflow artifact may have a short retention period, but the publish job must use the bundle produced by the same workflow run instead of locating tarballs from an untrusted source by version.

Alternatives considered

Publish recursively from the workspace. Rejected because it interleaves packing and registry writes, cannot prove the complete set before the first write, and allows workspace resolution and caller working-tree state to influence publication.

Test only built lib/ in the working tree. Rejected because that validates the build tree rather than the tarball selected by package.json#files. A dynamic chunk present in the working tree but omitted from the tarball is exactly the failure this proposal must catch.

Run only dsh --help. Rejected because Commander can print help and exit before loading the TUI, Web, or headless dynamic entry. It does not prove the default production startup path is complete.

Publish src and declaration maps to reduce missing-file risk. Rejected because the source plane is not a production-runtime fallback. Expanding the payload hides bundle-closure errors and turns local debugging outputs into accidental publication contracts.

Require truly atomic cross-package publication. Rejected because the npm registry has no such transaction. An immutable release bundle, complete pre-publication validation, integrity comparison, and idempotent recovery provide an implementable boundary while retaining the explicit limitation that partial uploads can be briefly visible.

Rebuild in the publish job after approval. Rejected because the tested and uploaded tarballs would lose content identity. The workflow artifact and checksums must carry the test inputs directly into publication.

Acceptance criteria

  • One pack entry discovers every target under packages/*/* and apps/* from a fixed commit, derives and displays a version from the UTC second and short commit before waiting for Enter, generates the complete release bundle before any registry write, and prints one copyable publish command; release waits again after packing, while --yes skips both confirmations.
  • Static manifest and tarball-content gates both reject published src and .d.ts.map while source manifests retain exports["./src/*"].
  • The release bundle records the complete package set, commit, derived version, tag, registry, and per-tarball integrity; every internal dependency is pinned exactly to that version, and publish consumes only that bundle without rebuilding.
  • An isolated integration test installs from local tarballs and starts the installed default dsh TUI under plain Node; deleting any required dynamic chunk makes the test fail deterministically.
  • Every published bin and applicable public runtime entry has post-tarball-install execution coverage, with resolution paths proving that no monorepo fallback occurred.
  • Publish safely resumes from the same manifest after partial success: matching integrity is skipped, conflicting integrity is rejected, and final verification requires every version and tag to agree.
  • A credential-free GitHub Actions job creates and tests the bundle, a protected job uploads the identical bundle, and the publication token exists only in the latter.

Risks

Full packing, installation, and startup add CI time and workflow-artifact volume. The implementation should cache external dependencies and the pnpm store, but it must not cache or reuse installed workspace output for target packages. Safe consumer probes can run in parallel to reduce latency.

Installing every tarball as a temporary project's top-level dependency can hide an undeclared internal dependency. The test generator should install each tested application's declared recursive closure and retain existing dependency gates. For @deepseek-ai/dsh, whose dependency surface approaches the full set, package-manifest and static graph checks remain necessary to detect undeclared edges.

Platform-specific optional dependencies, native addons, PTYs, and browser entries may require platform-owned probes. The first phase must cover primary dsh startup on the publication Linux runner and one local macOS path, then expand the matrix with the actual publication platforms. Skipping an unstable probe must not move a production path outside the gate.

Recovery cannot remove npm's partial visibility. During a failed publication, the registry may briefly contain only some package versions from the bundle. Operators and automation must treat the final bundle verification, not one successful npm publish, as the baseline-availability signal.

中文

问题

monorepo 中可运行的源码并不能证明发布后的包可运行。workspace link、TypeScript paths、tsx 源码加载和工作树里残留的 lib/ 都可能补上发布 tarball 中缺失的文件或依赖。即使现有构建产物测试使用普通 Node,它仍直接读取工作树中的 lib/,没有验证 package.json#files 最终选中了什么,也没有验证包管理器安装后的文件布局。一次开发模式正常的执行因此可能发布为缺少 bundle chunk、声明文件、配置或资源的包。

发布多个互相依赖的 @deepseek-ai 包还会产生集合一致性问题。如果脚本每 pack 一个包就立即 publish,那么后续 pack 或验证失败时,注册表中已经存在无法作为完整基线使用的前半组版本。npm 注册表没有跨包事务,因此这里的「一次性发布」不能承诺原子提交,只能承诺在任何远端写入前完整生成并验证发布集合,再由一个可恢复的编排命令发布这个不可变集合。

当前基线还需要人工在本机完成版本派生、认证、pack、发布和重试。后续 GitHub Actions 工作流必须复用同一套发布包与验证逻辑,不能在批准发布后重新构建另一组未经消费方测试的 tarball。

提案

发布流程以一个不可变的 release bundle(发布包集合)为边界。pack 阶段从一个确定的 Git commit 构建全部目标包、生成全部 tarball、检查 tarball 内容,并通过安装后集成测试;publish 阶段只读取这组 tarball 及其 manifest(元数据清单),禁止重建或重新 pack。

目标集合只包含 packages/*/*/package.jsonapps/*/package.json 中命名为 @deepseek-ai/* 的 workspace 包。根项目、website/、vendor、Python 与 native workspace 不属于该 NPM 基线。发现机制必须拒绝重复包名、不同基础版本、意外的 private 发布状态以及集合中的未知包,而不是维护另一份手工包名列表。

预发布版本由包的稳定基础版本、命令启动时精确到秒的 UTC 时间戳和目标 commit 的 10 位短 SHA 组成:<base>-<YYYYMMDDHHmmss>-<short-commit>。dist-tag 由基础版本派生为 dev-<base>。例如,基础版本 0.0.1、时间 2026-08-04T00:32:00Z 和 commit 909292dd7b 生成版本 0.0.1-20260804003200-909292dd7b 与 tag dev-0.0.1。同一 release bundle 的重试必须沿用原版本和 manifest;重新 pack 会按新的命令启动时间生成新版本。

pack 阶段按以下顺序执行:

  1. 将 ref 解析成不可变 commit,采集 UTC 时间戳,从该 commit 的根 manifest 派生版本,并显示 commit、时间戳、版本、tag、注册表和输出路径。packrelease 此时都会在昂贵操作开始前等待 Enter;自动化可用 --yes 跳过该确认。
  2. 在隔离的 detached worktree 中安装 frozen lockfile,并在暂存前运行源码 manifest 发布约束;调用方工作树中的未提交文件和旧构建输出不得参与发布。
  3. 将所有目标 manifest 暂存为派生版本,移除发布时的 private 标记,并把 dependenciesdevDependenciesoptionalDependenciespeerDependencies 中的内部 workspace 依赖全部改写为同一精确版本。
  4. 完整构建目标 commit,再运行 publint 和已构建包不变式。
  5. 为目标集合中的每个包执行 pack,但不执行任何注册表写入。
  6. 检查 tarball 内的 package manifest、文件清单、内部依赖版本、包名和版本,并拒绝缺失、重复或额外的 tarball。
  7. 生成包含 commit、版本、tag、注册表、每个包的 tarball 路径、SHA-256 与 npm integrity 的 release manifest 和校验和文件。
  8. 从本地 tarball 安装一个隔离消费方,运行当前实现已有的安装态产物探测,并将这些探测扩展为下文定义的完整产物平面集成测试矩阵。
  9. 仅当整个集合通过时输出一个可直接执行的 publish 命令;pack 命令本身始终保持无远端写入。

本地 release 命令组合 pack 与 publish。它先通过上述 pack 确认确定预期时间戳和版本,再在 pack 成功后等待第二次 Enter,随后发布同一 manifest;release --yes 跳过两次确认。独立的 packpublish --manifest 仍是 CI 分 job 和断点恢复使用的基础操作。

当前实现边界

已提交的 pack 命令实现了固定 commit 暂存、内部依赖精确固化、静态与 tarball payload 检查、不可变 manifest,以及把每个发布 tarball 都作为本地顶层依赖的隔离 npm 安装。它在输出 publish 命令前,用普通 Node 运行安装后的 dsh --versiondsh --dump-default-config 入口,再在 POSIX PTY 中启动安装后的默认 TUI,等待其 main-session- 就绪信号,并通过 /exit 退出。Publish 支持按 integrity 恢复,将只读注册表验证与认证身份检查分离,并以完整的远端 integrity 和 dist-tag 验证结束。

PR(Pull Request) CI 不会调用 pack 命令;安装态入口探测属于本地发布检查,而不是合并门禁。免凭据 CI 执行、其他每个 bin 与公开运行时入口的包自有探测、workflow artifact 传递及受保护 publish job 仍属于提案范围。

发布 payload 约定

发布包只携带消费方需要的构建产物。package.json#files 禁止包含 srclib/types/**/*.d.ts.map;tarball 内容门禁还要独立确认不存在任何 package/src/**package/**/*.d.ts.map,避免 manifest pattern 或 pack 行为绕过静态约束。运行时 JS、声明文件 .d.ts、配置、资源、worker 文件和 bundle 动态 chunk 必须按实际入口闭包收齐。

源码 manifest 可以保留 exports["./src/*"],供本仓库的源码平面解析使用;该 export 不代表源码会进入发布 payload,也不属于已发布包的消费方约定。静态门禁必须分别检查源码平面与发布 payload,不能通过删除 source export 来掩盖错误的 workspace 解析,也不能通过发布 src 来修补缺失的构建产物。

每个 tarball 必须不含 workspace: specifier,并且所有指向本次发布集合的内部依赖与对等依赖(peer dependency)都必须精确等于本次派生版本,禁止使用 ^~ 或其他 semver 范围跨越 commit 基线。除了明确仅供源码平面使用的 exports["./src/*"],package manifest 中声明的每个消费方入口都必须指向 tarball 内存在的文件;动态 import、运行时拼接路径和非 export 资源不能只靠 manifest 检查,必须由安装后执行覆盖。

产物平面集成测试

集成测试在全部 tarball 生成后、任何 publish 之前运行。它在 monorepo 外创建一个全新临时项目,通过本次 release manifest 中的本地 .tgz 文件安装声明依赖闭包,并从安装目录执行。测试必须使用普通 Node 与包管理器生成的 node_modules;禁止 tsx、tsconfig paths、workspace link、仓库源码路径、工作树 lib/ 和已发布注册表中的同版本包参与解析。测试还要断言关键模块与 bin 的真实路径位于临时消费方内。

安装使用本次发布选择的客户端行为。注册表上传必须使用 npm CLI(命令行界面),以满足私有注册表只接受 npm 客户端的策略;构建编排仍可使用 pnpm。tarball 测试不得先把这些包发布到真实注册表,也不得在测试后重新 pack。

测试至少覆盖以下执行面:

  • @deepseek-ai/dsh 安装后的 dsh --versiondsh --dump-default-config 在普通 Node 下成功,分别覆盖静态 CLI 入口和一个动态模式入口。
  • 安装后的默认 dsh 在 PTY 中完成一次无密钥 TUI 启动,到达既定 ready 信号后由测试受控退出。这条路径必须加载真实 TUI 动态 chunk,因此缺少类似 lib/tui-*.js 的发布文件会使门禁失败。
  • 每个其他已发布 bin 都定义一个不会访问真实服务或修改用户状态的包级冒烟命令。不同 CLI 不强制共用 --help;测试必须运行其真实安装入口并检查约定的退出或 ready 信号。
  • Node 兼容的公开运行时入口从安装目录加载;浏览器、worker 或必须由宿主协议驱动的入口使用对应的隔离 fixture(测试前置数据),但输入仍只能是本次 tarball。

这些测试验证可执行性,不替代单元测试、快照、真实 API e2e 或 publint。测试 fixture 应复用现有 built-bin 和 PTY 场景的行为断言,但必须把入口改为 tarball 安装结果;直接运行工作树 lib/bin.js 的测试不能算作本门禁。

发布与恢复

publish 命令先验证 release manifest、所有本地校验和、目标注册表、npm pingnpm whoami,再按确定顺序上传 tarball。命令只接受 pack 阶段生成的 manifest,不接受 workspace 目录作为发布输入。默认注册表为 https://registry.npm.harnessment.com/,每次 publish 都显式传入注册表和派生 tag,避免用户级 .npmrc 改变目标。

npm 不提供多包原子事务,上传仍会逐包发生。编排器通过幂等恢复缩小失败面:若远端不存在 <name>@<version> 就上传;若已存在且 integrity 与 release manifest 相同就跳过;若已存在但内容不同就立即失败。dist-tag 检查只读取 tag 映射,不解析默认 tag 指向的版本,因此即使无关 tag 指向的版本已不存在,也不会阻断恢复。完成后必须逐包确认版本 integrity 和 dist-tag 都指向本次版本,只有整个集合通过最终验证,工作流才报告发布成功。

如果 pack、tarball 检查或安装后集成测试失败,注册表必须保持零写入。如果 publish 在部分上传后失败,操作者使用同一 release manifest 重跑 publish 命令来恢复,不得重新 pack 并生成另一个时间戳版本来代替恢复。修复代码或改变构建输入后需要不同 tarball 时,才重新执行完整 pack 与测试。

GitHub Actions 集成

GitHub Actions 分为无凭据的 pack-and-test job 与受保护的 publish job。前者检出精确 commit,调用与本地相同的 pack 入口,运行 tarball 消费方测试,并上传完整 release bundle 作为工作流产物。后者依赖前者成功,从工作流产物下载 bundle、重新校验 manifest 和校验和,再调用同一个 publish 入口;它不能检出后重新构建。

PR 与普通 push 可以运行无凭据的 pack-and-test 信号,从而在合并前发现 payload 回归。实际私有注册表发布先通过 workflow_dispatch 提供,输入只包括目标 ref;UTC 时间戳由 pack job 生成,基础版本、短 SHA、tag、注册表和包清单都由仓库状态或受版本控制的配置派生。稳定发布触发方式不在本基线提案范围内。

注册表 token 只注入 publish job,并由受保护 GitHub Environment 控制人工批准、允许的分支或 tag 以及并发。pack-and-test job 不得读取发布凭据。工作流产物的保留期可以较短,但 publish job 必须使用同一 workflow run 生成的 bundle,不能按版本号从不受信任的位置寻找 tarball。

考虑过的替代方案

从 workspace 直接递归 publish。 不采用,因为命令会把 pack 与注册表写入交错,无法在第一次写入前证明整个集合完整,也容易让 workspace 解析与调用方工作树状态影响发布结果。

只测试工作树中构建后的 lib/ 不采用,因为这验证的是构建树,不是 package.json#files 选出的 tarball。工作树中存在而 tarball 中漏掉的动态 chunk 正是本提案必须捕获的失败。

只运行 dsh --help 不采用,因为 Commander 可以在加载 TUI、Web 或 headless 动态入口之前输出帮助并退出。它无法证明默认生产启动路径完整。

src 和声明映射一起发布以降低漏文件风险。 不采用,因为源码平面不是生产运行时的后备路径;扩大 payload 会掩盖 bundle 闭包错误,并把本地调试产物变成无意的发布约定。

要求真正的跨包原子发布。 不采用,因为 npm 注册表没有相应事务。不可变 release bundle、发布前全量验证、integrity 比对与幂等恢复提供可实现的边界,同时明确保留部分上传短暂可见的限制。

在批准后由发布 job 重新构建。 不采用,因为测试通过的 tarball 与实际上传的 tarball 将不再具有内容身份。工作流产物与校验和必须把测试输入直接传给发布步骤。

验收标准

  • 一个 pack 入口从确定 commit 发现 packages/*/*apps/* 的全部目标包,以 UTC 秒级时间戳与短 commit 生成并显示版本,再等待 Enter;它在任何注册表写入前生成完整 release bundle,并输出一个可复制的 publish 命令;release 在 pack 后再次等待,--yes 跳过两次确认。
  • 静态 manifest 门禁和 tarball 内容门禁都拒绝发布 src.d.ts.map,同时保留源码 manifest 中的 exports["./src/*"]
  • release bundle 记录完整包集合、commit、派生版本、tag、注册表和逐 tarball integrity;所有内部依赖都精确固化到该版本,publish 只消费该 bundle,绝不重建。
  • 一个隔离集成测试从本地 tarball 安装消费方,并用普通 Node 启动安装后的默认 dsh TUI;删除任一所需动态 chunk 会使该测试稳定失败。
  • 所有已发布 bin 和适用的公开运行时入口都有 tarball 安装后的执行覆盖,且解析路径证明没有回退到 monorepo。
  • publish 可在部分成功后用同一 manifest 安全重跑;相同 integrity 被跳过,不同 integrity 被拒绝,最终验证要求所有版本与 tag 一致。
  • GitHub Actions 的无凭据 job 生成并测试 bundle,受保护 job 上传完全相同的 bundle,发布 token 只存在于后者。

风险

全量 pack、安装和启动会增加 CI 时间与工作流产物体积。实现应缓存外部依赖和 pnpm store,但不得缓存或复用目标包的已安装 workspace 输出;并行执行安全的消费方 probe 可以降低时延。

把所有 tarball 都安装为临时项目的顶层依赖可能掩盖未声明的内部依赖。测试生成器应按被测应用的声明式递归闭包安装,并结合现有依赖门禁;对依赖面接近全集的 @deepseek-ai/dsh,仍需依靠 package manifest 与静态图检查发现未声明边。

不同平台的 optional dependency、native addon、PTY 与浏览器入口可能需要平台专属 probe。第一阶段至少在发布所用 Linux runner 和一个本地 macOS 路径上覆盖主 dsh 启动,后续矩阵按实际发布平台扩展;不能用跳过不稳定 probe 的方式把生产路径移出门禁。

恢复机制不能消除 npm 的部分可见性。发布失败期间,注册表可能短暂含有本次版本的一部分包;操作者与自动化必须以最终 bundle 验证结果而非单个 npm publish 的成功作为基线可用信号。