| Age | Commit message (Collapse) | Author |
|
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>
|
|
MAIN_SEPARATOR is the path separator; PHP's `'\\' === DIRECTORY_SEPARATOR`
uses it as an OS test only because PHP has no dedicated one. cfg!(windows)
says what the branch actually selects on, and leaves MAIN_SEPARATOR to the
sites that really join or split paths.
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>
|
|
The deadline is a constant baked in at build time, so PHP's define() of
COMPOSER_DEV_WARNING_TIME cannot make Application take that branch.
Application now holds the deadline in a field that __set_dev_warning_time
overrides, letting testDevWarning run instead of staying ignored. The
define() shim, whose only caller was that test, goes away with it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The port's fall-through stood in for PHP's `catch (\Exception $e)`, which
does not catch an `\Error`; a TypeError reaching JsonFile::read would come
back out as a `\RuntimeException` saying the file could not be read.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
PharArchiver::archive catches only `\UnexpectedValueException` and wraps
it in a `\RuntimeException` naming the target and sources; anything else
leaves the method as it was thrown. The port wrapped every error, so the
two `\RuntimeException('Can not compress to %s format')` raised inside
the `try` came back out with a second message around them and their code
replaced. Reading the message and code off the caught exception rather
than off whatever arrived also drops the guesswork the old `map_or(0, ..)`
had to do.
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>
|
|
Composer defines COMPOSER_DEV_WARNING_TIME from its phar stub when the
compiled version is a commit hash rather than a tag, so the value exists
only in the build artifact. It was a todo!() in the PHP shim, leaving the
warning branch in Application::do_run unreachable.
A build script now derives it the way Compiler does, from git describe
and the HEAD commit date, and composer::COMPOSER_DEV_WARNING_TIME holds
the result as a Rust constant instead of a runtime-defined one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Getting a rule's identity meant spl_object_hash(&*rule.borrow()), so the
solver borrowed a RefCell just to read an address, and a second function
spl_object_hash_process existed because one generic fn cannot tell Rc<T>
from &T.
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>
|
|
|
|
InstallationManager left both PRE_PACKAGE_* and POST_PACKAGE_* as empty
stubs, so a subscriber never ran at all and the difference from upstream was
silent rather than an explicit error.
Operations cross the boundary as R-table entities with generated proxy
stubs. Materializing them the way a Link crosses is not possible: a
materialized value is revived by unserialize() on the child side, so its
properties never pass through the wire decoder and a nested handle
descriptor would not come back as a stub — and an operation always holds a
PackageInterface. execute() now shares one Rc per operation through the
whole batch pipeline, so a plugin sees one object for both the pre- and the
post-event of an operation, as it does in PHP.
POST_PACKAGE_* also moves out of the operation's promise chain into the
post-exec callback list PHP runs after waitOnPromises().
The stub generator materializes non-public class constants verbatim now,
which the operation classes need for their `protected const TYPE`: a
constant has no entity behind it, so a copy in the worker cannot diverge,
and keeping the declared visibility exposes nothing the real class hides.
The E2E fixture added here compares the recorded events against upstream
Composer. It also surfaced that upstream starts an operation's chain where
it is built (a null prepare() becomes an already-fulfilled React promise
whose handlers run through the immediately drained queue) while this port
only drives its futures in wait_on_promises, so the repository state a
pre-event observes differs; that half of the comparison is a separate
`#[ignore]`d test.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
A proxy stub declares none of the real class's instance properties, and the
`__get`/`__set` forwarders were emitted only for classes that declare a public
one. Every other property access therefore got PHP's own answer for an
undeclared property — null on a read, a dynamic property on a write — so plugin
code reading state the entity holds ran on with null and failed somewhere else
entirely, or not at all.
Emit the forwarders on every root stub, add `__isset`/`__unset` alongside them
so `isset()` cannot answer false silently either, and serve all four from one
dispatcher that answers the state the Rust-side entity exposes and rejects every
other name.
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>
|
|
AliasPackage, CompleteAliasPackage and RootAliasPackage now have generated
proxy stubs, so a package handed to a plugin no longer has to be a real
package: it crosses as the stub matching its concrete variant, answers
getAliasOf / setRootPackageAlias / isRootPackageAlias /
hasSelfVersionRequires, and can be constructed from plugin code.
The setters RootPackageInterface declares are routed through that interface
for every root package instead of through the base Package state. Only the
alias variant needs it -- RootAliasPackage overrides all nine to write
through to the package it aliases -- but a real RootPackage delegates to the
same base state either way, so both take one path.
An alias of an alias has no representation here, so narrowing the
constructor argument to a real package is an explicit error rather than a
silent demotion.
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>
|
|
|
|
Replace the todo!() bodies with real ports. Four autoload-generator
tests now run for real; the rest stay #[ignore]d, but each ignore reason
now names the concrete missing symbol instead of a vague subsystem.
Production additions the ports need: the deprecated
AuthHelper::addAuthenticationHeader wrapper,
EventDispatcher::__set_dispatch_script_override as the seam for PHPUnit
onlyMethods(['dispatchScript']), and a define() stub in the shim.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
A materialized value used to cross as a constructor call: the class name,
the arguments, and any post-construction setter. Describing a real
instance that way needed a ReflectionProperty read for every field the
class exposes no getter for, and state no constructor takes (an unset
pretty string, a Link built without a pretty constraint) had no faithful
call to describe it at all.
The value now crosses as the object record serialize() writes for it,
which unserialize() revives without running a constructor, so both sides
transfer the state itself instead of a recipe for rebuilding it. The PHP
half keeps only the class list (also the allowed_classes list of every
frame payload) and the UTC rebasing of dates; describe(), build() and the
reflection are gone.
The wire codec gains O: records (PluginValue::PhpObject) and r: back
references, whose resolution reproduces PHP's numbering of every value in
a payload; a cyclic object graph and a PHP reference (R:) are rejected.
Two behaviours change with it: a date crosses carrying timezone_type 3
"UTC" rather than a +00:00 offset, which is what ArrayLoader builds a
release date as, and a Link subclass crosses as a P-table entity instead
of being silently downgraded to a plain Link.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The transport bound an AF_UNIX path under TMPDIR and waited for the child to
connect. That path has to fit in sun_path (108 bytes), so a deep TMPDIR made
every worker spawn fail, and the bind(2) itself is denied under a sandbox.
The parent now keeps one end of a socketpair and installs the other on
descriptor 3 before exec, which the worker opens as php://fd/3. The pair is
connected from the start, so the accept poll and its ten-second deadline are
gone; a child that dies before reading surfaces as EOF on the first call,
where worker_state already attaches its exit status.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
PlatformRepository probes ResourceBundle, IntlChar and Imagick to derive
lib-icu-cldr, lib-icu-unicode and lib-imagick-imagemagick. Those probes ran
against the shim's hard-coded class_exists allowlist, which never names
them, so the packages were silently missing: on a machine with intl,
`show --platform` listed fewer libraries than upstream Composer does.
The runtime seam now asks the real PHP: hasClass over RPC, and construct /
invoke through the worker for the three classes PlatformRepository reaches.
A live PHP object has no PhpMixed counterpart, so the seam answers with the
entries the caller reads off it. The seam's own callers read those entries
instead of returning null and the empty string.
Two addLibrary calls also had replaces and provides swapped, dropping
`lib-libxslt replaces lib-xsl` and `lib-zip-libzip replaces lib-zip`.
`show --platform` now matches upstream Composer byte for byte, and all 59
provideLibraryTestCases datasets pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
createRepository instantiates `new $class(...)` from the name registered
for the type. The port dispatches over the classes it implements, and the
remaining arm was a todo!(). setRepositoryClass is public API, so a plugin
registering a class of its own reached it and panicked.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
getDownloaderForPackage reports get_class($downloader) when the resolved
downloader's installation source does not match. Rust has no runtime class
name, so the message was built from a shim stub that panicked instead —
the error could never be returned.
DownloaderInterface now requires PhpClass, the trait already used for the
same purpose on Command, and each downloader states the name PHP reports.
That leaves get_class_obj without callers, so it is gone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The reason string blamed shirabe_php_shim::runtime::constant(), which
PlatformRepository stopped reaching once its constant lookups went through
the RuntimeInterface seam. All four pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The link getters and setters and the release date accessors were explicit
errors for every non-empty value, because an immutable value has no entity
to point a handle at. They now cross as materialized values: the descriptor
names the real class and the constructor arguments, and each side builds a
genuine instance of its own.
The semver constraint a link holds is encoded structurally rather than
re-parsed from its string form, so the pretty strings and the conjunctive
flag survive the crossing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
pathinfo only ever receives a string and only ever returns one for the
single-component options it supports, so the PhpMixed wrapping forced
every call site to pack and unpack the value again.
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>
|
|
|
|
The generated-file templates are heredocs in Composer's
AutoloadGenerator. Writing them as raw string literals keeps the emitted
PHP laid out the way it is written out, matching the blocks in
getAutoloadRealFile and getStaticFile that already use them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The proxy templates are heredocs in Composer's BinaryInstaller. Writing
them as raw string literals keeps the generated PHP and shell code laid
out the way it is emitted, instead of one escaped line.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The docblock allows string|iterable<SplFileInfo>, but every call site in
Composer passes a string, so the iterable branch was only a todo!().
Taking &str drops it together with the InvalidArgumentException that the
type now rules out.
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>
|
|
getLicenses(), getExceptionByIdentifier(), getIdentifierByName(),
isOsiApprovedByIdentifier(), isDeprecatedByIdentifier() and the
LICENSES_FILE / EXCEPTIONS_FILE constants had no callers.
Unused members are normally kept so the port stays a faithful mirror of
the PHP class, but SpdxLicenses is pure logic over the bundled SPDX data
that holds no Composer state, and plugins reach the real PHP
implementation rather than this port, so nothing can observe the
difference. The struct now records what is left out and why.
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>
|
|
PHP mutates the caught exception's protected $code via ReflectionProperty.
The port takes ownership of the exception through anyhow's downcast, writes
ERROR_TRANSPORT_EXCEPTION into its public code field and re-wraps it, so no
reflection equivalent is needed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Port the Symfony Filesystem branches this file had left out:
* copy() preserves the origin mtime via touch()
* exists() rejects paths longer than PHP_MAXPATHLEN - 2, so its return
type becomes anyhow::Result<bool>
* doRemove()'s symlink branch keeps the DIRECTORY_SEPARATOR disjunct,
which stops it from throwing on Unix where upstream never does
* symlink() normalizes separators and mirrors instead of linking when
copyOnWindows is set
* mirror() skips entries whose real path is the target directory or was
already created earlier in the same call
PHP_MAXPATHLEN is new in shirabe-php-shim.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The shim's filesystem entry points took `&str` even though each one resolves
to a local path through `std::fs` or a syscall, so callers holding a `PathBuf`
had to stringify it at the call site. They now take `impl AsRef<Path>`, the
form `file_exists`, `is_dir`, `unlink` and the rest of the already-converted
set use.
`Phar`, `PharData` and `ZipArchive` keep their archive path as a `PathBuf`.
`PharData::compress` names the compressed sibling by appending the suffix to
the file name rather than formatting the path into a `String`.
Arguments PHP resolves through a stream wrapper (`fopen`, `file_put_contents`,
`include`) still take `&str`, as do the byte-string operations (`dirname`,
`basename`, `pathinfo`) and archive-internal entry names, which are `/`-joined
logical names rather than OS paths.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
A single install leaves the installer contract half tested: it never reaches
`update`, never runs over an already-installed tree, and never reaches the
plugin's own `uninstall()` override — the one chaining onto the promise
`LibraryInstaller::uninstall` returns. Nor does it touch the configuration
surface real projects use, `installer-paths` and `installer-name`, which the
plugin reads back through the package proxy.
All three now compare command output as well as the resulting tree against
upstream Composer. Progress bar frames are dropped from that comparison:
Shirabe renders them differently for every install, including projects with
no plugin at all, so they say nothing about the plugin under test.
With those covered the plugin joins the verified list in the README.
|
|
The fixture project pins composer/installers 2.3.0 and requires two packages
of framework-specific types, so the plugin's LibraryInstaller subclass decides
where they land. Upstream Composer and Shirabe install the same project and
the whole resulting tree is compared.
The plugin tarball is fetched by `fixtures/e2e-installers/fetch` into a
git-ignored directory and pinned by a digest over the extracted files, so the
third-party source never enters this repository and the test skips itself
while the directory is absent. The tree is staged and only moved into place
once verified, so an unverified tree is never observable under the name the
test looks for.
|
|
An installer that extends LibraryInstaller reaches for the Composer object
graph in ways the proxy did not answer: the download manager and the config,
the writable repository methods, a React promise as its own return value, and
`new Package(...)` from its supports() path.
* `Composer::getConfig`/`getDownloadManager` are dispatched, and both classes
become generated proxy stubs. Their reads and mutators answer from the Rust
entity; the surfaces needing stubs of their own (ConfigSourceInterface,
DownloaderInterface) stay explicit errors.
* The download manager's futures are driven to completion and handed back as
already-settled React promises, since PHP declares a non-nullable
PromiseInterface there. A promise a plugin returns is drained the same way:
settled yields its value, rejected re-raises, pending is an explicit error.
* Proxy stubs now carry the real class's constructor and ask the Rust side to
allocate the entity; reviving a stub for an existing entity binds the handle
without running it. Classes Rust cannot build name themselves in the error.
* The package proxy covers `Package`'s own setters, `CompletePackage`'s
metadata, `RootPackage`'s root-only state, and `BasePackage::$id`.
Link values and release dates still have no wire image, so the methods
carrying them remain explicit errors.
|
|
The fixture plugin registers an InstallerInterface implementation of its own
and installs a package of a custom type with it, recording every contract
call it receives. A fresh install produces a byte-identical project tree on
both implementations, trace included.
A second test pins the divergence a re-run and a `remove` expose: the two
implementations consult getInstaller at different points, so the trace
order — and its length once `remove` re-creates the Composer instance —
differs. It is written in full and marked ignored rather than trimmed.
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>
|
|
The pinned plugin and its real dependency tree (nine packages, fetched by
commit and verified by tree hash into the git-ignored ext/) install under
both implementations, and the list/help renderings of the plugin-provided
normalize command must match upstream byte for byte. The execution
comparison is written but ignored: NormalizeCommand builds a second,
in-process Composer instance, and the worker's proxy stubs reject native
construction of the classes that path instantiates. Stale comments about
the missing worker-side application are updated to the current facts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
A Shirabe-authored CommandProvider fixture plugin (greet) runs under both
implementations: exit codes, the command's own output, help/list rendering,
alias resolution, a validation failure, and a file recording the shared
object graph the command observed (root package, strict application FQCN,
and the exit code of the built-in about command it invoked) must match.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
A same-FQCN Composer\Console\Application, hand-written under the new
php/runtime/ tree, hosts CommandProvider commands inside the PHP worker:
PhpCommandProxy overrides run() and forwards the stringified input, so the
real Symfony machinery binds, validates and executes against the live
command object, while help/list render Rust-side from a definition read
back at construction. Reverse \Shirabe\RustCommandStub rows let a plugin
command invoke built-in commands back in the Rust process, keeping every
command on the side whose helper set it was written for.
Composer\EventDispatcher\Event moves from a generated stub to a dual-mode
runtime class: the real BaseCommand::initialize constructs a
PreCommandRunEvent natively in the worker, which a proxy-only constructor
guard rejected. Its PRE_COMMAND_RUN dispatch reaches a new EventDispatcher
stub whose dispatch supports the observably-no-op no-listener case and
fails explicitly otherwise. The stub generator now accepts runtime-provided
classes as stub bases (never as targets) and cross-checks the Application
handoff property table against the real class.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Every Symfony input already ports __toString as a Display impl, but callers
holding a dyn InputInterface could not reach it; forwarding a command run
across the plugin RPC boundary needs the stringified input.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Fable 5 <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>
|