| Age | Commit message (Collapse) | Author |
|
Split date() into date_utc() and date_local(), the latter resolving the
system's local timezone through the tzfile crate ($TZ, then
/etc/localtime, falling back to UTC when neither is readable). The
timestamps Composer renders for humans -- the GitHub OAuth token note,
the GitHub API rate limit reset time, the Perforce client spec fields and
the "today" check of the show command -- now go through date_local().
PHP resolves its default timezone from the date.timezone ini setting,
which Shirabe does not read, so date_default_timezone_get/set have no
input left to model and are dropped from the shim and its callers. The
resulting difference is recorded in docs/known-incompatibilities.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The binary called itself Composer everywhere: the application name, the
logo, --version, about, and every warning that talks about the running
program. Prompts to file a bug also pointed at Composer's issue tracker.
Add SHIRABE_VERSION and SHIRABE_RELEASE_DATE next to the Composer version
constants and report those, naming the Composer version this port tracks
alongside them. Composer::VERSION and getVersion() are untouched, so the
composer platform package, composer-runtime-api and the HTTP User-Agent
keep the value plugins and package repositories expect.
build.rs stamps the release date with the UTC date of the HEAD commit,
the way Composer's Compiler fills in @release_date@ when building the
phar. It now also fails the build when git cannot be read, instead of
letting COMPOSER_DEV_WARNING_TIME fall back to the tagged-release value
and suppress the outdated-build warning forever.
Messages about the Composer ecosystem keep their wording. Two of them are
pinned by upstream installer fixtures (Rule's "cannot be modified by
Composer" and SolverProblemsException's "you can run Composer with") and
stay as they are so those fixtures can keep being used verbatim.
The e2e list comparison against upstream Composer now skips the banner,
which cannot match by design, and compares everything below it as before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
checkComposerAudit reported success instead of auditing anything, because
the binary ships no vendor/composer/installed.json on disk. It reads the one
in the embedded Composer PHP runtime now, and Composer's warning for a
missing installed.json is back.
Only that file leaves the bundle, into a temporary directory that goes away
with the handle; the runtime is unpacked whole only for a worker that cannot
read the bundle in place. Phar::extractTo's $files argument selects it,
which the shim ignored so far.
SHIRABE_COMPOSER_PHP_DIR moves into composer_runtime, so the worker and a
reader on the Rust side resolve the runtime through the same branch.
DiagnoseCommandTest::testCmdSuccess is ignored: packagist has advisories
against composer/composer 2.9.7, the version Composer::VERSION reports, so
diagnose exits 1 where the test expects 0. Upstream Composer 2.9.7 reports
the same advisories.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The command's own definition covers only --dev/--no-dev and the args
array, so any other application-level option reaching it (as when
GlobalCommand re-parses `--no-interaction=1` out of ArrayInput's
stringification) aborted the run with "does not accept a value".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
str_contains(), str_starts_with() and str_ends_with() were thin wrappers
over the str methods of the same semantics. Call sites now use
contains()/starts_with()/ends_with() directly.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The SignalHandler port was a no-op stub, so all four of Composer's abort
paths were dead code: nothing removed a half-created project, reverted
composer.json, or cleaned up half-installed packages.
Composer runs those handlers from pcntl callbacks, which a Rust signal
handler cannot do -- it may touch nothing beyond atomics. SignalSubscription
records the signal instead, and the abort runs from checkpoints on the normal
call stack, where the clean-up can borrow the state it needs. That also
resolves the closure-capture TODO(phase-c)s in RequireCommand and
InstallationManager, and replaces exit_with_last_signal's exit(0) with the
restore-and-re-raise Seld\Signal does.
A subscription is live only inside the four abort regions, so elsewhere the
signals keep their default disposition and kill the process at once. It is
installed without SA_RESTART so a signal interrupts an interactive prompt
rather than resuming the read. A signal reaches only the innermost
subscription, reproducing SignalHandler's single-stack dispatch.
Drop SignalRegistry, SignalableCommandInterface and the Application wiring
for them: nothing in Composer reaches that path, and SignalHandler discards
whatever they register. Signal handling from plugins and scripts is
undefined behavior; see docs/dev/signals.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Every parent module in the symfony-* crates already re-exported its leaf
modules with `pub use`, so each item was reachable by two paths. Make the
leaf modules private and route all callers through the single re-exported
path.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Composer restarts itself with Xdebug unloaded because Xdebug makes PHP
several times slower. Xdebug is never loaded into this process, so what
needs dealing with is the PHP worker: it is spawned with
`-d xdebug.mode=off` and `XDEBUG_MODE=off` (Xdebug reads the environment
variable first and lets it override every ini setting), which makes its
module init return before it installs any executor, compile, error or
opcode hook.
Rewriting the ini files and re-executing, the way xdebug-handler does,
would additionally cover Xdebug 2, which hooks unconditionally and has no
equivalent setting. That is not worth its machinery here: Xdebug 2 caps
out at PHP 7.4, while every PHP version Composer supports can run
Xdebug 3.
What remains of XdebugHandler is small enough to live beside the worker
it governs, so its crate is gone and its callers inline it. isXdebugActive
answers false without asking PHP whenever the worker is switched off, so a
command that needs no PHP does not spawn one just for the Xdebug warning;
diagnose reports what the worker measures instead, which still surfaces an
Xdebug that ignores the setting. PlatformRepository has no unloaded
extension to restore, since switching the mode off leaves it loaded.
COMPOSER_ORIGINAL_INIS is neither written nor read: it exists so a
restarted process can name the ini files it replaced, and IniHelper can
report the worker's own. IniHelperTest injects through that variable, so
none of its cases are ported.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
shirabe-xdebug-handler crate
Move `Composer\XdebugHandler` out of shirabe-external-packages and into
its own crate, so the path is `shirabe_xdebug_handler::XdebugHandler`
instead of
`shirabe_external_packages::composer::xdebug_handler::XdebugHandler`.
Standing alone, the stub no longer sits in a crate that shirabe-php-rpc
depends on, so drop the dependency-cycle rationale from the comments that
explain why callers reach for shirabe_php_rpc directly.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Move `Seld\Signal` out of shirabe-external-packages and into its own
crate, so the path is `shirabe_seld_signal::SignalHandler` instead of
`shirabe_external_packages::seld::signal::SignalHandler`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
shirabe-symfony-console crate
Move `Symfony\Component\Console` out of shirabe-external-packages and
into its own crate, so the path is
`shirabe_symfony_console::application::Application` instead of
`shirabe_external_packages::symfony::console::application::Application`.
The `delegate_to_inner!` and `delegate_command_trait_impls_to_inner!`
macros move with it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
shirabe-symfony-finder crate
Move `Symfony\Component\Finder` out of shirabe-external-packages and into
its own crate, so the path is `shirabe_symfony_finder::Finder` instead of
`shirabe_external_packages::symfony::finder::Finder`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
shirabe-symfony-process crate
Move `Symfony\Component\Process` out of shirabe-external-packages and
into its own crate, so the path is `shirabe_symfony_process::Process`
instead of `shirabe_external_packages::symfony::process::Process`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Move `Composer\Pcre` out of shirabe-external-packages and into its own
crate, so the path is `shirabe_pcre::preg::Preg` instead of
`shirabe_external_packages::composer::pcre::preg::Preg`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Concatenating a directory, MAIN_SEPARATOR and a component reimplements
what Path::join already does. Joining directly also drops the separator
from the intermediate values it used to be baked into, such as
ClassLoader's $pathEnd.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The shim's DIRECTORY_SEPARATOR was hardcoded to "/", so every ported
`'\\' === DIRECTORY_SEPARATOR` check compared against a constant that
does not track the target platform. std::path::MAIN_SEPARATOR and
MAIN_SEPARATOR_STR carry the same meaning as PHP's constant and resolve
per platform, so the Windows branches are selected on Windows targets.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Composer hands the exception it caught to the one it throws in its place,
so `getPrevious()` reaches the cause and Application's renderer prints the
whole chain. Every ported site dropped it, because the flat exception
structs had nowhere to put one. `AnyThrowable::into_previous` turns the
caught error into that argument, and the 13 sites now pass it.
`getCode()` came along for the ride at the four sites that derive the new
exception's code from the caught one (PharArchiver, ArrayLoader x2), and
ComposerRepository's message now names the caught exception's class
instead of the literal "Exception".
GitHubDriver::attemptCloneFallback took the previous exception's message
and appended it to its own, which no `\RuntimeException('Fallback to git
driver disabled')` in Composer ever says; it now chains it instead.
Git::syncMirror restores what PHP's `finally` does to an exception in
flight: the `git remote set-url` that scrubs credentials back out of the
URL runs in a `finally`, and when it fails PHP propagates *its* exception
over the one already leaving, chaining the displaced one as previous. The
port discarded the finally's result, so a failure to scrub the URL was
reported as a successful mirror sync. `AnyThrowable::set_previous`
models the engine-level chaining.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Ported exceptions were flat structs reached with `downcast_ref`, so
Composer's `catch (\RuntimeException $e)` only matched the exact leaf
type and `get_class($e)` had nothing to report. Each exception now
embeds an instance of the class it extends and travels inside an
`AnyThrowable`; `Catch::catch`/`catch_mut` walk that chain, and
`PhpClass::php_class_name` yields the PHP FQCN.
Dropping the `std::error::Error` impls from the exception types leaves
`AnyThrowable` as the only route into an `anyhow::Error`, so the walk
cannot be bypassed. A `no_exception_downcast` linter catches the
`downcast::<X>()` calls that would now silently answer `None`.
Three sites change behavior as a result: the `TransportException`
exit-code override reaches `MaxFileSizeExceededException`, the
`catch (\LogicException)` in findSimilar() reaches its subclasses, and
rendered exception titles carry the real class name rather than a
guess. `get_class_err()` is no longer a `todo!()`, which re-enables
FilesystemRepositoryTest::testCorruptedRepositoryFile.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
PHP's version_compare takes its operator as a string, so the shim's port
did too, and Constraint carried two families of operator constants plus
translation tables to convert between the string form and its own int
codes. Five copies of those tables had accumulated across Constraint,
CompilingMatcher and the plugin value bridge.
version_compare now takes a CmpOp, which makes an invalid operator
unrepresentable and removes the tables' reason to exist. Constraint
stores a CmpOp and keeps only the string parsing its constructor needs;
getOperator, compile and CompilingMatcher::match speak CmpOp as well.
PHP's OP_* numbering stays observable: a plugin reads the raw integer off
the Constraint object over RPC, so get_operator_constant and its new
inverse hold that 0..5 mapping.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
AutoloadGenerator::dump dispatches PRE_AUTOLOAD_DUMP / POST_AUTOLOAD_DUMP, and
EventDispatcher::make_autoloader answers those by walking back into the Composer
graph -- the local repository, the installation manager, and the generator
itself. Installer::run held mutable borrows of all three across the dump call,
so any plugin subscribing to either event panicked with "RefCell already
borrowed" before its listener ran.
dump() and build_package_map() now take the local repository, the installation
manager and the locker as shared handles instead of &mut dyn, and the borrow is
taken where it is used.
The generator itself is re-entered, not just borrowed twice: PHP listeners reach
it through Composer::getAutoloadGenerator() and call setDevMode() while dump()
is running, and make_autoloader asks the same object for a package map. That is
not expressible behind &mut self, so the mutable state moves into Cell/RefCell
fields and the whole AutoloadGeneratorInterface takes &self.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The RuntimeInterface seam asked the worker one question at a time: a round
trip per loaded extension, per ReflectionExtension::info() output and per
constant, so a single `show --platform` cost 70 to 100 of them. A `platform`
dispatch entry now answers all of it as one PHP array, which shirabe-php-rpc
decodes into a OnceLock-cached PlatformInfo, the way the diagnose command
already works.
Composer\Platform\Runtime therefore has no Rust counterpart any more. Its
work belongs to the running interpreter, and invoke()/construct() could only
be ported as a whitelist that panicked on anything unlisted; it is ported as
PHP into the worker instead, and PlatformRepository reads the answers off
PlatformInfo. Accessors panic on a name the payload does not carry, so the
worker and its consumers cannot drift apart unnoticed.
The tests describe the runtime as payload data where they used to mock the
seam, with the datasets unchanged. The one loss is the call-count assertion
of test_inet_pton_regression: the payload reports the result of
`@inet_pton('::')` rather than answering a call.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Enable clippy::multiple_inherent_impl and fix the 21 sites it reports.
Types whose inherent methods were spread across two or three impl blocks
now keep them in a single block; only the impl headers move, no method
bodies change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
|
|
Call sites whose haystack was an inline array of literals (or a local
built solely to feed one) had to wrap both sides in PhpMixed just to
compare, allocating a String per element on every call. matches! does
the same test against the underlying &str/i64/Option directly, so the
PhpMixed round trip and its .to_string()/.clone()/.iter().map()
conversions are gone.
Sites whose haystack is a runtime value or a named constant array are
left on in_array_strict: inlining a named constant would duplicate its
contents at the call site.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
|
|
PHP hands back a positional array, so every caller had to reach into a
PhpMixed list by index and fall back to a default when an element was
missing or the wrong variant -- fallbacks that could never fire, since
the list shape is fixed. LicenseMetadata names the four elements and
makes the accesses total.
This also drops the crate's last use of PhpMixed, and with it the
dependency on shirabe-php-shim.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The ported docblocks copied @param and @return straight from the PHP
source. When such a tag carries nothing but a type and an argument name,
the Rust signature already states it, so the line is noise. Tags whose
text adds prose beyond the type are kept.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
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>
|