aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/command
AgeCommit message (Collapse)Author
2026-07-25fix(package-discovery): honor ignored platform reqs in shadowed-repo lookupnsfisis
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>
2026-07-25fix(require-command): end input borrow before asking for confirmationnsfisis
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>
2026-07-25fix(package-discovery): pass IO so platform requirement warnings surfacensfisis
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>
2026-07-25refactor(json): embed Composer schemas instead of copying them to targetnsfisis
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.
2026-07-25refactor: replace redundant clones with movesnsfisis
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25refactor(operation): replace OperationInterface with AnyOperation enumnsfisis
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>
2026-07-24fix(event-dispatcher): invoke Closure listeners instead of always failing ↵nsfisis
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.
2026-07-24fix(pool-builder): use plain getPackages() on locked repositorynsfisis
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.
2026-07-20fix(search-command): avoid usize overflow panic when truncating descriptionnsfisis
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.
2026-07-20fix(diagnose-command): stop holding a Config borrow across http callsnsfisis
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.
2026-07-20feat(php-rpc): implement phpinfo() capture over RPCnsfisis
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.
2026-07-20fix(require-command): drop input borrows before determine_requirementsnsfisis
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>
2026-07-20fix(update-command): un-ignore test_interactive_tmp via assoc select choicesnsfisis
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>
2026-07-20fix(run-script-command): pass script choices as assoc array in interactnsfisis
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>
2026-07-20fix(io): widen IOInterface::select choices to PhpMixed for assoc arraysnsfisis
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>
2026-07-19fix(show-command): un-ignore 10 tests by fixing show-warnings typing and ↵nsfisis
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>
2026-07-19fix(search-command): un-ignore test_search by fixing JSON output and ↵nsfisis
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.
2026-07-19fix(update-command): un-ignore test_update by fixing 3 real bugsnsfisis
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.
2026-07-19fix(check-platform-reqs): restore raw Link column in text tablensfisis
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.
2026-07-18refactor(downloader): take &self across the downloader hierarchynsfisis
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>
2026-07-18perf(regex): eliminate per-call clone overhead in preg_* dispatchnsfisis
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>
2026-07-18fix(require-command): avoid unsupported regex lookahead in dev-branch checknsfisis
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.
2026-07-16fix(proxy-manager): correct singleton lifecycle and simplify to a plain Mutexnsfisis
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>
2026-07-11chore: use fully-qualified name for Rc/RefCellnsfisis
2026-07-07chore: fix stale commentnsfisis
2026-07-06fix(base-config-command): run BaseCommand::initialize before config setupnsfisis
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>
2026-07-05feat(run-script-command): resolve script command descriptions via find()nsfisis
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.
2026-07-05feat(exec-command): restore working directory via getInitialWorkingDirectorynsfisis
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.
2026-07-05feat(global-command): wire resetComposer and Application::run proxyingnsfisis
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.
2026-07-05feat(init-command): wire update/dump-autoload sub-command dispatchnsfisis
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.
2026-07-05fix(base-command): honour Application plugin/script disable defaultsnsfisis
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.
2026-07-05feat(reinstall-command): wire InstallationManager::execute and ↵nsfisis
AutoloadGenerator::dump The two phase-c TODOs blocking these calls were already resolved elsewhere (RepositoryInterfaceHandle::as_installed_repository_interface_mut was added after this file was ported), so reinstall now actually performs the uninstall/install operations and regenerates the autoloader instead of no-oping. Mirrors the pattern already used in installer.rs and dump_autoload_command.rs.
2026-07-04fix(search-command): use as_list() to read variadic tokens argumentnsfisis
ArgvInput stores variadic arguments as PhpMixed::List, not PhpMixed::Array, so as_array() always returned None and the search query was silently empty, causing packagist to reject every request with a 400 Bad Request.
2026-07-04fix(create-project-command): split chained config.borrow_mut() callsnsfisis
install_project's fluent builder chain called config.borrow_mut() four times inline as method arguments. Rust extends every argument temporary's lifetime to the end of the enclosing statement, so the first borrow_mut() was still alive when the second one ran, panicking with "already borrowed". Compute each value into a local before the chain instead, matching the pattern already used in install/update/require/remove_command.rs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04fix(pcre): restore missing regex delimiters in ported patternsnsfisis
Several Preg::*() call sites lost their PHP delimiter (and in one case the `i` modifier) during porting, since preg_*() expects the delimiter to be preserved in the caller's pattern literal and stripped internally. This made compile_php_pattern panic or silently misparse the pattern. Un-ignore the Version tests that were blocked by this bug.
2026-06-29feat(console): implement StringInput stringification in global proxynsfisis
StringInput inherits __toString from ArgvInput in PHP; mirror that with a Display impl delegating to its inner ArgvInput, and use it in GlobalCommand::input_to_string. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29feat(require): update locker hash with stability flags rewriternsfisis
Resolve the remaining todo! in update_requirements_after_resolution by passing the stability-flags rewriter closure to Locker::update_hash, porting the static closure from RequireCommand.php. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29feat(audit): implement get_packages non-locked branchnsfisis
Port AuditCommand::getPackages's non-locked path: build an InstalledRepository from the local repository and return its packages, filtered by RootPackage requires when --no-dev is set. The prior TODO(phase-c) assumption (InstalledRepository::new vs get_local_repository type mismatch) no longer holds since both sides use RepositoryInterfaceHandle. Enables the two previously ignored audit command tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29fix(advisory): pass IO as shared handle to Auditor::auditnsfisis
Auditor::audit took io as &mut dyn IOInterface, forcing the audit and installer post-audit call sites to hold a borrow_mut() on the shared IO RefCell for the whole call. During advisory fetching the repositories write to their own clones of the same handle, so the borrow_mut() collided with their borrow() and panicked with 'RefCell already mutably borrowed' on 'audit --locked'. Take the Rc<RefCell<dyn IOInterface>> handle instead so writes borrow briefly and never overlap. Un-ignore the locked-audit regression test that this unblocks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29chore(lint): ban bare `use anyhow::Result` and fully qualify itnsfisis
Add a no_banned_use linter that forbids importing anyhow::Result, and update all call sites to reference it via its fully-qualified path so it is never confused with std::result::Result. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28fix(repository): flatten InstalledRepository and unwrap filter repos in shownsfisis
flattenRepositories must recurse into InstalledRepository (which extends CompositeRepository in PHP) and ShowCommand must unwrap FilterRepository when categorizing repos. Without this, installed/locked/platform packages all fell through to the "available" bucket, dropping the version column and per-section grouping. Un-ignores 10 show_command tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28test(installer): port InstallerTest unit and integration harnessnsfisis
Port composer/tests/Composer/Test/InstallerTest.php. testInstaller (the provideInstaller cases) is fully ported and passes; the three integration tests port doTestIntegration in full (the .test fixture loader, FactoryMock, the in-process console Application with install/update commands, and the PHPUnit assertStringMatchesFormat matcher) and remain #[ignore]'d since the install pipeline is not yet executable end-to-end. Add test-only `__`-seams to the concrete types the test depends on, since their consumers (e.g. Locker takes the concrete InstallationManager) and the subclass-style mocks have no trait to mock: InstallationManager (recording mock + as_any), Factory (__create_mock), VersionGuesser, and InstalledFilesystemRepository. The production path (mock: false) is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28refactor: add linternsfisis
2026-06-27feat(command/update): port UpdateCommand input definitionnsfisis
Replace the empty set_definition stub with the full InputArgument/ InputOption set from Composer's UpdateCommand. The symfony input modeling was already complete; this was the last command still passing an empty definition, which made it reject its own options. Un-ignores test_no_security_blocking_allows_insecure_packages (now passing) and re-labels the six remaining update tests with their actual blockers (regex porting, resolver temporary-constraint, interactive mode, bump-after-update solver pool) since the old "empty InputDefinition" reason no longer applies. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27refactor(http): present reqwest instead of faking curl_versionnsfisis
- StreamContextFactory: User-Agent reports the HTTP stack as "reqwest" - RequestProxy::supports_secure_proxy: always true (reqwest+rustls can always TLS to a proxy); drop the now-dead curl<7.52 guard in get_curl_options - DiagnoseCommand::get_curl_version: phase-D TODO placeholder Empirically verified: reqwest sends no default User-Agent (shirabe sets it explicitly) and accepts https:// proxy URLs. init+install output stays byte-identical to Composer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27fix(json): encode empty stdClass fields as {} not []nsfisis
Empty PhpMixed::Array now serializes as [], so the several places that build a PHP `new \stdClass` via an empty Array were emitting [] where Composer writes {}. Use PhpMixed::Object for those empty-object cases: - InitCommand: require / require-dev - Locker::fixupJsonDataType: stability-flags / platform / platform-dev - JsonConfigSource fallback: require/config keys that must stay objects Align the affected test expectations with Composer: JsonFile::read() decodes with assoc=true, collapsing the on-disk {} back to [], so reads expect []. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27test: port Composer tests unblocked by mockall, add seamsnsfisis
Port 11 categories of previously-ignored Composer tests now reachable with the mockall crate: DownloadManager, VCS/Perforce/File downloaders, VersionSelector, PlatformRepository, Auditor, installer/FilesystemRepository, RootPackageLoader, util auth/http, commands, and Cache. Extract test seams additively on concrete structs as *Interface traits (Runtime, HhvmDetector, VersionGuesser, RepositorySet, Perforce, BinaryInstaller) plus mock-field seams (Cache, Filesystem); consumers take trait objects. Mocks are defined locally in the test crates via mockall::mock!, since automock-generated mocks are cfg(test)-gated and invisible across the integration-test boundary. dataProviders are ported in full; tests blocked by unported shims stay #[ignore] with documented reasons rather than reduced or weakened. Fix product bugs surfaced by the ports: - util/github: use the exception code, not the HTTP status, for 401/403 - advisory: serialize empty audit maps as [] to match PHP json_encode - repository/filesystem and downloader/file: fix RefCell double-borrow panics Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27fix(command/show): match PHP null/isset semantics in package outputnsfisis
Three porting mismatches caused show/info output to diverge from PHP: - "not found" hint appended " in /composer.json" whenever the working-dir key existed; PHP uses isset(), which is false for the null default. Now only appended when the value is non-null. - printPackages and generatePackageTree inserted "" for a missing description instead of null, so isset() rendered a spurious trailing space (and JSON emitted "" instead of null). Both now preserve null. Un-ignores the eight rendering-gap tests these fix (plus one already passing), and rewrites the remaining ignore reasons to name the real blocker (package categorization, --no-dev filtering, RefCell borrow, installer resolution) instead of a stale generic message. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27refactor(composer): hold managers behind *Interface traitsnsfisis
Composer/PartialComposer exposed its RepositoryManager, InstallationManager, EventDispatcher, Locker, DownloadManager, AutoloadGenerator and ArchiveManager as concrete types, but Composer's public setters (setDownloadManager() etc.) let plugins swap in subclasses. Introduce a *Interface trait per manager and store each as Rc<RefCell<dyn ...Interface>> so a replacement is honored. Only Composer's slots and the sinks fed from its accessors become trait objects; managers injected concretely at construction keep their concrete references, matching PHP semantics. Fluent setters on the affected classes now return () and Locker::update_hash is de-generified to a boxed FnOnce so the traits stay object-safe. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27refactor(command): replace composer_full_mut with shared composer_fullnsfisis
The compiler confirms none of the call sites invoke a &mut self method on the returned Composer, so the exclusive RefMut borrow was never needed and only risked borrow check conflict. Switch every site to the shared composer_full borrow and drop the now-dead composer_full_mut helper. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>