setAutoExit(false); // A command failure must reach the Rust side as an RPC throw, not get rendered here. $app->setCatchExceptions(false); $app->shirabeComposer = $config['composer']; $app->shirabeIo = $config['io']; $app->shirabeInitialWorkingDirectory = $config['initialWorkingDirectory']; $app->shirabeDisablePluginsByDefault = $config['disablePluginsByDefault']; $app->shirabeDisableScriptsByDefault = $config['disableScriptsByDefault']; foreach ($config['rustCommands'] as $meta) { $app->add(new \Shirabe\RustCommandStub($meta)); } foreach ($config['pluginCommands'] as $command) { $app->add($command); } return $app; } public function __construct(string $name = 'Composer', string $version = '') { if (!self::$shirabeConstructing) { // TODO(plugin): a plugin constructing its own Composer\Console\Application would // run commands outside the Rust-side orchestration; whether and how to support // that is undecided, so fail loudly instead of handing out a half-wired instance. throw new \RuntimeException( 'Shirabe does not support constructing Composer\Console\Application inside the plugin process yet' ); } parent::__construct($name, $version !== '' ? $version : Composer::getVersion()); } public function getIO(): IOInterface { return $this->shirabeIo; } public function getComposer(bool $required = true, ?bool $disablePlugins = null, ?bool $disableScripts = null): ?Composer { if (null === $this->shirabeComposer && $required) { // TODO(plugin): creating a Composer instance from scratch here would need // Factory::create over RPC; the handoff currently always carries the instance the // Rust side already built, so this only fires after resetComposer-style flows. throw new \RuntimeException( 'Shirabe cannot create a new Composer instance inside the plugin process yet' ); } return $this->shirabeComposer; } public function resetComposer(): void { // TODO(plugin): the Composer instance is owned by the Rust process; dropping only the // worker-side reference would desynchronize the two worlds, so this needs a // reset-and-refetch round trip that does not exist yet. throw new \RuntimeException( 'Shirabe does not support resetComposer() inside the plugin process yet' ); } /** * @return string|null */ public function getInitialWorkingDirectory() { return $this->shirabeInitialWorkingDirectory; } public function getDisablePluginsByDefault(): bool { return $this->shirabeDisablePluginsByDefault; } public function getDisableScriptsByDefault(): bool { return $this->shirabeDisableScriptsByDefault; } // Same as the real class: without Composer's global options in the definition, binding a // forwarded command line that carries e.g. --working-dir would fail here even though the // Rust side already consumed the option. protected function getDefaultInputDefinition(): InputDefinition { $definition = parent::getDefaultInputDefinition(); $definition->addOption(new InputOption('--profile', null, InputOption::VALUE_NONE, 'Display timing and memory usage information')); $definition->addOption(new InputOption('--no-plugins', null, InputOption::VALUE_NONE, 'Whether to disable plugins.')); $definition->addOption(new InputOption('--no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.')); $definition->addOption(new InputOption('--working-dir', '-d', InputOption::VALUE_REQUIRED, 'If specified, use the given directory as working directory.')); $definition->addOption(new InputOption('--no-cache', null, InputOption::VALUE_NONE, 'Prevent use of the cache')); return $definition; } }