| Age | Commit message (Collapse) | Author |
|
A plugin can now hand an InstallerInterface implementation to
InstallationManager::addInstaller across the wire, and a legacy
composer-installer package is loaded as one; both are backed by a
PhpInstallerProxy forwarding the whole installer contract to the entity in
the PHP worker. An installer returning a real promise is an explicit error
until promises can cross the boundary.
InstallationManager takes installers as shared handles instead of boxes, so
the object identity removeInstaller and PluginManager's registeredPlugins
compare against survives registration, and holds them in a RefCell:
Installer::run keeps a shared borrow of the manager for the whole run, and a
plugin activated inside it registers its installer from there. The type
cache keys on the installer itself, like upstream, so re-entrant
registration cannot leave a stale index behind. InstallerInterface::supports
is fallible for the same reason getCapabilities and getCommands are: it
answers over RPC.
Cloning a proxy stub clones the Rust-side entity and rebinds the copy to the
fresh handle. Previously only the classes declaring __clone got a throwing
body, and the rest let two stubs share (and twice release) one handle.
Package entities answer with AnyPackage::dup, which already carries
BasePackage::__clone and the RootAliasPackage override; the others are an
explicit error.
The package proxy covers the whole PackageInterface surface; only the link
maps and the release date still lack a wire image for their value objects.
PluginManager gains a test-only seam for the reported Plugin API version,
and the three PluginInstallerTest cases that need it are ported.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Port Application::getPluginCommands: resolve the local composer with
plugins force-enabled, fall back to Factory::createGlobal, and collect
commands from CommandProvider capability adapters. PhpCommandProxy now
mirrors name/description/aliases/hidden over RPC so `list` output
matches upstream; input definitions remain TODO(plugin).
PhpClass::php_class_name returns an owned String because PHP-backed
proxies only know their class at runtime (the override-skip warning
prints get_class). CommandProvider::getCommands hands out shared
Rc<RefCell> handles since the commands are stored in the application.
Factory::createGlobal now propagates createConfig errors instead of
swallowing them, and Application::getComposer only catches the
exception classes upstream catches, so a ParsingException reaches
doRun's GithubActionError path as in Composer.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
InstallationManager::execute now takes &self (the mock recorder moved
into a RefCell) and its callers hold only shared borrows: plugin
registration inside a batch re-enters the same manager handle through
Composer::getInstallationManager()->getInstallPath(), which panicked
on the RefCell re-borrow under the &mut shape — the same re-entrancy
the repository side already fixed, unreachable from the ported tests
because they call PluginInstaller::install directly like PHPUnit does.
The worker-side InstalledVersions mirror now matches the full tail of
FilesystemRepository::write: unconditional reload plus the
reflection-based selfDir/installedIsLocalDir restore. The previous
class_exists(false) guard rested on a lazy-load assumption that does
not hold in the worker (its real ClassLoader only knows the Composer
checkout's vendor dir, so a later lazy load would read the checkout's
installed.php, not the project's); the mirror is now skipped only when
the class is not autoloadable at all, i.e. no plugin runtime and hence
no observer code. Boot-time seeding stays TODO(plugin).
Also from the review: registered_plugins entries are removed only
after the deactivate/uninstall loop (PHP unsets last, and a throw must
leave the entry observable); extra.class keeps associative-array
values and fails loudly on non-strings instead of silently dropping
them; the two discarded write() results now propagate (they carry the
reload-push failure); register_package's allow-plugins skip message is
DEBUG like the addPlugin side; the loader-eviction divergence of
REGISTERED_LOADERS and the lossy UTF-8 spots carry searchable
markers; the test-only proxy downcast follows the __ naming rule; the
R-table dispatch clones the entity out instead of holding the table
borrow across the handler; the IO/PartialComposer stubs turn a
plugin-side `new NullIO()` into an explicit error instead of an
ArgumentCountError; and the empty() emulation covers float 0.0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Implement the remainder of PluginManager::registerPackage: the plugin
autoload map is built by the ported createLoader/parseAutoloads and
served to the worker over the existing reverse-RPC autoloader, files
entries go through a composerRequire-equivalent glue call, and
already-defined classes take the upstream _composer_tmp rename/eval
path. Instantiation uses the new NewObject/CallPhpMethod lanes backed
by a P table in the worker; PhpPluginProxy adapts the resulting handle
to PluginInterface, with $composer/$io exposed to plugin callbacks via
an R table (unsupported methods stay explicit errors). Hand-written
proxy stubs cover Composer, PartialComposer and the IO hierarchy, and
the stub autoloader is re-prepended after loading the Composer PHP
runtime so its vendor autoloader cannot shadow proxied FQCNs.
FilesystemRepository::write now mirrors InstalledVersions::reload into
a running worker (class_exists-guarded, so an unloaded class keeps its
upstream lazy-load behavior), removing the previously undefined
observation window.
The installer pipeline passes the installed repository as a shared
handle instead of a long-lived `&mut dyn`: plugin registration runs
inside InstallationManager::execute and re-enters the same local
repository through the RepositoryManager, which would panic on the
RefCell re-borrow under the old shape.
PluginInterface lifecycle methods now take an owned ComposerHandle
(plugins retain $composer past the call) and return anyhow::Result
(PHP plugin code may throw); the plugin list uses shared ownership so
the identity comparison of removePlugin survives the dual storage in
registeredPlugins, matching PHP reference semantics.
Ports the activate/upgrade/uninstall tests of PluginInstallerTest,
serialized across the shared worker process whose persistent class
table is exactly what exercises the rename path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Three fixes for the ComposerRepository/FilesystemRepository/PlatformRepository
hazards where inner-composition delegation skipped PHP's late-bound virtual
dispatch:
- ComposerRepository::has_package now builds its packageMap through the
late-bound getPackages() equivalent, so lazy-providers repos surface the
LogicException and available-packages repos load their package list, as in
PHP, instead of silently answering false from the raw array.
- RepositoryInterface::get_repo_name returns anyhow::Result<String>: PHP's
getRepoName() counts through the late-bound initialize(), which is fallible
in file-reading subclasses. FilesystemRepository and PackageRepository now
run that initialization instead of freezing the inner array repository to an
empty state (which also made a later write() truncate installed.json).
Supporting changes keep the initialization chain callable from &self:
JsonFile::read takes &self (indent moved into a RefCell), FilesystemRepository
dev_mode became a Cell, and WritableArrayRepository dev_package_names a
RefCell.
- PlatformRepository::new routes constructor packages through its own
add_package so the override handling and full platform initialization run
as they do via PHP's parent constructor; the inner find_package/add_package
delegations inside add_package (and ComposerRepository::add_package) gained
the same is_initialized guard, since the constructor path would otherwise
freeze the repository.
Same defect class as 7db937af, 97b5211a and 3e367f78.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
PHP's JsonFile::encode throws a RuntimeException when json_encode fails; the
port swallowed that into an .unwrap() marked TODO(phase-c). Return
anyhow::Result from encode/encode_with_options and propagate at every call
site (print_table and list_repositories become Result-returning to carry it).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Rust has no runtime class name, so `Command::get_class` existed purely to
let each command hand back its PHP class name, supplied through the
two-argument variant of `delegate_command_trait_impls_to_inner!` at the
impl site. Replace it with a general `PhpClass` trait plus an
`impl_php_class!` macro, so the name is stated once next to the type
definition and the mechanism is reusable outside commands.
`Command` gains `PhpClass` as a supertrait and drops `get_class`, and
`VcsDriverKind`'s hand-rolled `php_class_name` table moves onto the trait.
Behavior is unchanged: the same class-name strings are reported, and the
base command state still panics when asked for a name it cannot supply.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
args
Review of the suggested-values wiring against the PHP originals found:
- audit --format (Auditor::FORMATS) and --abandoned (Auditor::ABANDONEDS)
had no suggestions; only ignore-severity had been converted
- the same constant-passing shape was missed on --audit-format
(Auditor::FORMATS) in update, install, require, create-project and
remove
- update's packages argument used suggest_installed_package(false, true),
but PHP's suggestInstalledPackage(false) expands to (false, false); the
stray true came from an incorrect old TODO comment, and made platform
packages appear among the candidates
With these, every one of the 47 suggestedValues sites in the PHP command
definitions has a matching new5/new6 call.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Ports the per-command completion metadata that PHP passes as the
suggestedValues constructor argument, resolving all TODO(cli-completion)
markers:
- CompletionTrait providers on 18 argument/option sites (installed/root/
available package names, package types, prefer-install)
- static value lists (--format on show/outdated/search/fund/licenses/
check-platform-reqs, archive's FORMATS, audit --ignore-severity,
update --bump-after-update, repository's action list)
- command-specific closures: ConfigCommand::suggest_setting_keys,
ShowCommand::suggest_package_based_on_mode, RepositoryCommand's
suggest_repo_names/suggest_type_for_add, exec/run-script inline
closures (downcast from the this argument, as the closures are bound
to their concrete command in PHP)
- GlobalCommand::complete, delegating completion to the wrapped
subcommand through CompletionInput::from_string
- a complete() override on every Composer command forwarding to
base_command_complete (BaseCommand inheritance restoration)
Also fixes CompleteCommand to call merge_application_definition(true) as
PHP's default-argument call does; with false the application-level
"command" argument was missing from the bound definition, shifting every
argument-position detection by one.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
The seven suggest* methods resolve package names, types, and install
preferences for shell completion. PHP returns $this-bound closures; here
each method returns a SuggestedValues whose closure receives the bound
command as `this` at call time. The blanket impl over every BaseCommand
mirrors PHP's per-command `use CompletionTrait;` (the methods are private
there, so the wider visibility is observationally equivalent).
Notable PHP shapes kept: the hintsToFind counter machine iterates a
by-value copy per package (continue 2 -> labelled continue), and
suggestAvailablePackage pins an exact vendor match before truncating to
$max entries.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
PHP's CompletionInput extends ArgvInput, so it can be passed anywhere an
InputInterface is expected (GlobalCommand::complete binds and forwards it,
suggestion closures read options and arguments from it). The Rust port only
embedded the ArgvInput, so none of that surface was reachable.
Implement InputInterface by forwarding to the embedded ArgvInput, with bind
dispatching to the specialized CompletionInput::bind (PHP's virtual
dispatch), derive Clone, and teach GlobalCommand::input_to_string the
CompletionInput branch.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
InputArgument/InputOption
Composer backports symfony/console 6.1's $suggestedValues parameter in
Composer\Console\Input\{InputArgument,InputOption}; the Rust newtypes had
dropped it. PHP closures are bound to the command ($this), but a command
cannot capture a handle to itself while configure() runs inside new(), so
the closure receives the bound command as an explicit `this` argument at
call time instead.
- add SuggestedValues (list | this-taking closure) and wire it through
InputArgument::new5 / InputOption::new6 and their complete() methods
- track Composer-typed definition entries by name in BaseCommandData side
maps, standing in for PHP's instanceof checks (set_definition converts
entries to the Symfony types for storage)
- add base_command_complete, the BaseCommand::complete dispatch shared by
every Composer command
- introduce BaseCommand::base_command_data and make command_data a default
method on top of it
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
The _complete and completion commands were registered but always panicked:
get_class_of_command / instantiate_completion_output / tail_debug_log were
todo!() and the completion.bash resource was not shipped.
- make Command::complete return anyhow::Result so completion errors
propagate to CompleteCommand's catch-all (exit code 2) like PHP
- add Command::get_class as the port hook for PHP's get_class() debug log;
every command supplies its PHP FQCN via the delegation macro
- embed Resources/completion.bash at compile time (single-binary port);
get_supported_shells becomes a static list
- implement tail_debug_log by moving the shared output handle into the
'static process callback
- add OutputInterface::as_console_output so unsupported-shell errors go to
stderr as in PHP
- fix CompletionInput::bind to keep the argument name PHP assigns in the
foreach head even when the loop breaks on the first unset argument;
application-level completion always hit this and returned no suggestions
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
IOInterface::ask/select now return anyhow::Result<PhpMixed>, and
ConsoleIO::ask_question forwards QuestionHelper errors (validator
failures, MissingInputException) instead of collapsing them with
.expect(). In PHP these exceptions propagate from QuestionHelper
through ConsoleIO to the caller, so callers such as UpdateCommand's
interactive package selection must be able to observe them; the
MissingInputException is wrapped with its concrete type preserved so
Application's ExceptionInterface downcast keeps working. All call
sites now propagate with `?` (Perforce::query_p4_user becomes
Result-returning: PHP declares it void but exceptions still escape),
and the previously ignored
test_interactive_mode_throws_if_no_package_entered passes.
ask_confirmation/ask_and_hide_answer still collapse errors; extending
propagation to them is left as TODO(phase-c) pending a decision.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
PHP's ArrayRepository::count()/hasPackage() call $this->initialize(),
which late-binds to the concrete repository class and lazily loads its
packages. The Rust pass-throughs skipped that: they ran ArrayRepository's
stub initialize instead, returning 0/false and marking the repository
initialized with an empty package list, which made ensure_initialized()
skip the real initialization forever after.
Take &mut self in RepositoryInterface::count/has_package so the lazy
repositories (Filesystem, Platform, Composer) can guard with their real
initialize, and return Result from has_package since that initialization
can fail (PHP propagates the exception). InstallerInterface::is_installed
and InstallationManager::is_package_installed/mark_alias_installed
propagate the same way, which also resolves the TODO(phase-d) markers on
Package/Path/Artifact/Vcs repositories about initialization errors being
swallowed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
A native binary never ships vendor/composer/installed.json, so
Composer's "non-standard Composer installation" warning fired on every
diagnose run and forced exit 1. The self-audit itself stays: a Composer
source snapshot is planned to be embedded together with the plugin API
implementation, which will make it functional; until then the missing
file reports success, marked with TODO(phase-c).
Also un-ignore diagnose_command_test::test_cmd_success: the other half
of its ignore reason ("requires real network access") is no blocker —
the PHP original runs its live packagist/github checks unguarded too.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
The line was a "TODO: curl_version()" placeholder. Extend the diagnose
payload with curl_version() and the CURL_* constants getCurlVersion()
consults, so the libz/brotli/zstd/ssl/HTTP details come from the PHP
runtime instead of being guessed. curl_version() is only reachable while
the extension is loaded, mirroring the ioncube_loader_* entries.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
PHP's BufferIO defaults to StreamOutput::VERBOSITY_NORMAL; passing 0 sits
below VERBOSITY_QUIET, so every write was dropped and "Audit found some
issues:" was followed by an empty advisory table.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
diagnose used to read hardcoded shim stubs, so it described a fictional
runtime: OPENSSL_VERSION_NUMBER was always 0 and tripped the TLSv1.1/1.2
check, PHP_BINARY and OPENSSL_VERSION_TEXT were empty, and the extension,
function and ini probes answered from a fixed table.
The PHP worker gained a `diagnose` entry that returns every fact the
command needs as one PHP array, cached in a OnceLock so the several call
sites share a single round trip. Reading it back needed array support in
the serialize() parser, which in turn lets get_loaded_extensions and
get_all_ini_files return real lists instead of comma-joined strings.
Also fixes the openssl_version message, which dropped strstr()'s
before_needle argument during the port, and check_connectivity's
allow_url_fopen test, which did not follow PHP string truthiness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The ALLOW_SHADOWED_REPOSITORIES probe that decides whether to raise the
repository-priority error hardcoded ignoreNothing(), while PHP reuses
$platformRequirementFilter. With --ignore-platform-req the probe could fail to
find the lower-priority package and suppress the error PHP would raise.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The `fixed` option was read as a temporary inside the argument list of
update_requirements_after_resolution(), so the Ref lived until the end of the
enclosing let statement — i.e. across the whole call. When the resolved version
looks like a feature branch, that call asks for confirmation, and ConsoleIO
takes the same input RefCell mutably, panicking with "RefCell already borrowed".
Hoisting the read into its own statement ends the borrow before the call.
The expected output of the un-ignored test transcribed PHP's string
concatenation operator (`[y,n]? '.'`, used to keep the trailing space visible)
as a literal `.`; it now matches RequireCommandTest.php.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
findBestVersionForPackage is the only findBestCandidate() caller that PHP hands
$this->getIO() to; the port passed None, so VersionSelector silently skipped
every "Cannot use <pkg> as it requires <ext> which is missing from your
platform" warning. require/update/create-project therefore dropped a candidate
without telling the user why.
Un-ignores require_command_test::test_require, whose first data-provider case
asserts exactly that warning.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
build.rs guessed target/<profile> from OUT_DIR to place res/*.json next to
the executable (twice, since test binaries live in deps/), and JsonFile
resolved them through current_exe(). That made the binary undistributable
on its own.
The schemas are now include_str!'d and referenced through a
shirabe:///res/ URI that SchemaRetriever resolves, keeping the $ref
indirection PHP uses for the phar case. The res/ path segment is required
so composer-lock-schema.json's relative "./composer-schema.json" reference
still resolves.
|
|
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
Operations are only ever constructed by the dependency resolver, so a
plugin has no way to inject an implementation of its own and the set is
closed. Modelling it as an enum, like AnyPackage, removes the
OperationInterface trait together with its two parallel downcast
mechanisms (as_any() + downcast_ref, and as_*_operation()) and the
get_package() default method that panicked on UpdateOperation.
The PHP idiom `$op instanceof UpdateOperation ? getTargetPackage() :
getPackage()`, written out at six call sites, becomes
AnyOperation::get_target_package(). InstallationManager's three blocks
that matched on the type string and then recovered the type with
expect() collapse into exhaustive matches.
SolverOperation keeps only its TYPE constant; the shared
getOperationType()/__toString() implementations move to AnyOperation,
which also drops the five Self::TYPE.to_string() allocations.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
is_callable
RequireCommand registers an inline listener on InstallerEvents::PRE_OPERATIONS_EXEC
to track dependency_resolution_completed, mirroring PHP's `function () use
(&$dependencyResolutionCompleted) { ... }`. This is Composer's own code, not a
Plugin subscriber, but it went through the shared non-string-callable path, which
checked is_callable() against a hardcoded PhpMixed::Null and always failed,
breaking every `require` that reaches the install step.
Callable::Closure now carries the actual Rc<dyn Fn> instead of being a data-less
placeholder, and is invoked directly (Closures are always callable in PHP). The
ArrayCallable path used by future Plugin subscribers is untouched.
Un-ignoring the two require_command_test cases that cited this bug reveals two
separate, pre-existing issues (a missing ext-requirement warning message, and a
RefCell re-entrancy panic in ConsoleIO::ask_question); their #[ignore] reasons
are updated to describe the real current blocker instead of the now-fixed one.
|
|
PoolBuilder::build_pool() and warn_about_non_matching_update_allow_list()
called get_canonical_packages() on the locked repository during a
partial update, but PHP's PoolBuilder uses the plain getPackages().
get_canonical_packages() unwraps AliasPackage down to its base package,
discarding the alias's own version identity.
Locker::get_locked_repository() wraps a lock entry with
extra.branch-alias in a single CompleteAliasPackage rather than adding
a separate base object, so canonicalizing it silently dropped the
locked branch-alias version (e.g. 2.2.x-dev), leaving only the raw
dev-master version behind. For a package excluded from the partial
update's allow-list, that meant its locked branch-alias version could
no longer satisfy the root's constraint, producing a spurious solver
conflict instead of keeping the package pinned exactly as locked.
Fixed the same bug in AuditCommand's --locked package listing, which
had the identical get_canonical_packages()/getPackages() mismatch
against AuditCommand.php.
|
|
The negated remaining-width subtraction was cast straight to usize,
overflowing whenever it went negative (e.g. an abandoned-package
warning ate the available width) and panicking on slice indexing.
Route through the shim's substr(), which mirrors PHP's negative-length
semantics and clamps instead of overflowing.
|
|
execute() held &config.borrow() across check_http/check_composer_repo/
check_composer_audit, which reach HttpDownloader -> CurlDownloader::
download; that method does self.config.borrow_mut() on the same
Config RefCell, panicking with "RefCell already borrowed" once the
phpinfo panic that previously masked this was fixed.
Switch those three helpers to take the Rc<RefCell<Config>> handle
(matching check_version's existing pattern) and borrow only where a
field is actually read, so no borrow spans the downstream network
call. Un-ignore the now-passing run_diagnose smoke test and
test_cmd_fail; test_cmd_success stays ignored, now for two separate
reasons: it needs real network access (as the PHP original does), and
shirabe_php_shim::OPENSSL_VERSION_NUMBER is a hardcoded stub (0) that
always trips check_platform's TLSv1.1/1.2 support check regardless of
the real linked OpenSSL, forcing a non-zero exit code.
|
|
DiagnoseCommand::check_platform needed phpinfo() output but
shirabe-php-shim's ob_start()/phpinfo()/ob_get_clean() are unmodeled
todo!()s. Add a phpinfo dispatch entry to the PHP worker (capturing
output the same way extension_info already does) and a get_phpinfo()
wrapper, then switch check_platform to call it directly.
This unblocks the phpinfo-related panic in diagnose; the ignored
tests now hit a separate RefCell double-borrow bug in check_http, so
their ignore reasons are updated to point at that instead.
|
|
The Ref temporaries created by input.borrow() inside the argument
expressions of the determine_requirements call lived until the end of
the whole call statement, so ConsoleIO::ask_question's borrow_mut() on
the same shared input RefCell panicked with "RefCell already borrowed"
when the command prompted for packages. Hoist the argument computations
into locals so no borrow is held across the call, and un-ignore the
run_require CLI test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
PHP's UpdateCommand::getPackagesInteractively passes $autocompleterValues
keyed by package name to $io->select, so the selection resolves to package
names. The port passed only the keys as a list, making select resolve to a
numeric index that the update then treated as an unknown package. The old
ignore reason (non-interactive terminal error) no longer applied.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
PHP's RunScriptCommand::interact passes $options keyed by script name, so
select resolves the entered value to the script name and sets it as the
script argument. The port passed only the keys as a list, which made select
resolve to a numeric index instead of the script name.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
PHP's IOInterface::select accepts an associative choices array whose keys
are the selectable values, but the port narrowed it to Vec<String>, making
key-based selection unrepresentable. Accept PhpMixed (List or Array) like
PHP's array $choices; ConsoleIO already branched on both shapes internally.
Also mirror PHP in the single-select array_search fallback for numeric-keyed
arrays. All call sites keep their previous list-based behavior.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
repo bugs
Replace the PhpMixed-based `$showWarnings` hack in VersionSelector::
findBestCandidate with a typed ShowWarnings enum (Always / Predicate),
letting ShowCommand::findLatestPackage pass its real closure instead of
hardcoding `true`. Fix the --no-dev branch in ShowCommand::execute,
which built `repos` from an empty package list instead of sharing the
same InstalledRepository as `installed_repo`. Pass repository handles
instead of pre-borrowed `&dyn RepositoryInterface` refs into get_package/
generate_package_tree/add_tree to stop a RefCell double-borrow panic on
--all/--locked. Add the missing CompletePackage/RootPackage
set_release_date setter so the outdated sorting-by-age test can set
fixture dates. Resolve OutputFormatterStyleStack::pop's empty-style
todo!() via clone_box(), and fix FileDownloader's cache-GC log call to
pass the VERY_VERBOSE verbosity PHP uses.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
abandoned propagation
The json format branch ignored search results entirely and always wrote
null. Encode results into the same name/description/abandoned/url shape
Composer's array-backed repositories produce. Also fix
ComposerRepository's RepositoryInterface::search adapter, which dropped
the abandoned field from raw API results even when present.
|
|
test_update was skipped for a stale reason; running it uncovered three
distinct bugs it was actually catching:
- ApplicationTester::run never restored SHELL_VERBOSITY after
Application::configureIO mutates it, so one dataset's -vv verbosity
leaked into later runs sharing the process (Symfony's tester restores
it in a finally block; the port dropped that).
- Installer::do_install built its RepositorySet with a hardcoded empty
temporary_constraints map instead of self.temporary_constraints, so
--with never actually constrained the resolver.
- BumpCommand was missing a <warning> tag pair around one of its output
lines.
|
|
PHP's printTable builds a 5-column row for text output, with the raw
Link object (cast via __toString) as its own column separate from the
formatted description string. The port had collapsed both into one
column, dropping an empty column for successful checks and throwing
off the rendered column widths/spacing versus real Composer output.
|
|
Concurrent package operations call into the same downloader instances
through Rc<RefCell<dyn DownloaderInterface>>; with &mut self methods
every call holds a RefMut across its awaits, which panics with
'already mutably borrowed' the moment two operations overlap. This is
groundwork for fanning out InstallationManager's download/install
loops (same rework HttpDownloader/CurlDownloader already got).
- DownloaderInterface/ChangeReportInterface/ArchiveDownloader/
VcsDownloader methods now take &self; as_change_report_interface
returns &dyn instead of &mut dyn.
- Implementors move their genuinely mutable state behind cells:
FileDownloader.additional_cleanup_paths, the archive downloaders'
cleanup_executed, ZipDownloader.zip_archive_object,
VcsDownloaderBase.has_cleaned_changes, GitDownloader's stash/discard/
cache maps and GitUtil, SvnDownloader.cache_credentials,
PerforceDownloader.perforce. FileDownloader.io gains a RefCell layer
so get_local_changes can keep PHP's NullIO swap under &self.
- ProcessExecutor::execute_async now returns a future that captures
everything up front instead of borrowing the executor, and call
sites build the future before awaiting, so no borrow on the shared
executor is held while a subprocess runs.
- Filesystem::remove_directory_async becomes remove_directory_async_via
taking the Rc handle: the Filesystem is only borrowed for the sync
head/tail, never across the rm subprocess await (sync borrow_mut
users like rename/ensure_directory_exists would otherwise collide).
- DownloadManager async call sites hold shared borrows only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
regex::Regex::clone() does not share the underlying meta engine's
search-cache pool, so every fresh clone pays a ~10us warmup cost on
its first use. Two changes together eliminate this across nearly all
preg_* call sites:
- A php_regex! macro resolves PHP-style patterns to a per-call-site
&'static regex::Regex (via regex-macro's LazyLock), applied at the
majority of call sites throughout the codebase.
- Call sites still passing dynamic pattern strings go through
PATTERN_CACHE, which now stores Arc<(Regex, bool)> and hands out
Arc::clone()s instead of cloning the Regex itself.
PregPattern::resolve() returns a ResolvedPattern enum (Arc or
'static reference) rather than an owned Regex, so neither path ever
clones the Regex proper.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
The `regex` crate does not support negative lookahead, so the ported
`^dev-(?!main$|master$|trunk$|latest$)` pattern panicked at runtime on
any `require` invocation that reached version-selection. Replace it
with equivalent hand-written string logic per docs/dev/regex-porting.md.
|
|
reset() eagerly rebuilt the ProxyManager singleton immediately,
capturing env vars before a caller could set them for the next
request. PHP's reset() just nulls the static instance; getInstance()
lazily constructs on next use. Match that so proxy env vars set after
reset() are observed.
get_instance() also ensured the singleton was constructed under its
own lock, dropped that lock, and returned the bare Mutex; every caller
then took a second, independent lock. A reset() landing in that gap
would leave the caller observing None and panicking on
.as_ref().unwrap(), a state the old eager-reconstructing reset() could
not produce. Return the already-locked MutexGuard from get_instance()
instead, so construction and use happen under one lock, and update all
call sites accordingly.
Holding that guard across a loop body then deadlocked in
diagnose_command, since check_http_proxy transitively re-enters
get_instance() via HttpDownloader -> CurlDownloader, and
std::sync::Mutex is not reentrant. Re-acquire the lock fresh each
iteration with a short-lived guard instead.
Finally, Mutex::new is a const fn, so the OnceLock wrapper around it
was unnecessary indirection; a bare static Mutex<Option<ProxyManager>>
initializes to the same state without the get_or_init/get dance.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
|
|
|
|
BaseConfigCommand::initialize skipped the parent BaseCommand::initialize
chain (plugin enable/disable resolution, PRE_COMMAND_RUN event dispatch,
COMPOSER_NO_* env option overrides), unlike PHP's parent::initialize()
call. The trait-disambiguation blocker cited in the old TODO was already
solved elsewhere via the base_command_initialize free function; wire it
in here too so ConfigCommand and RepositoryCommand match PHP behavior.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
Application::find is now available, so getScripts can look up each
script's associated command and read its description, ignoring
CommandNotFoundException/NamespaceNotFoundException the same way the
PHP code does for scripts with no associated command.
|
|
Downcasts the generic Application handle to the concrete shirabe
Application to read getInitialWorkingDirectory(), so exec once again
switches back to the directory it started in (e.g. after `composer
global exec`), matching PHP's behavior.
|
|
Application::find/reset_composer and the shared ApplicationHandle are
now available, so GlobalCommand can reset the composer instance before
building the sub-command input and proxy execution through the full
Application::run dispatch, matching PHP's behavior. Un-ignores the
tests that only depended on this wiring, and re-points the remaining
ignores at their real (unrelated) blockers.
|
|
Application::find and BaseCommand::reset_composer are now available,
so the deferred update_dependencies/run_dump_autoload_command stubs
can find, reset, and run the sibling command directly, matching
InitCommand's PHP behavior.
|
|
BaseCommand::createComposerInstance and initialize() OR in the
Application's getDisablePluginsByDefault()/getDisableScriptsByDefault()
on top of the --no-plugins/--no-scripts flags; this was deferred with
a TODO(phase-c) since the shared Application handle wasn't wired up
yet. The same get_application() + downcast pattern used by get_io()
now covers this too.
|