| Age | Commit message (Collapse) | Author |
|
console_format!
The six tag-style color functions (info, comment, error, question,
highlight, warning) are pub only so that console_format! can call them
from generated code; they are not part of the public API. Rename them
to __format_*_message to make that intent visible, add a doc-comment
saying not to call them directly, and replace every remaining direct
call site with console_format!.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
|
|
|
|
Partial update of a non-allow-listed dev package now resolves and emits
the locked-repo entry verbatim, mirroring Composer's `PoolBuilder`.
Three coordinated changes:
- resolver: `lock_filter_allows` accepts the locked package's branch-
alias normalized versions, not just the base. Without this, root
constraints like `~2.1` against a `dev-master` locked package whose
branch alias is `2.1.x-dev` failed with "no matching package found".
- lockfile: new `lock_pinned_names` field on `LockFileGenerationRequest`
routes non-allow-listed packages through `previous_lock_lookup`
before `inline_lookup`, so the lock's source/dist references survive
even when the inline metadata has moved to a newer commit.
- update: `apply_partial_update` skips alias entries — re-pinning their
pretty `version` to the base would collapse the alias label and
emit a self-referential entry in the new lock's `aliases[]` block.
Unblocks partial_update_forces_dev_reference_from_lock_for_non_updated_packages.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Drop the content-hash-only short-circuit for `--lock` and route the
flag through the same updateMirrors flow Composer uses
(`UpdateCommand::execute` line 219). Locked packages are pinned at
their lock versions, but the resolver still runs and the installer
still emits the operation trace — including MarkAliasInstalled lines
for aliases the lock declares but installed.json hasn't recorded yet.
Three follow-on fixes the new flow needs:
- Re-attach `<lock-version> as <alias>` from `lock.aliases` when
building the mirrors-mode require list, so the resolver's alias
extractor materializes the alias entry. The bare `<version>` form
is required because `==<version>` fails Composer's normalize.
- Don't `continue` past Action::Skip in the install loop. Composer's
Transaction::calculateOperations emits MarkAliasInstalled even when
the target package is already at the right version, as long as the
alias is missing from installed.json.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
A path repo locked with `transport-options.symlink: false` is in
copy-mode and Composer's PoolBuilder keeps that entry pinned at its
lock version on a partial update. The previous unconditional skip
treated every path-repo dist as "always reload from disk", which
caused non-allow-listed copy-mode packages to drift.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Adds a `mozart-php-serialize` crate (a byte-compatible port of PHP's
`serialize()`) and a `mozart-registry::path_repository` module that
expands `type: path` entries into synthetic `type: package`
repositories. Each synthesized package carries the same SHA-1 dist
reference Composer computes (`sha1(\$json . serialize(\$options))`)
so the lockfile and trace lines match Composer byte-for-byte.
Two latent bugs surfaced once the path-repo flow exercised real
resolutions:
- `apply_partial_update` swapped path-repo packages back to their
locked version, defeating Composer's "path repos always reload"
rule (`PoolBuilder` treats them as canonical, not lock-bound).
Mirror the path-repo skip already used when constructing
`locked_packages`.
- `normalize_root_alias_atom` returned the raw input string for
stable numeric atoms (e.g. `1.1.1`), so the alias matcher's
`input.version \!= alias.version_normalized` check — comparing
against pool inputs that carry the 4-segment normalized form —
silently never matched. Run the parsed Version through Display so
both sides are in the same shape.
`install/update::run` gain a `path_repo_base_override: Option<&Path>`
parameter for the in-process test harness: Composer's PHPUnit
`InstallerTest::setUp` does `chdir(__DIR__)` so relative path-repo
URLs resolve against `composer/tests/Composer/Test/`, but the Rust
harness writes `composer.json` into a per-test tempdir and can't
chdir safely under parallel tests. Production callers pass `None`
and resolve against `working_dir`.
Greens 3 ignored installer fixtures:
partial_update_loads_root_aliases_for_path_repos
alias_in_lock
alias_in_lock2
|
|
Three related parity gaps surfaced by the `update-allow-list-patterns`
fixture:
1. `mozart-semver`'s wildcard parser turned `*.*` into `>=0 <1` (a
single-major range) because stripping the trailing `.*` left `*`
in the major slot, which `parse()` quietly read as `0`. Composer
reduces such patterns to a plain `*` (unconstrained) — match
that and short-circuit when the stripped base is `*`.
2. `expand_wildcards` passed any non-wildcard specifier straight
through, so a typo like `notexact/Test` (lock has
`notexact/testpackage`) entered the resolver as a real package
name and failed lookup. Mirror Composer's regex-based
`isUpdateAllowed`/`warnAboutNonMatchingUpdateAllowList`: every
specifier — wildcard or not — is matched against locked names
*and* current root-require names, with `*` expanded to `.*`,
and unmatched specs are warned and dropped instead of forwarded.
3. The lockfile generator's metadata loop hit the empty test repo
set when a partial update kept a non-allow-listed package at its
locked version that the inline repo no longer advertised, and
bailed with "Could not find version". Add a `previous_lock`
fallback that synthesizes a `PackagistVersion` straight off the
`LockedPackage` so the lock entry's own metadata stays
authoritative for packages that aren't moving.
|
|
Mozart silently ignored the `security-advisories` block on inline
`type: package` repositories and the `config.audit.block-insecure`
audit flag, so a `composer update` succeeded with packages a Composer
run would have refused to load. Mirror Composer's
`SecurityAdvisoryPoolFilter` for the slice that feeds the pool:
- Plumb a `security-advisories` field through `RawRepository` and a
`block_insecure` flag through `ResolveRequest`, lifted off
`composer.json`'s `config.audit.block-insecure`.
- Collect every advisory's `affectedVersions` constraint at resolve
time. When `block_insecure` is set and an inline package's
normalized version satisfies the constraint, drop it from the pool
before solving — root requires with no unaffected candidate then
fail with the standard "could not be resolved" error.
|
|
`update lock`, `update nothing`, and `update mirrors` were treated as
ordinary full updates: the resolver picked the highest matching
version of every root require and the install step rewrote refs from
the repository, masquerading transport metadata refreshes as content
changes (and accepting brand-new root requires the lock had never
seen). Mirror Composer's `setUpdateMirrors(true)` flow:
- Detect the bare-keyword form and skip composer.json's require /
require-dev entirely; require each locked package by exact version
instead. This drops fresh root requires Mozart shouldn't yet honor
and pins existing ones to their lock version.
- After lockfile generation, walk each new entry and copy the OLD
lock's source/dist reference back when the source/dist *type*
matches, mirroring `LockTransaction::updateMirrorAndUrls`. URL and
mirrors update; ref stays put — so a repo rename or mirror flip
emits no Update operation, but a real type change (`hg` → `git`)
still does.
|
|
The previous --minimal-changes wiring only populated the policy's
preferred-version map when no packages were named on the CLI, so a
partial update like `update foo --with-all-dependencies
--minimal-changes` saw an empty map and the resolver picked the highest
matching version for transitive deps that should have stayed at their
lock version. Mirror Composer's
`Installer::createPolicy(minimalUpdate=true)` directly: build the map
from the lock and skip only the packages explicitly named by the user
(the `updateAllowList`), so deps unlocked transitively by
`--with-(all-)dependencies` still prefer their lock version when the
constraint allows it.
|
|
`expand_with_(direct|all)_dependencies` only looked up dependencies by
their literal name in the lock. When a transitive require pointed at a
virtual / replaced name (e.g. `replaced/pkg1`) and the lock owned it
through another package's `replace` map (e.g. `dep/pkg1` replaces
`replaced/pkg1`), the replacer never entered the unlock set. The
partial-update resolver then left it pinned at its lock version and
silently kept the user on the old release. Mirror Composer's replace
branch in `PoolBuilder::loadPackage`: build a `replaced → replacers`
index over the lock and route every dep walked during expansion
through it before recursing.
|
|
The previous implementation pinned every resolved package back to its
locked version after the resolve, which discarded the new versions the
solver had to pick when a root constraint moved off the lock (e.g. a
require bumped from `1.*` to `2.*`). The lock effectively never moved,
so transitive cascades from a forced root-level update were lost.
Mirror Composer's `Installer::createPolicy(forUpdate=true,
minimalUpdate=true)` instead: thread the lock's `name → normalized
version` map through the policy as `preferred_versions`. The solver now
picks the locked version as a tiebreaker when it still satisfies the
active constraints, but moves freely when a constraint forces a
different version. Drop the post-process hook entirely.
|
|
Two related parity gaps surfaced by the `circular-dependency` fixture:
1. The root's `extra.branch-alias` entry was never materialized in the
pool, and root-level `replace`/`provide`/`conflict` constraints
written as `self.version` were forwarded verbatim. Mirror Composer's
`RootAliasPackage`: resolve `self.version` against the root's
declared version for the base entry, then add an extra alias entry
(carrying the base links plus a duplicate link per `self.version`
original retagged at the alias's version) when the root's version
matches an `extra.branch-alias` key.
2. `Pool::matches_package` returned on the first link to a target name
even when its constraint did not match the query, hiding any later
link to the same target. With the alias above, that masked the
second `replace` link tagged at the alias version. Keep iterating
when target matches but constraint does not, so a later link can
still satisfy.
|
|
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.
|
|
Three coordinated changes to make `update --with-dependencies` produce
the same operation trace Composer emits:
- LockFileGenerationRequest gains a previous_lock field. When a
resolved package matches an entry in the old lock at the same name +
version_normalized, its relationship-shaped fields (require /
require-dev / conflict / replace / provide / suggest) are carried
over verbatim. Source/dist refs and version-shaped fields still
refresh from upstream metadata so dev packages can still pick up new
commits. Without this carry-over, partial updates regenerated lock
entries from upstream COMPOSER repo definitions, which can declare
different requires than the lock — and topological_sort then sees a
graph Composer's transaction never built.
- Transaction's topological_sort and get_root_packages now expand
replace/provide targets when matching `require` links to result
packages, mirroring Composer's getProvidersInResult. Previously a
package was only treated as required when matched by its own name,
so packages reached only via replace/provide were mis-classified as
roots and the DFS stack visited deps in the wrong order.
- compute_operations iterates installed.json in reverse when emitting
removals, mirroring Composer's array_unshift onto operations. Two
co-orphaned packages otherwise emit removals in the wrong order vs
Composer's trace.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Previously requires_for_name returned the lock entry's requires when the
package was already locked, falling back to repo requires only when not.
That missed the case where the resolver would pick a *newer* version of
the locked package that added a new requirement on another locked
package — the new dependency stayed pinned and the upgrade was silently
suppressed. Union both sources so every candidate version's requires
contribute to the unlock cascade.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
`expand_with_direct_dependencies` only walked the lock map, so an
allow-listed package not yet in the lock (a freshly added root require)
contributed nothing to the unlock cascade. The resolver then kept
transitive deps pinned to their lock versions and bailed when the new
package's require could not be satisfied. Mirror Composer's
`PoolBuilder::loadPackage` by also walking inline / composer-repo
require lists for not-yet-locked packages.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Read `config.audit.block-abandoned` from composer.json (defaults to
false) and propagate it to the resolver. When set, the pool builder
skips packages whose `abandoned` field is truthy (`true` or a non-empty
replacement string), matching `SecurityAdvisoryPoolFilter`'s behavior in
`Composer\DependencyResolver`. With no candidates left, a root require
that only matches abandoned versions fails resolution with exit 2.
|
|
Mirror Composer's PoolBuilder/Request semantics for partial updates: each
non-allow-listed locked package becomes a non-fixed pool entry restricted to
its locked version, so `replace`-providing peers cannot silently displace
it. Path-repo packages are exempt — Composer always reloads them from disk.
Threading `--with-dependencies` through `expand_with_direct_dependencies`
now performs transitive expansion with a root-require barrier matching
UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE, so root requires stay
locked when reached via a transitive dep.
Newly green: remove_does_nothing_if_removal_requires_update_of_dep,
update_allow_list_removes_unused, github_issues_4795,
partial_update_with_deps_warns_root.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Mirrors Composer's `RootPackageLoader::extractAliases` +
`PoolBuilder::loadPackage` flow: strip the `as` clause from each root
require so the SAT side sees only the LEFT-hand constraint, and after
every package is loaded run a second pass that materializes an alias
entry for any input matching `(name, version_normalized)`. Locked-only
packages in a partial update are excluded via a new
`ResolveRequest::locked_package_names` so they don't pick up the alias
(`propagateUpdate=false` in Composer).
Two adjacent fixes uncovered while making `install_aliased_alias`
green:
- `Version::cmp` treated unnamed wildcard branches (`1.0.x-dev`,
`is_dev_branch=true && name=None`) as below every numeric version.
They are semantically the same as the four-segment `*-dev` form
Composer's `normalizeBranch` emits, so let only *named* branches
take the shortcut.
- `Constraint::Exact` / `NotEqual` used the derived `==`, which
compared `is_dev_branch` field-by-field and missed the
wildcard/numeric equivalence. Switch to `cmp` so both forms count
as equal.
- `Pool::matches_package` now falls back to parsing `pretty_version`
when the `version` parse doesn't match the constraint, so a
`dev-master` query lines up with a pool entry stored as the
internal `9999999.x.x.x-dev` expansion.
Net effect on installer fixtures: `install_aliased_alias` newly
green, plus `aliased_priority`, `aliased_priority_conflicting`, and
`install_dev_using_dist` come along for the ride.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
The root pool entry now seeded from composer.json carried provides and
replaces but no conflicts, so a root-level conflict like
\`{"some/dep": ">=1.3"}\` was silently dropped. Composer keeps these on
the RootPackage (which lives in the pool via RootPackageRepository), and
the SAT generator turns them into rules that forbid any candidate matching
the constraint — including a branch alias that would resolve to a
matching version. Without that, Mozart cheerfully installs both the
required dev branch and its conflicting alias.
Plumb composer.json's \`conflict\` map through ResolveRequest as
root_conflict and project it onto the root pool entry as PoolLink
conflicts; all callers updated.
Unblocks conflict_on_root_with_alias_prevents_update_if_not_required and
conflict_with_alias_prevents_update installer fixtures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Composer's RootPackageRepository puts a clone of the root package into
the pool as a fixed entry — its `require` / `require-dev` cleared, but
its name, version, provides, and replaces preserved. That way a
transitive `require` pointing back at the root resolves through the
pool the same way any other reference would, and legal circular
dependencies (root requires A, A requires root) work.
Mozart had no such seed: the rule generator only knew about the root
through the explicit root-require / root-provide / root-replace tables,
so a transitive consumer requiring the root by name failed with no
provider.
Plumb root_version through ResolveRequest (RawPackageData gains a
matching `Option<String>` field), build a fixed PoolPackageInput for
the root with provides/replaces lifted from request.root_provide /
root_replace, and skip the root by name when collecting the resolver's
output so it doesn't leak into the lock file. Falls back to
`1.0.0+no-version-set` (Composer's RootPackage::DEFAULT_PRETTY_VERSION)
when the root composer.json omits `version`.
Unblocks circular_dependency2, conflict_against_replaced_package_problem,
and provider_conflicts installer fixtures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Adopt indexmap workspace-wide so iteration order is deterministic and
follows insertion order. The non-deterministic order of std HashMap
otherwise leaks into resolver decisions when multiple valid solutions
exist (e.g. cyclic require pairs under prefer-lowest), making behavior
flaky and divergent from Composer's PHP-array semantics.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Mirrors `Composer\Repository\PlatformRepository`'s `$overrides` handling:
each override either replaces a detected platform package version or adds
a virtual one (e.g. ext-dummy), and `false` disables the package.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Port Composer's RuleSetGenerator::createRequireRule self-fulfilling
branch: when the root composer.json's `provide` or `replace` covers a
name it also requires (with intersecting constraints), skip emitting an
install-one-of rule for that root require. Composer relies on the root
package being a fixed entry in the pool so whatProvides() includes it;
Mozart does not yet add the root to the pool, so the same decision is
made via explicit `root_provide` / `root_replace` tables threaded
through ResolveRequest. Without this, an inline repo package whose name
matches the root's provide was being force-installed.
Fixes installer fixtures `provider_satisfies_its_own_requirement` and
`replacer_satisfies_its_own_requirement`.
|
|
Plumb Composer's `extra.branch-alias` mechanism end-to-end so a dev
branch (e.g. `dev-foobar`) can be installed alongside its numeric alias
(e.g. `3.2.x-dev`) and resolve constraints written against the alias
target.
Concretely:
- `mozart-semver`: stop treating pure-numeric `-dev` as a wildcard
branch — `3.2.9999999.9999999-dev` (the form `normalizeBranch` emits)
now parses as a classical version with `is_dev_branch=false`, so
constraints like `3.2.*` match it.
- `mozart-registry/composer_repo`: load `type: composer` repositories
from `file://` URLs (legacy embedded `packages.json`).
- `mozart-registry/resolver`: emit pool entries in pairs for dev
branches with `extra.branch-alias`, link them via `is_alias_of`, and
apply `@dev`/`@beta` etc. stability suffix flags from root requires.
- `mozart-sat-resolver`: alias rules (`PackageAlias` /
`PackageInverseAlias`) so alias and target install together; alias
packages skipped from same-name conflict indexing.
- `mozart-sat-resolver/policy`: `DefaultPolicy` now honors
`prefer_stable` via Composer's stability-tier comparison.
- `mozart-registry/lockfile`: split resolved set into real packages vs.
alias entries; populate the `aliases[]` block.
- `mozart-registry/installer_executor`: new `MarkAliasInstalled`
operation; `format_full_pretty_version` mirroring
`BasePackage::getFullPrettyVersion` (appends source ref[0..7] for
dev/git packages).
- Test harness rewrites fixture-relative `file://` URLs to absolute
paths.
Newly green fixtures: `install_branch_alias_composer_repo`,
`alias_solver_problems`, `alias_solver_problems2`,
`conflict_with_all_dependencies_option_dont_recommend_to_use_it`,
`unbounded_conflict_does_not_match_default_branch_with_branch_alias`,
`unbounded_conflict_does_not_match_default_branch_with_numeric_branch`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Three coupled changes that bring `compute_operations` + the in-process
trace recorder into byte-parity with Composer's `Transaction::__toString`
output:
- `TraceRecorderExecutor`: emit "Removing X (V)" instead of "Uninstalling
X (V)" — Composer's `UninstallOperation::__toString` uses "Removing".
- `install_from_lock`: run removals before installs/updates to mirror
`Transaction::moveUninstallsToFront`. Both dry-run and real-execution
branches now emit the same prefix order.
- `topological_sort`: replace recursive DFS with the stack-based DFS that
Composer uses in `Transaction::calculateOperations`. Roots are seeded
reverse-alphabetically (matching `setResultPackageMaps`'s uasort with
`strcmp(b, a)`), and `getProvidersInResult` is mirrored by treating a
package's `provide`/`replace` keys as additional name targets when
resolving a `require` link.
To make the third change work end-to-end, `LockedPackage` gains typed
`provide` and `replace` fields (Composer's lock preserves them; Mozart
was silently dropping them). `packagist_version_to_locked_package` now
copies them through.
Unignores 13 installer fixtures (10 newly green from the fix, 3 that
were already green-but-still-flagged): conflict_downgrade_nested,
install_from_lock_removes_package, install_security_advisory_matching_dependency,
load_replaced_package_if_replacer_dropped, partial_update_keeps_older_dep_*
(×2), partial_update_security_advisory_matching_locked_dep,
provider_packages_can_be_installed_together_with_provided_if_both_installable,
remove_deletes_unused_deps, replace_priorities,
update_allow_list_require_new_replace,
update_allow_list_with_dependencies_require_new_replace,
update_requiring_decision_reverts_and_learning_positive_literals.
Installer scoreboard: 75/187 → 88/187.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Replaces the spawn-based runner in tests/installer.rs with the
in-process harness from Step E. Every fixture now goes through
mozart::commands::{install,update}::run with an empty RepositorySet
(Composer's `'packagist' => false` test config) and a
TraceRecorderExecutor (Composer's InstallationManagerMock), and the
EXPECT section is now asserted against the recorder's trace -
load-bearing for behavior parity, not just exit-code.
The original CI failure (suggest_replaced) is now legitimately tested:
the empty RepositorySet makes b/b unreachable just like Composer's
test config, the inline package repo's eager preload finds c/c which
replaces b/b, and the topological install order in compute_operations
produces the c/c -> a/a trace the fixture pins.
Strict trace assertion surfaced 60 Mozart-vs-Composer divergences that
the exit-code-only spawn runner had been silently ignoring. Each is
marked `installer_fixture\!(name, ignore)` for now; the categories
break down roughly as:
- alias handling (alias_in_lock2, install_aliased_alias, update_alias*)
- replace / provider trace shape (replace_priorities,
provider_satisfies_its_own_requirement, replacer_*)
- update direction strings (update_changes_url, update_reference,
update_dev_*)
- partial-update + lock interactions (partial_update_*)
- allow-list with replace/dependency interactions
(update_allow_list_with_dependencies_require_new*)
These each become individual follow-up Mozart bugs rather than mass
silent-pass.
Also marks prefer_lowest_branches as ignore: it's a real flake driven
by HashSet iteration order in the resolver, where two equivalent
candidates can be picked in either order. That's a separate
determinism bug worth its own fix.
The proxy-hack env-vars in mozart-test-harness::runner are removed -
no test currently spawns the binary, and the in-process harness
expresses Packagist disablement directly via RepositorySet::empty
rather than relying on TCP failure to suppress network calls.
Headline numbers: 75 passed (in-process, exit-code + EXPECT trace) +
112 ignored, vs prior 136 passed (spawn, exit-code only) + 51 ignored.
The drop in passing count reflects the stricter assertion bar, not
new regressions.
Also removes tests/installer_in_process.rs - its single proof-of-
concept fixture (suggest_replaced) is now part of the unified
installer.rs harness.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Carve commands::install::execute and commands::update::execute into thin
CLI-arg-driven wrappers + run() entry points that take (working_dir,
args, console, repositories, executor) directly. The wrappers build a
production RepositorySet (Packagist) + FilesystemExecutor from cli, then
dispatch to run; in-process tests will call run directly with an empty
RepositorySet (Composer's `'packagist' => false` test config) and a
tracing InstallerExecutor.
The install -> update fallback (no composer.lock present) now goes
through update::run, forwarding the caller's repositories + executor so
test mocks survive the edge.
Also drop the now-dead InstallConfig::no_cache field — install_from_lock
stopped consuming the cache when FilesystemExecutor was extracted in the
earlier DI plumbing pass, so the field has no effect.
All 136 enabled installer fixtures + 114 mozart-registry tests + 541
mozart lib tests still green; clippy clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
ResolveRequest and LockFileGenerationRequest now take Arc<RepositorySet>
instead of a raw Cache. install_from_lock now accepts &mut dyn
InstallerExecutor instead of constructing FilesystemExecutor internally.
Both changes expose the DI injection points needed by the upcoming
in-process test harness, where Packagist must be replaced with an empty
RepositorySet (Composer's `'packagist' => false` test config) and
filesystem install execution must be replaced with a tracing recorder
(Composer's InstallationManagerMock).
The eager VCS scan and inline-package preload still happen inside
resolve(), so the RawRepository array is kept on ResolveRequest as
raw_repositories - migrating those through RepositorySet remains a
follow-up. RepositorySet gains with_packagist and empty constructors so
production callers and future tests have a uniform construction shape.
All 136 enabled installer fixtures + 114 mozart-registry tests + 541
mozart lib tests still green; clippy clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
`apply_partial_update` and `apply_patch_only` both pinned non-listed
packages back to the lock by copying `LockedPackage.version_normalized`
verbatim, falling back to the raw pretty `version` when the field was
missing. Lock files written by Composer always include the field, but
hand-written fixtures (every `--LOCK--` block in the installer
fixtures, in particular) typically only carry `version`. The 3-segment
form ("1.0.0") then leaked into the resolved package, where
`LockFileGenerationRequest::inline_lookup` compares against the
4-segment normalizer output ("1.0.0.0") and missed inline `type:
package` entries — triggering a Packagist fetch (and proxy-blocked
failure under the test harness) for a package that should never need
one.
Extract a single `locked_version_normalized` helper that runs the
pretty version through `mozart_semver::Version::parse(...).to_string()`
when the lock omits `version_normalized`, and use it from both call
sites. Mirrors `packagist_to_pool_inputs` and `inline_lookup`, which
already produce the 4-segment form.
Unblocks 26 installer fixtures: the entire update-allow-list cluster
(16, minus the alias subcase), five partial-update cases, and five
others (full-update-minimal-changes, load-replaced-package-if-replacer-dropped,
remove-deletes-unused-deps, remove-does-nothing-if-removal-requires-update-of-dep,
update-changes-url). Scoreboard: 107 → 133 of 187 installer fixtures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Mirrors Composer\Package\Loader\RootPackageLoader::load(): if the root
package's "name" appears as a key in its own "require" or "require-dev"
map, fail loudly before reaching the resolver. Without this, Mozart
would silently let the request hit Packagist (which has no entry for
the root's vendor/name) and report a misleading "could not be found"
error.
Wired into install::execute (when a lock file is present) and
update::execute (the no-lock fallback path). Carries the same wording
as Composer's RuntimeException so a future EXPECT-OUTPUT comparison
will match.
Also extends the installer test harness: when a fixture sets
EXPECT-EXCEPTION but no EXPECT-EXIT-CODE, assert that Mozart exits
non-zero. Full exception-class matching remains a follow-up (see
.ken/test_design.md §7.2).
Closes the gap exercised by the install-self-from-root installer
fixture.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
files_cache was Option<&Cache> and install_from_lock always passed None,
so downloaded zip/tar archives were never cached. Make the parameter
non-optional (&Cache) and wire it through every command that downloads
dist archives (install, update, require, remove, create-project,
archive). The Cache internally respects --no-cache via its enabled flag,
so the Option wrapper was unnecessary.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
Remove the Option wrapper from repo_cache in ResolveRequest,
LockFileGenerationRequest, and fetch_package_versions. All commands
now initialize a Cache via build_cache_config(cli.no_cache), ensuring
Packagist metadata is cached to disk (respecting --no-cache flag).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
Replace direct println\!/eprintln\! calls with console.write(),
console.info(), and console.write_stdout() across all command
handlers to respect verbosity settings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
- Migrate eprintln\! to Console for consistent colored output
- Use Composer terminology in lock file operations: Locking instead of
Installing, Upgrading/Downgrading instead of Updating
- Add is_downgrade() helper to distinguish upgrades from downgrades
- Pass Console through install_from_lock for proper output handling
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
Implement VCS driver/downloader infrastructure mirroring Composer's VCS
subsystem. Includes drivers for GitHub, GitLab, Bitbucket, Forgejo, Git,
Hg, and SVN with API-based metadata resolution, plus source downloaders
for Git/Hg/SVN. Integrates into mozart-registry via vcs_bridge module to
scan VCS repositories and feed discovered packages into the SAT resolver.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
passthrough
- Parse and apply --with temporary constraints to the resolver
- Support inline constraint shorthand (vendor/pkg:1.0.*)
- Reject --lock combined with specific package names
- Filter magic keywords (lock/nothing/mirrors) from package list
- Pass APCu CLI flags through to InstallConfig instead of hardcoding
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
apcu-prefix implicit enable
- Restrict --prefer-install to source/dist/auto and --audit-format to
table/plain/json/summary via clap value_parser
- Error when --prefer-install is combined with --prefer-source/--prefer-dist
- Wire --download-only through InstallConfig to skip autoloader and installed.json
- Implicitly enable --apcu-autoloader when --apcu-autoloader-prefix is set
- Apply same validation fixes to update, require, remove, create-project commands
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
Introduce a Symfony Console-style tag macro that replaces verbose
patterns like `console::info(&format!("text {name}"))` with
`console_format!("<info>text {name}</info>")`. Supports all 6 tag
types (info, comment, error, question, highlight, warning) with
format argument distribution across multiple tagged segments.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
- "Nothing to bump." → "No requirements to update in <path>."
- "N constraint(s) bumped successfully." → "<path> has been updated (N changes)."
- Dry-run now shows "<path> would be updated with:" followed by
" - require.<pkg>: <ver>" per change, matching Composer's format
- Also update bump message in update command for consistency
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
|
|
- --patch-only: restrict updates to patch-level changes by pinning
packages back to locked versions when major.minor differs
- --root-reqs: auto-populate update list with root require/require-dev
packages when no explicit packages are specified
- --bump-after-update: bump composer.json version constraints to match
resolved versions after update, with dev/no-dev/all modes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
Composer never shows the internal __root__ identifier to users. Add
root_name field to ResolveRequest so the resolver can substitute the
real package name (e.g. "laravel/laravel") in pubgrub error reports.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
|
|
Replace reqwest::blocking with async reqwest across the entire codebase.
All command execute functions, registry API calls (packagist, downloader,
resolver, lockfile), and the main entry point now use async/await with
the tokio runtime. The pubgrub resolver runs on spawn_blocking since its
DependencyProvider trait is synchronous, using Handle::block_on for
async I/O within that context.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
Extract modules from the single `mozart` crate into 5 focused library
crates to improve compilation parallelism and architectural clarity:
- mozart-constraint: version constraint parser (independent)
- mozart-core: base types, console, validation, platform utilities
- mozart-archiver: archive creation (tar, zip, bzip2)
- mozart-registry: Packagist API, cache, resolver, downloader, lockfile
- mozart-autoload: autoloader generation and PHP scanner
Refactor Console::from_cli and build_cache_config to accept primitive
args instead of &Cli to break circular dependencies. Introduce
[workspace.dependencies] for centralized version management. Remove 9
unused direct dependencies from the CLI crate.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
Implement Phase 7.2 error handling & UX infrastructure:
- Add exit_code module with MozartError, bail()/bail_silent() helpers,
and Composer-compatible exit code constants (0-5, 100)
- Redesign Console struct with Verbosity enum (Quiet/Normal/Verbose/
VeryVerbose/Debug), ANSI auto-detection via IsTerminal, and
verbosity-gated output methods (info/verbose/debug/error)
- Thread Console through all 33 command execute() signatures
- Replace all std::process::exit() calls with structured MozartError
returns handled in main()
- Migrate eprintln\! status messages to console.info() for quiet-mode
suppression
- Add suggest module with Levenshtein distance and "Did you mean?"
formatting for future package name suggestions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
Implement a cache module with CacheConfig and Cache structs supporting
read/write (string and binary), atomic writes via temp+rename, TTL-based
expiration, and size-limited garbage collection. Wire the repo cache into
packagist.rs and resolver.rs for API response caching, and the files
cache into downloader.rs for dist archive caching. Implement the
clear-cache command with full clear and --gc modes. All existing call
sites pass None for backward compatibility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|