本文基于一次完整的 npm 包生命周期管理实践整理:从项目配置、首次发布、多次版本迭代,到废弃旧版本时踩过的认证与政策坑。适用于第一次往 npm 发包的开发者。
一、发布一个包需要准备什么
1. package.json 关键字段
json
{
"name": "your-package-name",
"version": "1.0.0",
"description": "一句话说明包的用途(会显示在 npm 搜索结果中)",
"type": "commonjs",
"main": "index.js",
"bin": {
"your-package-name": "index.js"
},
"files": ["index.js", "README.md", "LICENSE"],
"keywords": ["mcp", "cli", "file-operations"],
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/you/your-repo.git"
},
"engines": { "node": ">=18" },
"publishConfig": { "access": "public" },
"dependencies": { }
}
几个容易忽略的点:
files白名单:只写必须发布的文件。没有它,测试脚本、配置文件、临时文件全会被打进去bin+ shebang :想让用户npx your-package直接运行,入口文件首行必须是#!/usr/bin/env node,并在.gitattributes中强制该文件 LF 行尾(CRLF 的 shebang 在 Linux/macOS 下会解析成node\r,直接启动失败)engines:声明 Node 版本要求,避免用户在不兼容环境里跑出诡异报错publishConfig.access: "public":免费账户发 scoped 包(@xxx/yyy)必须显式声明,非 scoped 包可省略
2. 配套文件
| 文件 | 作用 |
|---|---|
README.md |
npm 包主页内容,安装页转化率的关键 |
LICENSE |
没有明确 license 的包,别人法律上不敢用 |
.gitattributes |
跨平台行尾控制(尤其 bin 入口文件) |
.npmignore 或 files 字段 |
二选一,推荐后者(白名单比黑名单安全) |
3. 发布前自检
bash
# 本地模拟打包,检查 tarball 内容与体积
npm pack --dry-run
# 确认要发的文件一个不多、一个不少
二、认证与发布
1. 登录的几种方式
bash
npm login # web 浏览器授权(推荐)
npm whoami # 验证登录状态
坑 1:404 可能是 401。 发布一个已存在的包时,如果认证失效,npm 返回的是 404 Not Found 而不是 401------这是 npm 故意不泄露包是否存在。遇到莫名的 404,先跑 npm whoami 检查登录状态。
坑 2:.npmrc 里的旧 token。 C:\Users\<你>\.npmrc 中残留的失效 _authToken 会让所有操作静默失败。删除该行重新登录即可。
2. 发布
bash
npm publish
发布成功后验证:
bash
npm view your-package-name versions --json # 查看已发布版本列表
npx -y your-package-name # 实际跑一次
也可登录npmjs官网查看,点击右上角个人头像-packages:

注意 npx 缓存 :发布新版本后,本机 npx 可能仍用缓存的旧版,清掉缓存再验证:
bash
npx clear-npx-cache
# 或手动删除 C:/Users/<你>/AppData/Local/npm-cache/_npx
三、修改包后发布更新
1. 遵循语义化版本(SemVer)
| 变更类型 | 版本号 | 示例 |
|---|---|---|
| 修复 bug、不改接口 | 补丁版本 1.0.1 | 修一个匹配失败 |
| 新增功能、向后兼容 | 次版本 1.1.0 | 加一个新工具函数 |
| 破坏性变更 | 主版本 2.0.0 | 改参数签名 |
2. 更新流程清单
bash
# 1. 改代码 + 跑测试(发包前测试必须全绿)
# 2. 更新 package.json 版本号
# 3. 同步 lockfile(很多人漏这步)
npm install --package-lock-only
# 4. 语法/构建检查
node --check index.js
# 5. 发布
npm publish
# 6. 清 npx 缓存验证新版
坑 3:lockfile 版本漂移。 只改 package.json 不重新生成 package-lock.json,仓库里的版本信息会不一致。npm install --package-lock-only 可以只刷新 lockfile 不动 node_modules。
四、废弃旧版本(deprecate)
当你修了一个影响较大的 bug,希望提醒用户别再用旧版时:
bash
npm deprecate your-package-name@1.0.0 "Deprecated: please upgrade to ^1.6.0. Known issues in this version."
deprecate 的真实语义(重要):
- 它是警告,不是拦截 。用户仍可以
npm install your-package-name@1.0.0,只是安装时多一行npm WARN deprecated,npm 页面上该版本带 Deprecated 徽标 - 默认安装(不带版本号)永远装最新版,不受影响
- 老 lockfile 中的引用不受任何影响------这正是它温和的地方
坑 4:EOTP 二次认证。 开启 2FA 的账户执行 deprecate 会被要求 one-time password。web login 得到的 token 无法免 OTP,会报 EOTP 错误。解决办法:
bash
# 方式一:带验证器动态码
npm deprecate your-package-name@1.0.0 --otp=123456 "消息"
# 方式二:npmjs.com 生成 Granular Access Token(勾选 Read and write + 允许 bypass 2FA),
# 写入 .npmrc 的 //registry.npmjs.org/:_authToken= 行后正常执行
坑 5:403 Forbidden。 报 "Two-factor authentication or granular access token with bypass 2fa enabled is required" 时,同上------要么提供 OTP,要么换合规 token。npm 正在逐步限制传统 token 直接发布和执行敏感操作。
最终废弃的版本效果(不勾选show deprecated versions的话,默认不展示已废弃的版本):

五、能不能直接删掉旧版本或整个包?
结论先行:基本不能,也别想。
npm 的 unpublish 政策:
| 情形 | 规则 |
|---|---|
| 包发布 < 72 小时且无人依赖 | 可自助 unpublish |
| 超过 72 小时 | 需向 npm support 人工申请,"有人安装过"通常被拒 |
| unpublish 后 24 小时内 | 包名可重新发布 |
| 超过 24 小时 | 包名永久锁定,任何人(包括原作者)不能再用该名发布 |
这套设计是为了防供应链攻击:如果版本号可以删除后重发,攻击者删掉 1.2.3 再发一个同名恶意 1.2.3,所有 lockfile 锁死该版本的用户会自动中招。
所以「删包重发清空旧版本」的思路行不通:删不掉,即使删掉也要换包名,README 里所有 npx your-package-name 示例、外部引用全部作废,代价远超收益。
正确姿势:旧版本留着(默认安装永远拿最新版,旧版几乎无害),介意的话打个 deprecate 标记就到头了。
六、发布经验清单(TL;DR)
首次发布:
-
files白名单 +npm pack --dry-run核对 tarball - bin 入口 shebang + LF 行尾(
.gitattributes) - README / LICENSE / keywords / repository 配齐
-
npm login+npm whoami确认登录
每次更新:
- 测试全绿
- SemVer 决定版本号
-
npm install --package-lock-only同步 lockfile - publish 后清 npx 缓存验证
遇到报错先对照:
404-> 十有八九是认证失效,查npm whoami401->.npmrc里的旧 token 该删了403-> 2FA 要求,补--otp或换 granular tokenEOTP-> 同上,敏感操作(deprecate/publish)强制二次认证
版本治理:
- 旧版本有已知缺陷 ->
npm deprecate打警告(提示不拦截) - 想物理删除 -> 死心,unpublish 政策不允许
npm 的所有"反直觉"设计(404 代替 401、不给删包、OTP 强制)本质上都是同一条原则:生态信任高于作者便利。理解了这一点,上面所有的坑都不再是坑。
博客已按 markdown 输出。全程未包含账户名、邮箱、token、认证链接等敏感信息,包名与版本号均为公开信息或占位符,可直接发布。