故事的起点是一条简单的命令:

dsh plugin --profile web add dsh-skills-nexus

期望的结果是安装 dsh-skills-nexus 插件,但实际返回的是:

[ERR_PNPM_FETCH_404] GET https://registry.npmmirror.com/dsh-skills-nexus: Not Found - 404
This error happened while installing a direct dependency of C:\Users\asswsw\.dsh\profiles\web
dsh-skills-nexus is not in the npm registry, or you have no permission to fetch it.

错误信息很明确:dsh-skills-nexus 不在 npm registry 中。这意味着我们需要将它发布到 npm。

但发布一个 npm 包远不止 npm publish 那么简单。本文将完整记录从问题诊断到成功发布的全过程,包括踩过的坑、做出的决策、以及最终验证的闭环。


第一章:发布渠道决策——为什么选择 npmjs 而非 GitHub Packages

1.1 初步调研

首先确认生态现状:

npm view dsh-skills-nexus version
# npm error code E404
# npm error 404 Not Found - GET https://registry.npmjs.org/dsh-skills-nexus - Not found

npm view dsh-research version
# 0.4.3

npm view @deepseek-ai/dsh-host-webserver version
# 0.0.1-rc.1

结论dsh-skills-nexus 名字在 npmjs 公共 registry 上可用(未被占用),而同类包 dsh-research@deepseek-ai/dsh-host-webserver 都在公共 npm registry 上。

1.2 GitHub Packages 可行性分析

用户提出:「如果发布 GitHub package 也做到安装的话也行」。我们评估了三个硬伤:

硬伤一:强制 scoped 包名

  • GitHub Packages 的 npm registry 强制 @owner/name 形态
  • dsh plugin add dsh-skills-nexus 需要 unscoped 裸名
  • dsh-skills-nexus 这个裸名在 GitHub Packages 根本不存在

硬伤二:公开包也要鉴权

  • GitHub Packages 即使是 public 包,安装也需要 read:packages 的 PAT
  • 错误日志中 No authorization header was set for the request 恰好证明 dsh profile 的 pnpm 环境没有任何 auth 配置
  • npmmirror 不代理 GitHub Packages

硬伤三:破坏一条命令安装的承诺

  • 每个消费者都得改 .npmrc(scope + token)
  • 与 README 主打的 dsh plugin add 单命令体验冲突

最终决策:发布到 npmjs.com 公共 registry 是唯一正解。


第二章:发布就绪度检查

2.1 package.json 审查

检查当前 package.json 是否具备发布形态:

{
  "name": "dsh-skills-nexus",
  "version": "0.3.0",
  "bin": {
    "dsh-skills-nexus": "lib/cli/index.js"
  },
  "exports": {
    ".": "./lib/index.js",
    "./resolve": "./lib/resolve.js",
    "./package.json": "./package.json"
  },
  "files": [
    "lib",
    "cordis.patch.yml",
    "README.md"
  ],
  "engines": {
    "node": ">=20.0.0"
  },
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/xiaxi626/dsh-skills-nexus.git"
  }
}

检查清单

  • name/version/description/license/repository 齐全
  • bin 指向 lib/cli/index.js
  • exports 定义清晰
  • files 包含 lib + cordis.patch.ymldsh.bundle.patch 激活所需)+ README.md
  • engines 声明 Node 版本要求
  • lib/ 是已提交的构建产物,CI 有新鲜度门禁

结论:package.json 已是完整发布形态,无需修改。

2.2 版本策略决策

检查 git 状态:

git log --oneline -3
# 35a7d22 (HEAD -> master, origin/master) docs(cli): add SSH/git+https forms to --help
# 7585005 docs: add build-on-nexus guide for tool authors
# adc9e7f fix(completions): repair the fish names rule and the zsh expectation

git tag --list
# v0.3.0

git rev-parse v0.3.0
# 0f280b96690493052970f8b5f4a014ca4aba4d6e

git rev-parse HEAD
# 35a7d22795c7fa09d4c96b33932c76647389ad50

发现v0.3.0 tag(0f280b9)≠ HEAD(35a7d22)。tag 之后又积了 completions P2、双语文档、--help SSH 补充等 [Unreleased] 条目。

两个选择

  • A:发布 v0.3.0 tag 内容(checkout 到 tag 时刻状态)
  • B:发布 HEAD 内容(但版本号仍是 0.3.0,会导致 tag ≠ tarball)

决策:选择 A——发布 v0.3.0 tag 内容,保证 npm 包与 GitHub tag 严格对应、可追溯。master 上的 [Unreleased] 属于未来版本(0.4.0),不随 0.3.0 发布。


第三章:发布准备

3.1 添加 prepublishOnly 门禁

为了防止未来发布时手滑发坏包,在 HEAD 的 package.json 添加:

{
  "scripts": {
    "prepublishOnly": "npm run typecheck && npm run lint && npm test"
  }
}

说明

  • prepublishOnly 会在 npm publish 时自动运行
  • 守护未来从 HEAD 发布的版本
  • v0.3.0 发布不受影响(worktree 用 tag 的旧 package.json,没有这个脚本)

提交并推送:

git add package.json
git commit -m "build: run typecheck/lint/test automatically before npm publish"
git push

3.2 创建发布工作树

使用 git worktree 隔离 v0.3.0 tag 内容,避免污染当前开发分支:

git worktree add .release-v030 v0.3.0
# Preparing worktree (detached HEAD 35ee9ea)
# HEAD is now at 35ee9ea chore(release): v0.3.0

优势

  • 在独立目录 checkout v0.3.0 tag 内容
  • 不影响主工作区的 master 分支
  • 发布完成后一键清理

3.3 在工作树跑门禁

进入工作树,安装依赖并运行三道门禁:

cd .release-v030
npm ci
npm run typecheck  # ✅ 通过
npm run lint       # ✅ 通过
npm test           # ✅ 135/135 通过,0 失败

注意:v0.3.0 的 package.json 没有 prepublishOnly,所以这里手动等价执行。

3.4 预览 tarball

确认将要发布的内容:

npm pack --dry-run

输出:

📦  dsh-skills-nexus@0.3.0
Tarball Contents
  1.1kB LICENSE
  14.8kB README.md
  247B cordis.patch.yml
  574B lib/cli/args.d.ts
  ... (共 72 个文件)
Tarball Details
  name: dsh-skills-nexus
  version: 0.3.0
  package size: 42.8 kB
  unpacked size: 153.8 kB
  shasum: 8e9cfedc03bb0b0e0516e4832833848951daf04d
  total files: 72

检查

  • ✅ 包含 lib/cordis.patch.ymlREADME.mdLICENSEpackage.json
  • ✅ 不包含 src/test/node_modules.github
  • ✅ shasum 记录:8e9cfedc03bb0b0e0516e4832833848951daf04d

第四章:Registry 配置陷阱

4.1 发现问题

检查本机 npm 配置:

npm config get registry
# https://registry.npmmirror.com

问题:用户全局 registry 指向 npmmirror.com(国内镜像),不是 npmjs.org。

进一步追查来源:

npm config get userconfig
# C:\Users\asswsw\.npmrc

Select-String -Path C:\Users\asswsw\.npmrc -Pattern "registry"
# registry=https://registry.npmmirror.com

根因.npmrc 中配置了 registry=https://registry.npmmirror.com。这会让 npm login / npm publish 都打到镜像上——镜像不支持发布和登录。

4.2 解决方案

方案 A(推荐):保留镜像加速日常安装,登录/发布时显式指向官方源

npm login --registry=https://registry.npmjs.org
npm publish --registry=https://registry.npmjs.org

方案 B:干脆把默认源恢复成 npmjs 官方

npm config delete registry

选择方案 A——日常 npm install 继续享受镜像加速,只在发布时显式指定。


第五章:登录与发布

5.1 登录 npmjs

npm login --registry=https://registry.npmjs.org
# 浏览器打开 https://www.npmjs.com/auth/cli/...
# 授权 xiaxi626 账号

npm whoami --registry=https://registry.npmjs.org
# xiaxi626

5.2 发布

从工作树发布(注意:在 bash 中用 cd,不是 PowerShell 的 Push-Location):

cd .release-v030
npm publish --registry=https://registry.npmjs.org

踩坑记录

  • 第一次尝试时,用户在 bash 中执行了 PowerShell 命令 Push-Location,导致 command not found
  • 结果 npm publish 从主工作区(HEAD)执行,触发了 prepublishOnly
  • ESLint 扫到了 .release-v030/lib/*.d.ts 产物(因为工作树在主仓库内),报错 Unexpected any
  • 教训:跨 shell 命令要注意语法差异,bash 用 cd,PowerShell 用 Push-Location

正确执行后,收到 npm 确认邮件:

Hi xiaxi626!

A new version of the package dsh-skills-nexus (0.3.0) was published at 2026-09-23T12:46:53.537Z
The shasum of this package is 8e9cfedc03bb0b0e0516e4832833848951daf04d.

验证:shasum 8e9cfedc03bb0b0e0516e4832833848951daf04d 与发布前 npm pack --dry-run 预览完全一致——确认发布的包严格对应 v0.3.0 tag(commit 35ee9ea)。


第六章:验证与清理

6.1 验证发布

# 在 npmjs 官方 registry 查询
npm view dsh-skills-nexus version --registry=https://registry.npmjs.org
# 0.3.0

# 在 npmmirror 镜像查询(验证同步)
npm view dsh-skills-nexus version --registry=https://registry.npmmirror.com
# 0.3.0(已自动同步,无需手动触发)

6.2 验证安装

回到最初的命令:

dsh plugin --profile web add dsh-skills-nexus
# ✓ Lockfile passes supply-chain policies
# dependencies:
# + dsh-skills-nexus 0.3.0
# Done in 4.8s

成功!最初报错 404 的命令现在正常工作。

6.3 清理工作树

git worktree remove --force .release-v030
git status --short
# (空,工作区干净)

第七章:经验总结

7.1 Registry 配置陷阱

问题:用户全局 registry 设为镜像时,npm login / npm publish 会打到镜像上失败。

解决:登录/发布时显式 --registry=https://registry.npmjs.org

最佳实践

  • 日常安装用镜像加速(.npmrc 配置 registry=https://registry.npmmirror.com
  • 发布时显式指定官方源
  • 或者干脆不设全局 registry,用 --registry flag 按需指定

7.2 跨 Shell 命令陷阱

问题Push-Location / Pop-Location 是 PowerShell 命令,bash 中需用 cd / cd ..

教训

  • 写文档/教程时明确标注 shell 类型
  • 跨 shell 操作时注意语法差异
  • 误用会导致命令从错误目录执行

7.3 Worktree 方案优势

场景:从历史 tag 发布包,保证 npm 包与 GitHub tag 严格对应。

方案

git worktree add .release-vX.Y.Z vX.Y.Z
cd .release-vX.Y.Z
npm publish --registry=https://registry.npmjs.org
cd ..
git worktree remove --force .release-vX.Y.Z

优势

  • 隔离 tag 内容,不污染当前开发分支
  • master 上的 [Unreleased] 条目属于未来版本,不随当前 tag 发布
  • 发布完成后一键清理

7.4 npmmirror 同步

现象:发布后 ~10 分钟内自动同步,通常无需手动触发。

手动触发:访问 https://npmmirror.com/sync/dsh-skills-nexus

验证npm view <package> version --registry=https://registry.npmmirror.com

7.5 发布后撤回窗口

规则:npm 允许发布后 72 小时内 npm unpublish,之后只能 deprecate。

建议

  • 发布前充分验证(门禁 + tarball 预览)
  • 发布后立即验证(npm view + 实际安装测试)
  • 发现问题 72 小时内可撤回

第八章:后续发布流程

以后要发新版本(如把 [Unreleased] 切成 0.4.0):

8.1 切版本

按既有流程:

# 1. 版本 bump(同步改 package.json 与 package-lock.json)
npm version 0.4.0 --no-git-tag-version

# 2. CHANGELOG:在最新条目上方加 `## [0.4.0] - <日期>` 段头

# 3. 跑全门禁
npm run typecheck
npm run lint
npm test
npm run build

# 4. 提交
git add package.json package-lock.json CHANGELOG.md lib/
git commit -m "chore(release): v0.4.0"

# 5. 打 tag
git tag -a v0.4.0 -m "v0.4.0"

# 6. 推送
git push origin master --follow-tags

8.2 发布

从新 tag 的 worktree 发布:

# 1. 创建工作树
git worktree add .release-v0.4.0 v0.4.0

# 2. 进入工作树,安装依赖,跑门禁
cd .release-v0.4.0
npm ci
npm run typecheck
npm run lint
npm test

# 3. 发布(prepublishOnly 会自动跑门禁)
npm publish --registry=https://registry.npmjs.org

# 4. 清理
cd ..
git worktree remove --force .release-v0.4.0

说明

  • prepublishOnly 门禁会自动跑 typecheck/lint/test
  • 发布后 npmmirror 自动同步
  • 用户 dsh plugin add dsh-skills-nexus 会装 latest(0.4.0)

结语

从一条 404 错误到成功发布 npm 包,整个过程涉及:

  1. 渠道决策:排除 GitHub Packages,选择 npmjs 公共 registry
  2. 就绪度检查:确认 package.json 发布形态、版本策略
  3. 发布准备:添加 prepublishOnly 门禁、创建 worktree、跑门禁、预览 tarball
  4. 配置诊断:发现 registry 指向镜像,显式指定官方源
  5. 登录发布:浏览器授权、从 worktree 发布、验证 shasum
  6. 验证清理:确认 npmjs/npmmirror 可查、实际安装成功、清理工作树

关键教训:

  • Registry 配置陷阱:镜像不支持发布,需显式指定官方源
  • 跨 Shell 命令:bash 用 cd,PowerShell 用 Push-Location
  • Worktree 方案:保证 npm 包与 GitHub tag 严格对应
  • npmmirror 同步:自动同步,通常无需手动触发

最终结果:

  • dsh-skills-nexus@0.3.0 成功发布至 registry.npmjs.org
  • ✅ 发布内容与 v0.3.0 tag(commit 35ee9ea)严格对应
  • dsh plugin --profile web add dsh-skills-nexus 安装成功
  • ✅ 工作区干净,临时工作树已清理

这次发布不仅解决了一个 404 错误,更建立了一套可重复的发布流程,为未来的版本迭代打下基础。


参考资料