npm publish 报 E401 Unable to authenticate,或装内部包时报 E404——多半不是令牌本身坏了,而是私服还没接对:服务没起来、仓库类型不对、registry 末尾 / 漏了,或 _authToken 路径和 scope 没对齐。
下面用 AngusRepo 社区版私有化部署,按操作顺序走完一遍:安装 → 建仓与令牌 → .npmrc(含 scope)→ 最小包 publish / install → 验证。命令里的主机写成 repo.example.com,换成你的域名即可。更细的 401 见 npm publish 401 排查。
1. 安装(得到可打开的控制台)
前提:Docker Engine + Compose v2;约 2 核 / 4 GB;放行 80;gm.example.com 与 repo.example.com 已解析到本机(试用可写 /etc/hosts)。完整步骤见 第一次跑通。
curl --fail --location --progress-bar -o AngusRepo-Community-1.0.0.zip \
https://repo.anguskit.com/raw/raw-public/AngusKit/repo/AngusRepo-Community-1.0.0.zip
unzip AngusRepo-Community-1.0.0.zip
cd AngusRepo-1.0.0
./install.sh
向导与快速开始对齐:Compose;Access 选 2(捆绑反代);TLS 选 4(HTTP :80);Database 选 Compose 内 MySQL;Public DNS name 填后缀如 example.com。出现 Install finished. 后:
./bin/angusctl.sh doctor # 看到 doctor: OK
浏览器打开 http://gm.example.com/ 登录,再打开 http://repo.example.com/。Windows 用 curl.exe + .\install.bat。需要 HTTPS 时见 安装与部署。
下文默认 HTTP 私有化,URL 写成 http://repo.example.com/...。上了可信证书后,把 .npmrc 里的协议和 _authToken 主机路径一并改成 https:// 对应形式,避免混协议导致 401。控制台能打开后,先建仓库和令牌,再改客户端配置。
2. 建仓、令牌与拓扑
npm publish 只能进 HOSTED;PROXY、GROUP 只读。在 http://repo.example.com/ 创建(名称可自定,后面配置保持一致):
| 仓库名示例 | 类型 | 干什么 |
|---|---|---|
npm-hosted |
HOSTED | 内部包发布目标;建议禁止覆盖 |
npmjs-proxy |
PROXY | 代理 npmjs(远程 URL:https://registry.npmjs.org) |
npm-all |
GROUP | 统一安装入口;成员顺序 hosted → proxy |

npm GROUP 的 packument(versions / dist-tags)按成员取并集,同名版本保留先匹配成员;tarball 等二进制仍按成员顺序 first-match。成员顺序写成 hosted → proxy,内部版本进并集,同坐标冲突时 HOSTED 优先,有助于降低依赖混淆风险。开发者只配一个安装 URL。
同一控制台继续准备:
- [ ] 创建访问令牌(只显示一次,立刻保存)
- [ ] CI 对
npm-hosted有 Write;开发者对npm-all有 Read - [ ] GROUP 成员顺序确认为 hosted → npmjs-proxy
export NPM_TOKEN="your_access_token" # .npmrc 的 _authToken 填这个
export REPO_HOST="repo.example.com" # 换成你的 repo.<域名>
具体路径以控制台「接入配置」为准;字段说明见 npm 格式。
3. 安装:.npmrc 与 scope
默认口径:安装走 GROUP(npm-all),发布走 HOSTED(npm-hosted)。scope 不要指到 PROXY。
配法一:只切公司 scope(迁移期推荐)
公共包继续走原来的源,只有 @mycompany 走私服 GROUP:
@mycompany:registry=http://repo.example.com/npm/npm-all/
//repo.example.com/npm/npm-all/:_authToken=${NPM_TOKEN}
//repo.example.com/npm/npm-hosted/:_authToken=${NPM_TOKEN}
项目 .npmrc 可以提交 URL;对 npm CLI / Yarn 1,令牌行可用 ${NPM_TOKEN} 由环境注入。npm 自 7.10 起已移除 always-auth,写了也不生效——靠上面两条 URL 作用域 _authToken 覆盖元数据与 tarball 路径。若流水线用 pnpm ≥11.5.3,项目 .npmrc 里的 ${...} 凭据行会被忽略,令牌改走用户级配置或 pnpm_config_,见 pnpm 和 yarn 怎么用私有源。
配法二:整个项目默认走私服
registry=http://repo.example.com/npm/npm-all/
//repo.example.com/npm/npm-all/:_authToken=${NPM_TOKEN}
//repo.example.com/npm/npm-hosted/:_authToken=${NPM_TOKEN}
三个高频坑:
- registry URL 末尾要有
/,漏掉是最常见的低级错误。 _authToken那一行没有http:///https://,主机路径必须和 registry 完全一致(含路径和末尾斜杠),对不上就是 E401。- 从 npmjs 迁过来时,
package-lock.json会锁旧 registry——删掉 lock 后重新npm install,或批量改resolved字段,否则会继续打到公网。
先确认令牌对 HOSTED 生效:
npm whoami --registry http://repo.example.com/npm/npm-hosted/
应打印当前用户名;这里就失败,先别 publish。
4. 发布:publish 到 HOSTED
发布目标必须是 HOSTED,不能指向 GROUP 或 PROXY。scoped 包默认私有发布:
npm publish --registry http://repo.example.com/npm/npm-hosted/ --access restricted
也可以在 package.json 里写死发布地址:
{
"name": "@mycompany/ui-kit",
"version": "1.0.0",
"publishConfig": {
"registry": "http://repo.example.com/npm/npm-hosted/",
"access": "restricted"
}
}
同版本重复 publish 会 409。这是预期行为:改 bug 请升 patch,不要覆盖。确需对外公开时再改 access: "public",并确认仓库 ACL 允许匿名读。
5. 验证闭环(最小可发布包)
不要先装一个还不存在的包。按下面顺序:建包 → 发布 → 查元数据 → 干净目录安装。
mkdir -p /tmp/angus-npm-demo && cd /tmp/angus-npm-demo
cat > package.json <<'EOF'
{
"name": "@mycompany/ui-kit",
"version": "1.0.0",
"description": "AngusRepo npm smoke package",
"main": "index.js",
"publishConfig": {
"registry": "http://repo.example.com/npm/npm-hosted/",
"access": "restricted"
}
}
EOF
echo "module.exports = { ok: true };" > index.js
# 复用上文 .npmrc(或写到本目录)
cat > .npmrc <<EOF
@mycompany:registry=http://repo.example.com/npm/npm-all/
//repo.example.com/npm/npm-all/:_authToken=${NPM_TOKEN}
//repo.example.com/npm/npm-hosted/:_authToken=${NPM_TOKEN}
EOF
npm publish
npm info @mycompany/ui-kit --registry http://repo.example.com/npm/npm-all/
再开一个干净目录验证安装:
mkdir -p /tmp/angus-npm-consume && cd /tmp/angus-npm-consume
cat > .npmrc <<EOF
@mycompany:registry=http://repo.example.com/npm/npm-all/
//repo.example.com/npm/npm-all/:_authToken=${NPM_TOKEN}
//repo.example.com/npm/npm-hosted/:_authToken=${NPM_TOKEN}
EOF
npm init -y
npm install @mycompany/ui-kit@1.0.0
node -e "console.log(require('@mycompany/ui-kit'))"
控制台制品列表应能看到 1.0.0;node 应打印 { ok: true }。
CI 最小片段
流水线注入 NPM_TOKEN(不要用 Admin 令牌),在仓库根写入与本地一致的 .npmrc 后:
- run: |
printf '@mycompany:registry=http://repo.example.com/npm/npm-all/\n' > .npmrc
printf '//repo.example.com/npm/npm-all/:_authToken=%s\n' "$NPM_TOKEN" >> .npmrc
printf '//repo.example.com/npm/npm-hosted/:_authToken=%s\n' "$NPM_TOKEN" >> .npmrc
npm ci
npm publish --registry http://repo.example.com/npm/npm-hosted/ --access restricted
完整流水线编排另见 Jenkins 构建测试发布。pnpm / Yarn 接同一套私服见 pnpm 和 yarn 怎么用私有源(pnpm ≥11.5.3 不会展开项目 .npmrc 里的 ${NPM_TOKEN})。
6. 日常维护(publish / install 跑通之后)
清理旧版本 — npm-hosted 按「保留最近 N 版本」配置,先 Dry Run,见 清理策略。正式版保持不可变。
PROXY 冷缓存 — 首次拉公共包会慢,属正常;可对高频依赖做预热。
入库检查 — 上传时有轻量门禁;需要更细的漏洞 / 许可证分析时再接外部扫描。打开阻断前先处理误报,见 入库门禁与 SBOM。
报错速查
| 症状 | 常见原因 | 怎么处理 |
|---|---|---|
E401 Unable to authenticate |
_authToken 路径与 registry 不一致,或令牌过期 |
核对主机路径与末尾 /;见 401 排查 |
ENEEDAUTH |
拉私有坐标时没带令牌 | 确认 npm-all / npm-hosted 的 _authToken 行存在且路径对齐(npm ≥7.10 无 always-auth) |
E403 Forbidden |
有身份但无 Write / Read | 查 ACL;publish 账号要对 npm-hosted 有 Write |
E404 Not Found |
GROUP 成员顺序错、scope 没生效、包未发布 | npm info;浏览器打开 metadata URL |
| lock 仍打 npmjs | package-lock.json 锁了旧 resolved |
删 lock 重装,或改写 resolved |
E409 Conflict |
同版本重复发布 | 升版本号 |
| 装到旧版本 | 私服缓存了 metadata | 等 TTL,或在控制台对该包失效缓存 |
ETIMEDOUT |
PROXY 冷缓存首次回源 | 二次安装应走缓存;可预热核心依赖 |
字段级说明见 npm 格式;更多排错见 故障排查手册。
FAQ
能不能 publish 到 GROUP?
不能。GROUP / PROXY 只读,发布必须指向 HOSTED。scope 用于安装路由;发布地址用 publishConfig 或 --registry 钉死 HOSTED。
迁移时要不要一上来全局换源?
不建议。优先用 scope:先切 @mycompany,公共源不动,跑稳再决定是否全局收口。只有 npm、治理很轻时 Verdaccio 仍可先撑一阵;要多格式或统一 ACL 时再并进制品库。
_authToken 那一行要不要写 https://?
不要。主机路径不含协议;必须与 registry 的主机 + 路径 + 末尾 / 完全一致,npm 才会带上认证。
从公网源迁过来为什么还在打 npmjs?
npm v7+ 的 lockfile 会记录 registry。迁仓后删 package-lock.json 再装,或更新其中的 resolved 字段。
小结
- 安装并打开控制台 → 建 HOSTED + PROXY + GROUP → 发令牌。
- 安装走 GROUP(
.npmrc/ scope);发布走 HOSTED(publishConfig或--registry)。 - 末尾
/+npm-all/npm-hosted两条_authToken路径对齐,是避免 E401 /ENEEDAUTH的关键;pnpm 令牌注入见专文。 - 用最小包跑通 publish → info → 干净目录 install;迁仓时处理 lockfile。