aboutsummaryrefslogtreecommitdiffhomepage
AgeCommit message (Collapse)Author
2026-07-16refactor(remote-filesystem): return headers from copy/get_contentsnsfisis
Wrap RemoteFilesystem in Rc<RefCell<>> inside HttpDownloader, in prep for the upcoming &self conversion of add()/get(). copy()/get_contents() now bundle the response headers into their return value instead of requiring a follow-up get_last_headers() call, since two separate calls through a shared RefCell could otherwise race: nothing would guarantee the reader observes the headers from its own request rather than one clobbered by a concurrently borrowed call. get_last_headers() itself is left in place, mirroring RemoteFilesystem::getLastHeaders() in PHP.
2026-07-16refactor(curl-downloader): wrap AuthHelper in Rc<RefCell<>>nsfisis
CurlDownloader's download() is about to become an &self async method as part of the HttpDownloader async rearchitecture; its auth_helper field needs interior mutability ahead of that change. RemoteFilesystem keeps its own AuthHelper as a plain field since it stays &mut self.
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-16refactor(locker-test): reuse test_case's installation_manager helpernsfisis
locker_test.rs defined its own installation_manager, identical to test_case.rs's (both build a bare InstallationManager over a mock HttpDownloader). Expose the shared one via pub(crate) and drop the duplicate. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16test(plugin): port capability-query tests, record remaining TODO(phase-d) ↵nsfisis
reasons Implement test_incapable_plugin_is_correctly_detected and test_querying_non_provided_capability_returns_null_safely against a real PluginManager, using hand-written PluginInterface/Capable stubs in place of PHPUnit's ad-hoc mocks. Wire the shared config_stub test helper into the plugin test binary, and derive Debug on the SetUp struct per this project's convention. The remaining plugin_installer_test.rs cases stay #[ignore]d: install/update/uninstall wiring in PluginInstaller and class instantiation in PluginManager::register_package are still TODO(plugin) stubs, so no plugin ever actually gets registered. Each now records its blocking TODO(plugin) site via // TODO(phase-d). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16test: update TODO reason for unported test casesnsfisis
No test logic changes.
2026-07-16test(downloader): record TODO(phase-d) reasons and port two git-downloader testsnsfisis
zip/git/file downloader tests that were already #[ignore]d for a documented reason lacked the required in-body // TODO(phase-d) comment; record it for each. Two git_downloader_test.rs cases, test_download_uses_various_protocols_and_sets_push_url_for_github and test_update_doesnt_throws_runtime_exception_if_git_command_fails_at_first_but_is_able_to_recover, had been left as todo!() with a TODO(phase-d) claiming ComposerMirror::process_git_url's github regex lacks PCRE delimiters and panics. That regex already has delimiters and does not panic (verified directly), and other tests in this same file already use Package::set_source_mirrors to give a real package a mirror-prefixed getSourceUrls() list, so the stated blocker no longer applies. Port both tests using that existing technique instead of recording a TODO(phase-d) for them. No other test logic changes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16test(util): port remaining todo!() tests in util test suitensfisis
Implement previously-todo!() tests in auth_helper_test.rs, process_executor_test.rs, remote_filesystem_test.rs, and stream_context_factory_test.rs by porting the corresponding PHPUnit test methods. Extend IOStub with writeRaw/setAuthentication call tracking and askAndValidate/getAuthentication overrides to model the PHPUnit mocks these tests rely on, deduping the resulting call-recording fields into a small generic CallRecorder<T> helper instead of repeating the same RefCell<Vec<T>> push/borrow().clone() boilerplate five times. testStoreAuthWithPromptInvalidAnswer and testPromptAuthIfNeededMultipleBitbucketDownloads had initially lost the ported PHPUnit mock's argument/call-count assertions (askAndValidate's exact prompt string, and hasAuthentication/getAuthentication's exactly(2) call counts), silently narrowing what the tests verify; IOStub now records these calls and the tests assert on them, matching upstream. Tests left unportable (PHP set_error_handler machinery, closures in data providers, network/subclass-mock dependencies, etc.) keep #[ignore] with a single // TODO(phase-d) reason recorded in the function body. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-11fix(console-application): render exceptions to ConsoleOutput's error outputnsfisis
Resolve the base_run render_exception TODO by downcasting the OutputInterface handle to the concrete ConsoleOutput type, mirroring `$output instanceof ConsoleOutputInterface` from Symfony's Application::run.
2026-07-11chore: remove stale commentnsfisis
2026-07-11fix(command-loader): return Rc<RefCell<dyn Command>> from get()nsfisis
CommandLoaderInterface::get() returned Box<dyn Command>, which didn't match the Rc<RefCell<dyn SymfonyCommand>> Application::add() expects, leaving both call sites as todo!() panics.
2026-07-11chore: use fully-qualified name for Rc/RefCellnsfisis
2026-07-11feat(completion): thread Rc<InputOption> through suggest_optionsnsfisis
InputDefinition stores options as Rc<InputOption> for sharing, so CompletionSuggestions::suggest_option[s] now accepts Rc<InputOption> instead of owned values, resolving the ownership mismatch left as a todo!() in Application::complete and CompleteCommand.
2026-07-11refactor(application): inline plugin command warning writesnsfisis
Buffering warnings into a Vec was a stale Phase B workaround for a borrow conflict that no longer exists now that io is a separately cloned Rc<RefCell<dyn IOInterface>> handle; write them directly in the loop like the original PHP does.
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(validating-array-loader): implement LoaderInterfacensfisis
Convert errors/warnings/config to RefCell so load() can satisfy the trait's &self signature, matching upstream's `instanceof ValidatingArrayLoader` check in VcsRepository. This makes the InvalidPackageException downcast path in VcsRepository reachable for the first time instead of being permanently dead code.
2026-07-05fix(git-bitbucket-driver): match PHP exception/fallibility semantics for ↵nsfisis
fallback paths VcsDriverInterface::get_source/get_dist were made fallible in the Rust port even though PHP's are infallible, forcing GitBitbucketDriver's fallback delegation to silently swallow errors via unwrap_or_default()/ok().flatten(). Make the trait infallible to match PHP, updating the mechanical Ok(...) wrapping in all implementors. Also narrow attempt_clone_fallback's cleanup to only trigger on RuntimeException, mirroring PHP's catch (\RuntimeException $e), using the same downcast pattern already used by has_composer_file for TransportException.
2026-07-05fix(problem): port PHP reason-string formatting for alias/security-advisory ↵nsfisis
paths Both code paths were left as phase-c placeholders (debug-formatted reason_data, and a security-advisory fallback that ignored getMatchingSecurityAdvisories entirely). The blockers noted in those TODOs were already resolved elsewhere (RuleSetGenerator now wires reason_data for alias rules, and BasePackageHandle/PackageInterfaceHandle are the same type), so port the PHP logic faithfully.
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-05feat(remote-filesystem): support file:// URLs in get_remote_contentsnsfisis
get_remote_contents was a full stub always returning None, so any file:// download raised a TransportException. Read local files directly for the file scheme, mirroring PHP's file_get_contents transparently handling the file:// stream wrapper. Also fixes file_get_contents5 to strip the file:// prefix like the 0-arg variant already did.
2026-07-05perf(pcre): cache compiled regex patterns across preg_* callsnsfisis
Composer's classmap generator re-issues the same PHP-derived pattern string for every scanned file (and every token within it), relying on PCRE's built-in compiled-pattern cache to make that free. shirabe had no equivalent, so every Preg::* call recompiled the pattern from scratch via regex::Regex::new(), making `composer create-project` autoload generation ~130x slower than Composer on a fresh laravel/laravel install (130s vs ~1s). Memoizing compiled patterns by their raw string in compile_php_pattern closes that gap. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-05fix(platform): match PHP truthy semantics for CI env checksnsfisis
Platform::get_env("CI").is_some()/is_none() only checked whether the variable was set, unlike PHP's (bool) Platform::getEnv('CI') which treats "" and "0" as falsy. CI="0" (used by some CI providers to explicitly disable CI mode) would previously flip behavior compared to Composer.
2026-07-05feat(installation-manager): render install/download progress barnsfisis
Real Composer shows a ProgressBar during InstallationManager's waitOnPromises (`Package operations: N installs` ... `0/109 [>---] 0%` ... `100%`), but this port never wired one up: output_progress was set by callers and never consulted. This adds the same gating PHP uses (output_progress, ConsoleIO, not CI, not debug, more than one operation) for both the download phase and the install/extract phase. The port runs downloads/installs serially rather than as concurrently polled promises (see the existing TODO(phase-c-promise) notes), so there is no active-job count to poll for intermediate snapshots. Stepping the bar per completed operation was tried first, but it interleaves with the "- Installing ..." lines mid-terminal-line since both share the same overwrite/newline state; rendering a single 0% -> 100% jump after each phase avoids that garbling at the cost of the timing-driven intermediate snapshots real Composer shows.
2026-07-05fix(autoload-generator): read exclude-from-classmap/classmap as PhpMixed::Arraynsfisis
parse_autoloads_type stores exclude-from-classmap and classmap as PhpMixed::Array (string-keyed), but dump()/create_loader() read them with as_list(), which only matches PhpMixed::List and thus always returned None. exclude-from-classmap patterns declared by vendor packages (e.g. symfony/service-contracts' /Test/) were consequently never excluded from the generated classmap. Switched both call sites to as_array()/.values(), matching the already-correct usage earlier in the same function.
2026-07-05fix(composer-repository): use create_packages for lazy metadata-url packagesnsfisis
load_async_packages (the v2 metadata-url/packagist protocol path) called a separate create_packages_static helper instead of the instance method create_packages. PHP has a single createPackages method used everywhere, so this duplicate silently skipped the notification-url injection (and dist-mirror/transport-options setup) that create_packages performs. Every package resolved via the lazy provider path ended up missing notification-url in composer.lock/installed.json. Removed the now-dead duplicate.
2026-07-04fix(cache): gate "reading/writing ... cache" messages behind debug verbositynsfisis
Cache::read/write/copy_to called write_error (always visible) instead of write_error3(.., IOInterface::DEBUG) like the PHP source, so these messages leaked into default-verbosity output instead of only showing under -vvv, producing extra lines not present in real Composer's output.
2026-07-04fix(class-map-generator): avoid unsupported regex backreference/lookahead in ↵nsfisis
heredoc detection PhpFileCleaner's heredoc-start pattern used a `\1` backreference to match the closing quote, and skip_heredoc's delimiter boundary check used a `(?!...)` negative lookahead. The `regex` crate supports neither, so it panicked with "invalid regex" whenever a scanned PHP file contained a heredoc/nowdoc (e.g. vendor code pulled in via `composer create-project`). The backreference is expanded into three quote-state alternatives, and the lookahead becomes a direct check of the next byte.
2026-07-04fix(console-application): implement --profile via IOInterface::enable_debuggingnsfisis
The --profile flag parsed the option but never actually enabled the timing/memory output, because ConsoleIO::enableDebugging existed only as an inherent method, unreachable through the `dyn IOInterface` handle held by Application. Promote enable_debugging to an IOInterface trait method (default panics, since only ConsoleIO/BufferIO ever legitimately receive this call) so do_run can invoke it directly without downcasting.
2026-07-04feat(php-shim): stub memory_get_usage/memory_get_peak_usage to return 0nsfisis
2026-07-04feat(plugin-installer): implement PluginInstaller::get_plugin_managernsfisis
Wires PartialComposer.as_full() to fetch the PluginManager, replacing the todo!() stub. Mirrors PHP's assertion that $this->composer must be a fully-loaded Composer instance.
2026-07-04fix(php-rpc): panic on RPC failure instead of silently swallowing itnsfisis
Worker spawn failure, socket I/O errors, and unparseable responses used to fall back to plausible-looking defaults (empty string, false, None), making real failures indistinguishable from legitimate PHP-side results. Panicking is not the final design, but replaces silent data corruption with a loud failure until proper error propagation is implemented.
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(platform-repository): avoid infinite recursion in addOverriddenPackagensfisis
PHP's addOverriddenPackage calls parent::addPackage() (a non-virtual call straight to ArrayRepository), but the port called self.add_package, re-entering PlatformRepository::add_package. Since the newly created override package's name also starts with "php-", this recursed forever and blew the stack.
2026-07-04feat(config): use shirabe-branded default home/cache/data dirsnsfisis
Avoid colliding with an existing Composer installation on the same machine by defaulting COMPOSER_HOME/CACHE_DIR/DATA_DIR paths to shirabe/Shirabe instead of composer/Composer, while keeping the env var names, composer.json/lock, and vendor/composer/ unchanged for ecosystem compatibility.
2026-07-04fix(package): reset repository/id on package clonensfisis
AnyPackage::dup() (PHP's `clone $package`) copied the `repository` and `id` fields verbatim instead of resetting them like PHP's BasePackage::__clone() does. LibraryInstaller::install() relies on the duplicate being unbound so it can register the package with the local repository; without the reset, add_package() silently failed with "A package can only be added to one repository", leaving installed.json empty after every install/create-project run.
2026-07-04fix(event-dispatcher): avoid reentrant RefCell panicsnsfisis
Two related "already borrowed" panics reachable from AutoloadGenerator::dump() (which holds the local-repository, installation-manager, and config RefCells for the duration of its own statement, per the temporary-lifetime-extension pattern fixed separately in create_project_command.rs): - ensure_bin_dir_is_in_path called config.borrow_mut() to read "bin-dir", but Config::get only needs &self; use borrow() so it can coexist with an outer borrow instead of conflicting with it. - make_autoloader's real body needed composer_handle.borrow_mut() plus the same local-repository/installation-manager RefCells the caller already holds mutably, which cannot be made reentrant-safe without a larger restructuring. Since all 3 call sites already discard its return value, and its only effect (registering a Composer-generated ClassLoader for autoloading during event-listener PHP execution) is unobservable in this port — there's no embedded PHP interpreter to register it into, and class_exists for user-defined classes is a hardcoded-false shim so the caller's very next check always treats the class as unavailable regardless — make it a genuine no-op. This unblocks the post-autoload-dump event for any script listener naming a PHP class (e.g. Illuminate\Foundation\ComposerScripts), which every create-project/install run reaches once real packages get installed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04fix(installer): preserve fixedRootPackage identity for solver poolnsfisis
PHP's createRepositorySet does `$this->fixedRootPackage = clone $this->package;` once and then passes that same object both to `new RootPackageRepository($this->fixedRootPackage)` and, later, to createRequest($this->fixedRootPackage) — object identity matters because the solver assigns a pool id by mutating the package object itself. The port instead called RootPackageInterfaceHandle::dup() a second time when registering the RootPackageRepository, producing a second object that never went through the pool and so never got an id. create_request's `request.fix_package(root_package_handle)` then referenced a package with id -1, which add_rules_for_request treats as a real bug: "Fixed package ... was not added to solver pool." This surfaced whenever a create-project run reached the second-stage install (i.e. every dist-installed project with real dependencies). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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(xdebug-handler): implement get_skipped_versionnsfisis
The restart-to-disable-xdebug mechanism isn't ported (is_xdebug_active is hardcoded false), so a restart never happens and PHP's self::$skipped stays at its default empty string. Return that directly instead of todo!(), since PlatformRepository::initialize() calls this unconditionally. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04fix(php-rpc): wire Runtime::invoke/get_extension_info/extensionsnsfisis
Route the platform-repository seams that need real PHP introspection through php-rpc instead of todo!()/hardcoded shims: - Runtime::invoke now handles the two dynamic callables PlatformRepository actually reaches (inet_pton, curl_version) via new php-rpc calls; other callables remain unsupported. - Runtime::get_extension_info uses a new `extension_info` php-rpc call (ReflectionExtension::info() + output buffering) instead of todo!(). - Runtime::get_extensions/get_extension_version now query the real PHP process (get_loaded_extensions, phpversion) instead of the shirabe-php-shim's hardcoded "standard CLI environment" model, so platform requirement checks see the extensions actually installed. 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-07-04test: ignore broken testsnsfisis
2026-07-02feat(php-rpc): implement Runtime::has_constant/get_constant via php-rpcnsfisis
Runtime::hasConstant/getConstant need a real PHP interpreter's defined()/ constant() to answer platform requirement checks (e.g. PHP_ZTS, PHP_INT_SIZE), which the shim can't provide since Rust constants aren't queryable by string. Extend shirabe-php-rpc's protocol to carry one string argument and return the full PHP scalar range, add defined/constant dispatch entries to the worker, and wire Runtime and get_php_version/get_php_binary onto them.
2026-07-02chore(lint): ban std::io::Read/Write, Any, Command use importsnsfisis
Extends no_banned_use to cover std::any::Any, std::io::Read/Write, and std::process::Command, and teaches the linter to allow `as _` imports so trait methods can still be brought into scope without binding the banned name. Fully qualifies all existing usages across the codebase.