| Age | Commit message (Collapse) | Author |
|
Mirror Composer's DumpAutoloadCommand by scanning the local repository
for packages whose install path is absent on disk, emitting the same
"Not all dependencies are installed" warning, and returning exit code 1
when any are missing. Also reorders the surrounding flow to follow
Composer's command sequence.
|
|
|
|
Introduce mozart_core::composer::Composer with require()/try_load()
constructors and a config() accessor, modelled on PHP Composer's
BaseCommand::requireComposer / tryComposer. ComposerConfig and
composer_home move into mozart-core so Composer::load can resolve
placeholders consistently.
Migrate dump-autoload, archive, exec and run-script away from ad-hoc
composer.json reads. exec and run-script now fail when composer.json
is missing instead of silently falling back to "vendor/bin",
matching the upstream requireComposer contract.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
|
|
Previously each VCS mirror was keyed by sha1(url), which made
cache directories opaque and incompatible with Composer's layout.
Composer's GitDriver and GitDownloader both use the form
Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize(\$url)), so a
Mozart user migrating from Composer (or vice versa) could not
share an existing cache.
Reimplement GitUtil::sanitize_url to follow that pattern: redact
credentials and access tokens (Url::sanitize semantics, including
the GitHub token regex), then replace every byte outside [a-zA-Z0-9.]
with '-'. The credential redaction also collapses URLs that differ
only in their access_token to the same key.
|
|
DriverConfig used to expose a generic cache_dir hardcoded to a
relative ".cache/mozart/vcs", with each driver appending its own
"git"/"hg" subdir. As a result, the cache-vcs-dir setting (and
COMPOSER_CACHE_VCS_DIR env var) had no effect on where mirrors
were actually stored.
Replace cache_dir with cache_vcs_dir, resolving its default the
same way Composer does (COMPOSER_CACHE_VCS_DIR → COMPOSER_CACHE_DIR/vcs
→ XDG/HOME fallbacks), and have GitDriver/HgDriver use it directly.
This brings the Mozart cache layout in line with Composer's: a single
shared vcs root with one subdirectory per sanitized URL.
|
|
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.
|
|
Mirror composer's BaseDependencyCommand::doExecute by collapsing the
duplicated working-dir/load/lookup/print pipeline in depends.rs and
prohibits.rs into a single dependency::do_execute helper driven by an
`inverted` flag. The clap arg structs stay in their per-command modules
and just forward to the shared entry point.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
|
|
Composer anchors its license expression regex with `^...$`, but Mozart's
parser tokenizer silently skipped edge whitespace, accepting inputs like
" MIT" or "MIT\t". Mirror Composer by rejecting edge whitespace before
parsing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
|
|
Mirror Composer's Command\ShowCommand::printLicenses(): emit one line
per license identifier, expanded to "<full name> (<id>) [(OSI
approved)] <url>" when the id is in the SPDX database, or just the id
otherwise. The URL is built by mozart-spdx-licenses' new
LicenseInfo::url() so the construction lives in the SPDX crate, like
Composer's SpdxLicenses::getLicenseByIdentifier().
|
|
Mirror Composer's Package\Loader\ValidatingArrayLoader::load() license
block: warn on non-string/wrong-shape values, validate the SPDX
expression with proprietary→MIT substitution, and surface "extra
spaces" diagnostics. Validity is gated on the manifest's `time` field
(checked only for releases without a date or within the last 8 days),
mirroring Composer's strtotime('-8days') window.
|
|
Mirror Composer's Util\ConfigValidator::validate() license handling:
treat empty string and empty array as missing, accept array form, and
emit deprecation warnings (with GPL-specific -only/-or-later
suggestions) for identifiers flagged deprecated in the SPDX database.
|
|
|
|
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.
|
|
|
|
Port the 31 .test fixtures under
composer/tests/Composer/Test/DependencyResolver/Fixtures/poolbuilder/
as #[ignore]'d cases in mozart-registry/tests/poolbuilder.rs. Each
fixture is parsed eagerly so format-level regressions surface
immediately, while the runner itself is unimplemented\!() — removing
#[ignore] from a case will force the missing pool-build entry point
into existence rather than silently mis-run. Generalize
mozart-test-harness's split_sections to take a per-format valid-section
list and add a poolbuilder parser alongside the installer one.
|
|
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>
|
|
Three Composer-compat bugs surfaced by the github_issues_9290 fixture,
fixed together since they form one resolution path:
- RawPackageData.repositories now accepts a JSON object keyed by name,
matching RepositoryFactory::createRepos which iterates either int-
or string-keyed arrays via PHP foreach.
- Version::parse fills every unspecified position of a `.x-dev` branch
with 9999999, mirroring VersionParser::normalizeBranch. Previously
`2.x-dev` parsed to 2.0.9999999.9999999-dev and failed to satisfy
^2.8.
- is_platform_package limits the `php-` family to the closed set
{64bit,ipv6,zts,debug} per PLATFORM_PACKAGE_REGEX. Vendor packages
like `php-http/client-common` are no longer misclassified.
Unblocks github_issues_7051, _8903, _9012, _9290.
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>
|
|
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>
|
|
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.
|
|
`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.
|
|
Mozart's install verification didn't surface the slice of Composer's
SAT-verify failure where a locked package's `require` targets the
current root by name but the root's `version` no longer satisfies the
declared constraint (e.g. lock has `b/requirer` requiring `root/pkg
^1`, root composer.json now ships `2.x-dev`). The install ran
package operations against a lock that Composer would have rejected
with exit-code 2. Add a targeted check that walks each locked
package's requires, looks for ones aimed at the root's name, and
fails with the same "found root/pkg[X.x-dev] but it does not match
the constraint" pointer Composer prints from `Problem::getPrettyString`.
|
|
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 --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.
|
|
Composer's `install` runs a SAT verify over the locked repository so a
declared `conflict` between two locked packages (including via a
branch-alias or the lock's top-level `aliases` block) fails fast with
exit-code 2 and "Your lock file does not contain a compatible set of
packages." Mozart skipped that step and proceeded to install both
packages. Add a targeted check that walks each locked package's
`conflict` map against every name a locked package effectively
advertises (own version, `extra.branch-alias` target, lock-level
`aliases` entry, `replace` constraint) and bails with the same exit
code when a match is found.
|
|
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 UninstallOperation::show renders the package's
getFullPrettyVersion(), which for dev packages includes the
(truncated) source reference. Mozart was passing only the bare
pretty version, so removal lines for dev packages dropped the ref.
The MarkAliasUninstalled detection also missed the synthetic
9999999-dev alias that ArrayLoader::getBranchAlias surfaces for
default-branch dev packages without an explicit branch-alias. Those
aliases were never being retired alongside their targets. The new
lock's implicit branch-aliases (from extra.branch-alias and the
default-branch fallback) now count as "still present", so packages
that remain in the lock don't trigger spurious uninstall traces.
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>
|
|
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>
|
|
`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>
|
|
Composer's Installer::doInstall prints the missing-requirement warnings
and continues when config.allow-missing-requirements is true, rather
than bailing with ERROR_LOCK_FILE_INVALID. Mozart was always bailing,
diverging on the install-from-incomplete-lock-with-ignore fixture.
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.
|