npm publish 拒绝 version 字符串是因为其不满足 semver 2.0 规范,如含前导零(1.02.3)、v 前缀(v1.2.3)、缺失分隔符(1.2.3beta)、构建元数据(1.2.3+build)等均非法。

npm publish 为什么拒绝你的 version 字符串
npm publish 拒绝发布,不是因为你没登录或权限不对,大概率是 version 字段不满足 SemVer 2.0 规范。npm 严格校验 package.json 中的 version:它必须是形如 1.2.3、1.2.3-alpha.1 的合法 SemVer 字符串,不能带空格、前导零(如 1.02.3)、多余字母(如 1.2.3b)或 Git 描述符(如 1.2.3-abc123 ——除非明确加 - 分隔预发布标识)。
常见错误现象:npm ERR! Invalid version: "1.2.0-beta"、npm ERR! Invalid version: "v1.2.3"、npm ERR! Invalid version: "1.2.3+20240501"(构建元数据不被 npm publish 接受)。
-
1.2.3-beta✅ 合法(预发布版本) -
1.2.3beta❌ 缺少-分隔符 -
v1.2.3❌ 开头的v是常见 Git tag 习惯,但 npm 不认 -
1.02.3❌02是非法数字(不能有前导零) -
1.2.3+build.1❌ 构建元数据会被 npm 忽略,且部分 CI 工具会直接报错
CI 脚本里自动生成 version 的坑点
很多自动化发布流程用脚本从 Git tag 或 commit 提取 version,但容易忽略 npm 的校验边界。比如用 git describe --tags 输出 v1.2.3-5-gabc123,直接塞进 package.json 就会失败。
实操建议:
- 始终用
semver这个 npm 包做校验和标准化:semver.valid('v1.2.3')返回null,而semver.clean('v1.2.3')返回'1.2.3' - CI 中写入
package.json前,先运行npm version --no-git-tag-version 1.2.3—— 它会自动校验并重写version字段,比手动画 JSON 更可靠 - 避免在脚本里拼接字符串生成 version;改用
semver.inc('1.2.3', 'patch')或semver.coerce('v1.2.3')等函数处理输入
lerna 和 changesets 对 version 格式的隐式要求
lerna version 和 changesets 都依赖合法 SemVer,但它们各自有额外约束。lerna 默认只接受 major/minor/patch 三类 bump,如果手动指定 --conventional-commits,则会尝试解析 commit message 生成预发布版(如 1.2.3-alpha.0),此时 alpha 后必须跟数字,1.2.3-alpha 仍非法。
changesets 要求每个 .changeset/*.md 文件顶部的 ---\nversion: 1.2.3\n--- 必须严格合规,否则 changeset version 会中断。
- lerna 的
--force-publish不跳过 version 校验,只是绕过“是否需要发布”的判断 - changesets 的
version字段不支持构建元数据(+后内容),也不接受rc缩写,必须写成1.2.3-rc.1 - 若用
lerna publish --canary,生成的 canary 版本形如1.2.3-alpha.1.sha123456,其中sha123456是构建标识,但 npm publish 会截断+及之后内容 —— 所以确保 lerna 配置中canary的 format 符合 SemVer(默认是合规的)
本地验证 version 是否能过 publish 关卡
别等 CI 报错才修复。本地就能快速验证:npm pack 会模拟 publish 流程中的校验,失败时提示和真实 publish 一致;而 npm version patch 本身就会校验当前 version 是否合法,并尝试 bump。
- 运行
npm pack --dry-run(npm ≥ 8.16.0)可跳过打包,只做校验 - 把
package.json的version改成待发布的值后,执行npm version prerelease --preid=beta --no-git-tag-version—— 它会检查合法性,并生成类似1.2.3-beta.0的结果 - 用 Node.js 内置
require('semver').valid('your-version-string')在调试终端一行验证,返回非 null 即可
最常被忽略的是:Git tag 名称和 package.json 的 version 必须一致,且都得是合法 SemVer。哪怕 tag 是 v1.2.3,npm publish 也只认 1.2.3 —— tag 本身不参与校验,但 CI 脚本若从 tag 读值却忘了 trim('v'),就全崩了。











