minimum-stability: stable 挡不住 dev-main,因其仅约束未显式标注稳定性的包;显式声明如 "dev-main" 或 "@dev" 直接绕过该限制,且传递依赖中子包自设 minimum-stability: dev 也会导致 dev 版引入。

开发版和稳定版混用不是版本号打架,而是稳定性标签(stability flag)在依赖树里互相撕扯——Composer 默认拒绝把 dev-main 当成 ^3.0 用,哪怕它实际代码完全兼容。
为什么 minimum-stability: stable 挡不住 dev-main
这个配置只管“没写明稳定性”的包。一旦某个依赖(包括你自己的 require、别人包的 require-dev,甚至私有仓库的 repositories 配置)显式写了 "vendor/pkg": "dev-main" 或 "@dev",Composer 就直接放行,minimum-stability 形同虚设。
- 最常中招的是
require-dev里的测试工具:比如orchestra/testbench自己设了"minimum-stability": "dev",它拉进来的laravel/framework就可能变成dev-laravel11,哪怕你主项目锁着^10.0 -
composer show -a看到的dev-main (dev)不代表能装——得确认该分支的composer.json本身没被minimum-stability: beta卡住,且你的auth.json能访问私有仓库 - 别信 Packagist 页面上写的 “Latest: dev-main”——那只是索引到了分支,不代表它通过了语法校验或 autoload 定义完整
用 as 别名让 dev-main 假装是稳定版
这不是 hack,是 Composer 官方支持的语义化对齐手段:通过 "dev-main as 3.0.0" 告诉解析器“这个开发分支的行为等价于 3.0.0”,从而满足其他包的 ^3.0 约束。
- 只对已明确声明
branch-alias的包有效(查composer show vendor/package输出里的aliases行),自己项目里加没用 - 别名不改代码,只改版本认知——你得确保
dev-main真的没破坏3.0.0的 API,否则运行时会崩 - 典型写法:
"monolog/monolog": "dev-main as 2.10.0",这样既满足^2.0,又绕过^1.0包的排斥 - 私有组件开发时最实用:
"you/internal-sdk": "dev-develop as 1.5.0",让下游项目按1.5.*正常 require
真正屏蔽开发版的三层防线
靠单个配置不可能一劳永逸,必须组合拦截:
- 根级
"minimum-stability": "stable"是底线,不写等于裸奔 - CI 流程里加硬检查:
grep -q '"version":"dev-' composer.lock && exit 1,发现就阻断发布 - 对已知高危包(比如某个老 SDK 总带进
dev-master),用"replace": {"vendor/bad-pkg": "*"}占位,再配composer update --with-dependencies触发冲突暴露
别指望 --ignore-platform-reqs 解决这个——它只跳过 PHP 版本或扩展检查,对稳定性标签冲突完全无效。真正麻烦的从来不是装不上,而是装上了却没人知道它是 dev 版。











