| Age | Commit message (Collapse) | Author |
|
Mirror `CheckPlatformReqsCommand::execute` end-to-end: build an
`InstalledRepoLite` from lock/installed plus the root and the real
PlatformRepository, ksort the combined `$requires`, and run the
candidate matching loop with `findPackagesWithReplacersAndProviders`
so an installed package that `provide`s or `replace`s a platform name
(e.g. `symfony/polyfill-mbstring` providing `ext-mbstring`) is now
recognised as satisfying the requirement.
Fixes the JSON output schema to match Composer:
`failed_requirement` is the `{source, type, target, constraint}`
object (or null), `provider` is the bare "provided by …" string (or
null), and `status` is the unwrapped `success`/`failed`/`missing`.
Also switches `--format` to a clap `value_parser` and replaces the
"No installed packages found" hard error with Composer's warn-then-
proceed path so an empty lock yields `[]` and exit 0.
Adds `mozart_core::installer::InstalledCandidate` plus
`InstalledRepoLite::add_candidate` /
`find_with_replacers_and_providers` as the shared substrate for
future commands (`depends`, `prohibits`, `audit`) that need the same
provider/replacer index.
|
|
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.
|
|
Splits execute() into execute → do_bump → update_file_cleanly mirroring
Composer's structure, switches state loading to Composer::require, adds
the no-lock fallback to vendor/composer/installed.json, routes warnings
and errors through stderr, and matches Composer's wording verbatim.
JsonManipulator is deferred — update_file_cleanly always falls back to
a full structured rewrite for now, documented in known-incompatibilities.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Split the inline 370-line execute() into execute / install_project /
install_root_package, mirroring Composer's three-method shape and argument
order. Replace the bespoke caret/tilde/wildcard semver helpers with
mozart_semver::VersionConstraint, harden stability inference (handle the
@stability suffix and reject invalid values), and align user-facing wording
("Creating a ...", "Cannot create project directory ...", "Could not find
package ...") with Composer's strings. Add the --ask directory prompt, the
interactive VCS-removal prompt, the empty-target-directory bail, and the
COMPOSER_ROOT_VERSION / COMPOSER env-var handling that the PHP command does
after extraction.
Custom repositories, the canonical Installer pathway, the signal handler,
and script events are still deferred — see .ken/command_compat_plan/create_project.md.
|
|
Restructures diagnose to mirror Composer's 17-step DiagnoseCommand:
adds composer.json schema validation, custom composer-repo
connectivity, COMPOSER_IPRESOLVE warning, and the
checkConnectivityAndComposerNetworkHttpEnablement preflight; drops
Mozart-only extras (cache-dir, lock freshness, trailing summary).
Extracts the manifest validator into mozart-core::config_validator
so both ValidateCommand and DiagnoseCommand depend on the shared
module rather than each other -- the same shape Composer uses with
Util\\ConfigValidator. Adds a thin HttpDownloader wrapper in
mozart-core::http, shadowing Composer's Util\\HttpDownloader.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Composer's OutdatedCommand (isProxyCommand = true) just remaps options
and re-invokes `show --latest [--outdated]`; Mozart's outdated.rs was a
~700-line parallel reimplementation of show's outdated logic with its
own classifier, renderer, JSON shape, and platform predicate. Collapse
it into a thin proxy that builds a ShowArgs from OutdatedArgs and calls
show::execute, mirroring OutdatedCommand::execute field-for-field.
This restores show as the single source of truth for rendering, JSON
fields, --strict, and the mutual-exclusion checks. Surfaced gaps in
show.rs (--major-only/--minor-only/--patch-only filtering, --sort-by-age,
JSON enrichment with homepage/source/time/abandoned) are deferred per
the plan.
|
|
Drive the command from Composer::require() and route the
(installed | locked) branch through the ported PackageSorter,
RepositoryUtils::filterRequiredPackages, and PackageInfo helpers
in mozart-core. --no-dev for installed packages now filters via
root.require closure instead of dev_package_names membership;
text output annotates the name cell with an OSC 8 hyperlink to
the view-source/homepage URL; summary ties resolve in first-seen
order via IndexMap + stable sort_by_key(Reverse(count)) to mirror
PHP's arsort().
|
|
Switch to Composer::require() for the entrypoint, drop the Mozart-only
--dry-run / --no-dev flags, mirror selection inline using a port of
BasePackage::packageNameToRegexp, read autoloader options from
composer.config(), and route the autoload dump through
composer.autoload_generator(). Empty-result and unmatched-pattern
warnings now emit on stderr with <warning> markup, matching
$io->writeError. Plugin/script-event dispatch and Transaction-based
operation building remain TODO until the installer_executor lands.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
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>
|
|
Mirror Composer's two-pass pipeline: load default-branch metadata from
remote repos first, then fall back to installed.json funding only for
packages whose default branch had nothing. Also fix the prev-line
dedup (was reset inside the inner loop), emit OSC 8 hyperlinks for
URLs, route format errors through console.error + bail_silent with
Composer's wording, and emit `[]` for empty JSON output to match
PHP's json_encode of an empty array. Drop the lockfile-preferred
heuristic — Composer reads only installed.json via the local
repository.
|
|
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.
|
|
Move source acquisition (root or remote dist download/extract),
composer.json archive metadata reading, filename generation, self-
exclusion, filter aggregation, and archive creation from the archive
command into a new ArchiveManager in mozart-archiver, mirroring
Composer's ArchiveCommand <-> ArchiveManager split. The command becomes
a thin wrapper that selects the package and delegates archiving.
Adds a one-way mozart-archiver -> mozart-registry dep since
ArchiveManager::archive() handles dist downloading internally (the
analog of Composer's injected DownloadManager).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
archive and diagnose were not honoring $COMPOSER_HOME/config.json
because they bypassed Factory::createConfig() — archive used literal
"tar"/"." defaults when no composer.json was present, and diagnose
reimplemented cache-dir resolution from environment variables.
Mirror Composer's tryComposer + Factory::createConfig() fallback so
global config (archive-format, archive-dir, cache-dir) applies in both
commands.
|
|
|
|
The StatusArgs struct redefined `verbose` as bool while Cli defines a
global `verbose: u8` with ArgAction::Count. clap's runtime type check
panicked on access. Drop the local field and rely on cli.verbose, which
matches Composer's StatusCommand treating -v|-vv|-vvv as a single flag.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
Add the Composer state-container types (LocalRepository,
RepositoryManager, InstallationManager, AutoloadGenerator,
AutoloadDumpOptions, PlatformRequirementFilter, Locker) plus the
factory wiring that builds them from composer.json and
vendor/composer/installed.json.
AutoloadGenerator::dump lives in mozart-autoload as an extension
trait so the orchestrating algorithm sits next to the classmap
scanner while the state container stays in mozart-core. Rework
dump-autoload to drive both, mirroring
$composer->getAutoloadGenerator()->dump(...).
|
|
Composer's ClearCacheCommand uses $io->writeError() for the per-cache
status lines and the final summary; Mozart was writing them to stdout
via console.info(). Switch to console_writeln_error\! so the output
stream matches Composer.
|
|
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>
|
|
|
|
--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.
|
|
Adds crates/mozart-core/src/factory.rs with get_cache_dir(),
get_data_dir(), and create_config() — a Rust port of
Composer\Factory::createConfig() (auth loading and htaccess creation
are out of scope for now).
Also fixes a correctness bug on Linux: the previous Config::default()
resolved cache-dir to $XDG_CONFIG_HOME/composer/cache via the
{$home}/cache placeholder, whereas Composer uses the XDG cache base
($XDG_CACHE_HOME or ~/.cache), giving ~/.cache/composer.
Callers updated:
- Composer::load() uses create_config() as the global baseline before
merging project-level config.
- config command execute_read() builds the global baseline with
create_config() and overlays local config on top when not --global,
matching Composer's actual layering order.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Config uses serde with kebab-case field mapping; known properties are
strongly-typed fields and unknown keys flow into an extra BTreeMap.
resolve_references is moved to the new config module.
|
|
|
|
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>
|