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

生产环境必须屏蔽开发版,靠 minimum-stability 全局设为 stable 不够——它只管“准入”,不管“显式绕过”。真正起效的是组合 prefer-stable: true + 显式约束过滤,否则 dev-main 仍可能被某个依赖悄悄带进来。
为什么 minimum-stability 设成 stable 还会装上 dev-main
因为 minimum-stability 是“最低门槛”,不是“最高限制”:只要某个包在 require 里写了 "vendor/pkg": "dev-main" 或 "vendor/pkg": "^2.0@dev",Composer 就会照单全收,完全无视你全局设的 stable。
-
minimum-stability只影响未显式标注稳定性的约束(比如只写"vendor/pkg": "^2.0") - 一旦某处写了
@dev、dev-main或dev-develop,该包就直接越过门槛 - 更隐蔽的是传递依赖:你没写
dev,但你依赖的 A 包在它自己的composer.json里声明了"minimum-stability": "dev",那它的子依赖 B 就可能被拉成dev版本
如何真正屏蔽所有 dev 分支包(包括传递依赖)
没有一劳永逸的开关,但有可落地的三层拦截:
- 保持
composer.json根级"minimum-stability": "stable"(不写也默认是它),这是基础防线 - 显式禁用常见开发分支名:在
config段加"platform-check": false不起作用;真正有效的是用replace声明虚拟包来“占位”,例如:"replace": { "vendor/pkg": "*" }再配合composer update --with-dependencies触发冲突检测(慎用,仅限已知问题包) - CI 流程中硬性检查:
grep -q '"version":"dev-' composer.lock && exit 1,发现即失败,不给上线机会
composer show -a 查出来的 dev-main (dev) 能不能信
能查到,不代表能安全装——它只说明 Packagist 索引到了这个分支,但是否可用还取决于三件事:
- 该分支的
composer.json是否通过了 Packagist 的语法校验(缺autoload或name会静默跳过) - 仓库是否公开,或你的
auth.json是否配了对应 token(私有 GitLab/GitHub 仓库最常卡在这) - 该分支是否被其自身
composer.json的minimum-stability限制住(比如它自己设了"minimum-stability": "beta",那dev-main就不会被识别为有效候选)
所以 composer show -a 是起点,不是结论;看到 dev-main (dev) 后,务必跑一次 composer require vendor/pkg:dev-main --dry-run --stability=dev 看是否真能解析成功。
上线前清理 dev 包的实操步骤
别只改代码,锁文件里的 dev 提交 hash 会顽固残留:
- 先确认目标包当前状态:
composer show vendor/pkg,看versions行是否含dev-或reference - 强制切回稳定版:
composer require vendor/pkg:^2.5(不带@dev,不加--stability) - 检查
composer.lock:搜索"vendor/pkg"段,确认version字段是"2.5.3"这类语义化版本,而非"dev-main"或"reference": "a1b2c3d" - 最后执行
composer install --no-dev --optimize-autoloader,确保 vendor 目录里没混入任何开发版代码
最易忽略的点:composer.lock 里存的是精确 commit,不是分支名。即使远程 dev-main 被 force-push 覆盖,旧 hash 仍会锁定在 lock 文件里,下次 install 依然拉那个失效提交——所以清理必须落到 lock 文件内容本身。


















