| Age | Commit message (Collapse) | Author |
|
Composer needs DownloadManager (from mozart-registry), but mozart-core
sits below mozart-registry in the dependency graph — adding the field
would create a dependency cycle. Moving Composer and create_composer to
the mozart CLI crate breaks the cycle and lets the root state container
hold a DownloadManager.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
module
Removes the per-command duplicate implementations of
get_platform_requirement_filter from dump_autoload and reinstall,
and lifts a single canonical version into commands.rs.
|
|
Eliminate the nested &console_format!(...) boilerplate at every call site
by teaching console_writeln!, console_write!, console_writeln_error!, and
console_write_error! to accept a format literal + variadic args directly,
matching the println!/eprintln! ergonomics. Propagate the format string
span into generated code so rustc errors point to the right location.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- A3: wire --major-only/--minor-only/--patch-only version constraint filtering
in fetch_latest_for_package (mirrors ShowCommand::findLatestPackage 1500-1515)
- A4: implement --sort-by-age ordering by release date
- A5: add abandoned-package warning after each list row
- A6: print color legend before list when --latest is on; ASCII markers
(\! ~ =) in non-decorated mode
- A7: split list into "Direct dependencies" / "Transitive dependencies"
sections when --latest && \!--direct (mirrors Composer 671-695)
- A8: replace local is_platform_package with mozart_core::platform::is_platform_package
- A9: emit --installed deprecation warning (mirrors Composer 143-145)
- A10: apply --strict exit-code check in both installed and locked paths
- A11: fetch and display latest: line in single-package detail view
- A12: add conflict/provide/replace link sections to detail view
- A13: add released/names/support/autoload fields to detail view
- A14: use "locked" JSON key for --locked output (was incorrectly "installed")
- A15: unify show_installed_package_detail + show_locked_package_detail into
a single PackageDetail struct + print_package_detail function
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
InstallCommand pipeline
- Match Composer's deprecation wording for --dev and --no-suggest (A1)
- Add missing URL to lock-fallback warning (A2)
- Reorder arg validation to match InstallCommand::execute order (A3)
- Rename collect_install_* helpers to verify_lock_* and consolidate
into a single verify_lock() entry point (A4)
- Couple classmap_authoritative=true → optimize_autoloader=true,
mirroring Composer's setter side-effects (Installer.php 1263-1272) (A5)
- Centralise is_platform_package in mozart_core::platform, removing
divergent local copies from install.rs and update.rs (A6)
- Move compute_operations, topological_sort, alias helpers, and
locked_to_installed_entry into mozart-registry::installer_executor::
transaction, re-exported from the parent module (A8)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Implement std::str::FromStr for AuditFormat and AbandonedHandling
instead of ad-hoc from_str methods (resolves should_implement_trait).
Group Auditor::audit() parameters into AuditOptions to resolve
too_many_arguments.
|
|
- Add mozart-registry::version_selector::VersionSelector mirroring
Composer\Package\Version\VersionSelector; wraps find_best_candidate
and find_recommended_require_version_string for per-arg resolution
- Decompose execute() into named helpers matching Composer's structure:
CommandState, revert_composer_file, get_inconsistent_require_keys,
get_packages_by_require_key, update_file, update_file_cleanly (stub
for PR 3), do_update, update_requirements_after_resolution (stub for PR 2)
- Fix firstRequire gating: compute first_require from the original file
before applying changes; apply setUpdateAllowList only when
!first_require and lock exists (prevents over-pinning on fresh projects)
- Add --fixed gate: bail when fixed && !dev && type != "project",
matching Composer L173-189 wording verbatim
- Wire --no-security-blocking + COMPOSER_NO_SECURITY_BLOCKING env var
into block_insecure in ResolveRequest (was always false)
- Wire COMPOSER_NO_AUDIT env var to skip audit step (tracked)
- Match Composer's revertComposerFile messaging: "deleting <file>" for
newly-created, "reverting <file> and <lock> to their" / "to its" for
existing files; also removes lock file on newly-created revert
- Auto-create "{\n}\n" when composer.json is missing or empty, mirroring
Composer L138-152; delete file on dry-run cleanup (finally block)
- Add resolution-failure hint: "You can also try re-running mozart
require with an explicit version constraint…" for unversioned packages
- Update deprecated-flag warnings to stderr (write_error) with
Composer-matching wording for --no-suggest
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Add mozart-core::advisory::{AuditFormat, AbandonedHandling, AuditConfig}
mirroring Composer\Advisory\AuditConfig; reads audit.ignore,
audit.ignore-severity, audit.ignore-abandoned, audit.abandoned,
audit.block-insecure, audit.block-abandoned, audit.ignore-unreachable
from composer.json config with full apply-scope support
- Add mozart-registry::advisory::Auditor mirroring Composer\Advisory\Auditor;
process_advisories() filters by package name, advisory ID, CVE, source
remote ID, and severity; filter_abandoned_packages() respects ignore-abandoned
- Add RepositorySet::get_matching_security_advisories() wrapping
fetch_security_advisories with version-matching and unreachable-repo tracking
- JSON output now includes ignored-advisories and unreachable-repositories keys
- --abandoned falls back to audit.abandoned config (was hardcoded to "fail")
- --ignore-severity merges with audit.ignore-severity config
- --ignore-unreachable ORs with audit.ignore-unreachable config
- Move normalize_or_separator into repository/mod.rs alongside version matching
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Port `Composer\Installer\SuggestedPackagesReporter` to
`mozart_core::installer` (modes, add_package, add_suggestions_from_package,
output, output_minimalistic, escape_output) and slim
`commands/suggests.rs` to mirror `SuggestsCommand::execute`. Defines
`HasSuggests`, `InstalledRepoLite`, `RootInfo` as the minimal stand-ins
for Composer's `PackageInterface` / `InstalledRepository` /
`onlyDependentsOf`.
Also fixes a latent bug where `provide`/`replace` virtuals were read
from `extra_fields` (always empty after a serde round-trip into
LockedPackage's typed fields) and moves the "additional suggestions
... --all" hint to fire after the rendered sections, matching
Composer's order.
|
|
Replace the dist-hash tree-diff implementation with Composer's VCS-level
status flow: three buckets (errors / unpushed_changes / vcs_version_changes)
populated via ChangeReportInterface / DvcsDownloaderInterface /
VcsCapableDownloaderInterface, and a bitfield exit code (1|2|4) instead
of always 1.
Supporting work:
- mozart-semver: add normalize_branch (VersionParser::normalizeBranch).
- mozart-vcs: extend VcsDownloader trait with unpushed_changes /
vcs_reference; port GitDownloader::getUnpushedChanges (HEAD-ref
discovery + git diff --name-status remote...branch + two-pass fetch);
fix git status invocation to use --untracked-files=no (Composer parity);
add hasMetadataRepository preconditions to git/hg/svn local_changes;
port VersionGuesser (git/hg/svn dispatch — Fossil omitted, feature
branch detection runs sequentially instead of via async promises).
- mozart-core: extend LocalPackage with pretty_version, package_type,
installation_source, source, dist, extra; add InstallationSource and
PackageReference. factory.rs reads them from installed.json.
- mozart-registry: new download_manager mirroring
DownloadManager::getDownloaderForPackage.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Replace the HTTP-only post-filtered implementation with a Repository::search
trait dispatch that mirrors ComposerRepository::search semantics for all
three modes (FULLTEXT/NAME/VENDOR). --only-name now does an OR-of-tokens
regex match against the full Packagist list.json index instead of a
substring match against a fulltext page, so e.g. \`mozart search --only-name
mono log\` matches \`monolog/monolog\` like Composer does. Other parity
fixes: regex::escape on non-fulltext queries, format check before mutex
check, 4-space JSON indent, OSC 8 terminal hyperlink emission when a
result has a url, <warning>\! Abandoned \!</warning> styling on abandoned
rows, and the Mozart-only "No packages found" warning is dropped to match
Composer's silent empty-result behavior.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Replace the hand-rolled composer.json -> composer.lock -> Packagist
fallback with a BrowseRepos composite that dispatches via a uniform
find_packages(name) over the root package, the local installed
repository, and the Packagist remote -- matching HomeCommand's
initializeRepos() + findPackages() loop.
- Extend InstalledPackageEntry with homepage/support so the local repo
carries the same fields HomeCommand reads off
CompletePackageInterface; propagate them through
locked_to_installed_entry.
- Collapse three extract_url_from_* helpers into a single
handle_package mirror.
- Relax is_valid_url to a filter_var(FILTER_VALIDATE_URL) analog (drop
the http/https scheme allowlist).
- Route warnings and "No package specified" notices to stderr; match
HomeCommand's exact wording.
- Merge the macOS/Linux open_browser branches; add the literal "web"
window-title argument on Windows.
|
|
--no-cache redirects COMPOSER_CACHE_DIR to /dev/null (mirrors Application::doRun).
cache_files_maxsize is now u64 with a "300MiB"-style string deserializer.
Cache::new() takes readonly instead of enabled; is_usable() detects null devices.
|
|
|
|
Composer's clear-cache deletes cache-vcs-dir alongside repo/files
caches, and GCs it via gcVcsCache (TTL-based, top-level subdir
deletion, no size cap). Mozart was silently leaving VCS mirrors
on disk forever — every clear-cache run grew the cache monotonically.
Add cache_vcs_dir to CacheConfig (default {cache-dir}/vcs, env
override COMPOSER_CACHE_VCS_DIR), wire it into clear-cache, and
add Cache::gc_vcs mirroring Composer's gcVcsCache semantics.
|
|
Composer's config.cafile/config.capath were accepted by the config
command but ignored by every HTTP request. Centralize reqwest client
construction in mozart_core::http, pre-load the configured CA bundle
at startup, and route every callsite (registry, vcs drivers, diagnose,
self-update) through the shared builder so user-supplied roots are
actually used during HTTPS verification.
|
|
ArrayDumper emits `default-branch: true` into the lock for any package
that came from a default branch, and ArrayLoader reads it back to
synthesize the `9999999-dev` alias inside Locker::getLockedRepository.
Mozart was dropping the flag in two places: packagist_version_to_locked_package
ignored pv.default_branch when building the lock entry, and
locked_package_to_packagist_version hardcoded default_branch=false when
re-hydrating a lock-pinned package's metadata for partial updates. The
result was that a non-allow-listed default-branch dev package (e.g. f/f
in update-changes-url) ended up in the new lock without the marker, so
collect_stale_installed_aliases thought its `9999999-dev` alias had
been retired and emitted a spurious MarkAliasUninstalled trace.
|
|
Composer's Locker::getLockedRepository runs each locked package through
ArrayLoader::load, which materializes any extra.branch-alias as a
separate AliasPackage in the locked repository. Mozart was only adding
the base locked package to the pool, so a `dev-master` locked entry
with branch alias `2.2.x-dev` was invisible to numeric root constraints
like `~2.1` on a partial update — the resolver bailed with "no matching
package found" even though Composer accepts the same lock. Surface
each branch-alias as a sibling pool entry pointing at the base via
is_alias_of.
|
|
Composer's VersionParser::normalize maps `master`/`trunk`/`default`
(with or without `dev-` prefix) to `dev-NAME`, not `9999999-dev`. Mozart
was emitting the four-segment 9999999 form for these atoms in
normalize_root_alias_atom, so a root require like `dev-master as 1.1.0`
recorded its target as `9999999.9999999.9999999.9999999-dev` and never
matched the pool's `dev-master` entry — the alias was silently dropped
and transitive `^1.1` requires couldn't see the materialized 1.1.0 alias.
|
|
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>
|
|
Mirror Composer's `AliasPackage::replaceSelfVersionDependencies`: a base
package's `replace` / `provide` / `conflict` link whose constraint
matches the base's own version (the resolved form of `self.version`) is
duplicated on the alias at the alias's version. A root require like
`a/aliased: dev-next as 4.1.0-RC2` paired with `replace: { foo:
self.version }` previously left the alias with a `dev-next` constraint,
so a transitive `foo ^4.0` requirement saw no numeric provider and the
solver bailed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Mirror Composer's RootPackageLoader::extractAliases regex so root
requires like `1.*||dev-feature-foo as 1.0.2||^2` and
`dev-feature-foo, dev-feature-foo as 1.0.2` get every `<X> as <Y>`
clause stripped in place and recorded as a separate root alias entry.
The previous single-atom strip left the alias inline, where the parser
then took the RIGHT side per atom and never matched the actual
dev-branch package.
Also fix split_and so a comma-separated AND group like
`dev-foo, dev-bar` splits into two atoms. The space-only operator-glue
heuristic was collapsing it into a single atom because neither half
starts with an operator or digit. Splitting on commas first preserves
the unambiguous separator while keeping `>= 1.0.0` glued within each
comma-part.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Mirror Composer's PoolBuilder::markPackageNameForLoading: when the root
requires a name with a version constraint, loads of that name (seed and
transitive) are filtered down to candidates whose own version (or any
emitted branch-alias version) satisfies the constraint. Without this,
the actual package at a non-matching version slips into the pool
alongside a provider satisfying the root require, masking what should
be a conflict (provider-gets-picked-together-with-other-version-of-
provided-conflict.test).
Also restore the Composer v1 compat path in inline_package: when the
JSON sets version_normalized to the legacy 9999999-dev sentinel,
re-normalize from the human-readable version field so a root require
for `dev-master` matches the loaded package.
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.
|
|
Two related parity gaps surfaced by the `alias-with-reference` fixture:
1. A root require like `dev-main#abcd as 1.0.0` left the SAT-side
constraint as `dev-main#abcd`, which no candidate matched, so
resolution failed before the alias could be materialized. Mirror
Composer's `extractAliases` regex (which captures only the
constraint up to `#`) and `RootPackageLoader::extractReferences`
(which records the hash separately): drop the trailing `#hex` from
the resolver-side constraint and from the alias's left-hand side.
Lockfile generation already pulls the reference back out of the
raw require map for the post-resolve override.
2. `MarkAliasInstalled`'s trace line gated the reference suffix on
the *target* package's stability, so a stable alias like `1.0.0`
pointing at a dev-branch target rendered as `1.0.0 abcd`. Mirror
`AliasPackage::getFullPrettyVersion`: the alias decides on its own
whether to append the suffix based on its own normalized version,
so a stable alias skips the suffix even when the target is dev.
|
|
A partial update reuses every non-allow-listed locked package as a
fixed pool entry, ignoring stability filters. So when a user tightens
`minimum-stability` (or drops a `stability-flags` entry the lock used
to ride on), Mozart silently kept the rejected version and produced a
plan Composer would have failed on. Mirror Composer's
`Pool::isUnacceptableFixedOrLockedPackage` path: walk the locked
packages before pool construction, surface every entry whose version
no longer passes `passes_stability_filter`, and bail with the same
"fixed to <v> (lock file version) ... rejected by your
minimum-stability" pointer Composer prints from
`Problem::getPrettyString`.
|
|
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.
|
|
`packagist_version_to_locked_package` was forwarding only `extra` and
`notification-url` into `extra_fields`, so an `abandoned: "<replacement>"`
declared in the package metadata never reached the lock. The same-version
update detector then saw the lock and installed.json agreeing on
"not abandoned" and skipped the resync, leaving the deprecation state
stale on disk. Emit the field when truthy (string or `true`), matching
Composer's `ArrayDumper::dump`.
|
|
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>
|
|
Composer's FilterRepository wraps a repository with three knobs:
`only` / `exclude` to drop packages by name, and `canonical: false` to
relax the repo's authoritative claim on its package names so
lower-priority repos can still answer. Mozart was ignoring all three,
so first-listed inline / composer-repo entries always shadowed later
repos and `only` / `exclude` lists were silently no-ops.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Composer's `Transaction::calculateOperations` only emits a
MarkAliasInstalledOperation when the alias isn't already in
`presentAliasMap`. Mirror that here: walk installed.json for each
package being installed/updated, recover its prior alias set (explicit
`extra.branch-alias` entries plus the synthetic `9999999-dev` alias for
`default-branch: true` dev packages), and suppress the trace line when
the new lock's alias normalized version was already there. Avoids the
spurious "Marking ... as installed" emitted on a same-alias dev ref bump.
|
|
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 `Locker::getLockedRepository` flow when validating that
every root require is satisfied by the lock and when emitting trace
operations: a `dev-*` package's `extra.branch-alias` entry surfaces an
AliasPackage at the alias version, so requirement matching considers
that version too and `MarkAliasInstalled` fires for the branch-alias
when the lock has no matching `aliases[]` entry. Dedupe by
`alias_normalized` so packages aliased through both sources don't get
two trace lines.
|
|
Mirror Composer's PackageRepository (extends ArrayRepository) which only
emits packages whose own name matches a queried name. Eager-loading every
inline package let the SAT resolver pick a replacer that nothing required
by name, masking broken transitive dependencies.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
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>
|
|
Two pieces of Composer's update-trace machinery were missing:
1. VersionParser::isUpgrade in Composer\Package\Version (which overrides
the upstream Semver one) substitutes dev-master / dev-trunk /
dev-default with the 9999999-dev default-branch alias, then returns
true whenever either side starts with `dev-`. Mozart's is_upgrade
compared via the generic version order, so dev-master → dev-foo came
out as Downgrading. Port the override.
2. Transaction::calculateOperations seeds removeAliasMap from the
currently-installed AliasPackages and emits MarkAliasUninstalled for
every entry not covered by the new lock. Mozart never emitted those,
so updating away from a branch-aliased package produced no trace
line for the alias retirement. Walk installed.json's
`extra.branch-alias` map, compare against the new lock's aliases[]
block, and emit a MarkAliasUninstalled PackageOperation (a new
variant on the executor surface — no filesystem effects, only the
trace recorder cares).
Unblocks update_alias, update_alias_lock2, and update_no_dev_still_resolves_dev
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>
|
|
Composer's UpdateOperation::format renders the from/to versions through
DISPLAY_SOURCE_REF_IF_DEV first, but if both sides come out identical it
re-renders in DISPLAY_SOURCE_REF (when source refs differ) or
DISPLAY_DIST_REF (when only dist refs differ) so the trace doesn't show
a useless `pkg (X => X)` line. Mozart skipped the switch and emitted the
default form on both halves, so a same-version-different-dist-ref update
showed up as `dev-master def000 => dev-master def000` instead of
`dev-master def000 => dev-master`.
Add format_update_pretty_versions to render the pair Composer's way and
plumb the resolved to_full_pretty through PackageOperation::Update so
the trace recorder uses it verbatim.
Unblocks update_installed_reference and update_picks_up_change_of_vcs_type
installer fixtures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Composer's Transaction fires an UpdateOperation when an installed
package's source/dist reference moved, even if the version string is
unchanged — that is how a `dev-main#abcd` root require pinning a new
commit propagates through `composer install`. Mozart was checking only
(name, version) and short-circuiting to Skip, so the package stayed
pinned to whatever reference installed.json carried.
Compare references in compute_operations and route mismatches into
Action::Update. The trace recorder needs the from-side display string
to include the reference suffix (`dev-master abc123`) so the EXPECT
output matches Composer's UpdateOperation::format; thread that through
PackageOperation::Update as a separate from_full_pretty field while
keeping from_version (sans suffix) for the upgrade-vs-downgrade
direction check, which has to compare normalized versions like
Composer's VersionParser::isUpgrade does.
Unblocks update_reference, update_reference_picks_latest, and
updating_dev_updates_url_and_reference 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>
|
|
Mirror Composer's `Solver::checkForRootRequireProblems`: a root require
that resolves to zero pool providers produces no SAT rule, so the
solver previously succeeded with an empty plan instead of reporting
the unresolvable requirement. `RuleSetGenerator::generate` now returns
those misses alongside the rule set, and `resolve()` short-circuits
into `ResolveError::NoSolution` so install/update exit with code 2 to
match Composer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
When a `type: package` repository declares a name already declared by a
higher-priority `type: package` entry, drop it. Mirrors Composer's
RepositorySet first-repo-wins semantics so duplicate names across inline
repositories cannot promote a lower-priority version into the pool.
Greens 4 installer fixtures: install_prefers_repos_over_package_versions,
repositories_priorities2, repositories_priorities4,
update_package_present_in_lower_repo_prio_but_not_main_due_to_min_stability.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Composer's ArrayLoader and AliasPackage rewrite "self.version" to the
declaring package's own version when building Link objects, so a
package's replace/provide/conflict/require constraints carry a concrete
"= <version>" rather than the literal string. Mozart was passing
"self.version" through verbatim, which then failed to parse in
Pool::matches_package and caused replace_alias.test to fail to find a
provider for c/c 1.* via a/a's branch alias.
Push the substitution into make_pool_links, threading the source
package's normalized version through the call sites in resolver.rs
(packagist/inline/composer-repo) and vcs_bridge.rs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|