From 577bd35f97fd46ad5f296980c86f5fcc51413f5c Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 3 May 2026 21:23:37 +0900 Subject: fix(update): mirror Composer's always-include-dev resolution path Composer's `Installer::doUpdate` hardcodes `includeDevRequires=true` for the first solve, so a `--no-dev` update still considers require-dev during resolution and writes a complete lock file (the flag only gates what gets installed). Mozart was passing `include_dev: dev_mode`, dropping require-dev from both the resolver pool and the lock when `--no-dev` was set, which broke fixtures where a non-dev requirement was satisfied by a package pulled in transitively through require-dev (e.g. `provided/pkg` provided by a require-dev metapackage). Also extend `classify_dev_packages` to walk `provide`/`replace` edges so the production BFS reaches packages that satisfy a `require` virtually, matching what Composer's `extractDevPackages` second-Solver run achieves through a real solve. --- crates/mozart/src/commands/update.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'crates/mozart/src') diff --git a/crates/mozart/src/commands/update.rs b/crates/mozart/src/commands/update.rs index 43825f2..2853fc8 100644 --- a/crates/mozart/src/commands/update.rs +++ b/crates/mozart/src/commands/update.rs @@ -1102,7 +1102,12 @@ pub async fn run( root_version: composer_json.version.clone(), require, require_dev, - include_dev: dev_mode, + // Mirrors `Composer\Installer::doUpdate` line 498: + // `requirePackagesForUpdate($request, $lockedRepo, true)` — + // require-dev is always part of the first solve, regardless of + // --no-dev. The flag only affects what gets installed and the + // packages-dev split in the lock file. + include_dev: true, minimum_stability, stability_flags: IndexMap::new(), prefer_stable, @@ -1283,12 +1288,14 @@ pub async fn run( resolved = apply_patch_only(resolved, lock); } - // Step 9: Generate new lock file + // Step 9: Generate new lock file. `include_dev: true` matches Composer: + // `update --no-dev` still writes a complete lock file with packages-dev + // populated, so a later `install` (with dev_mode) sees them. let new_lock = lockfile::generate_lock_file(&lockfile::LockFileGenerationRequest { resolved_packages: resolved, composer_json_content: composer_json_content.clone(), composer_json: composer_json.clone(), - include_dev: dev_mode, + include_dev: true, repositories: repositories.clone(), previous_lock: old_lock.clone(), }) -- cgit v1.3.1