diff --git a/src/Flame/Composer/Manager.php b/src/Flame/Composer/Manager.php index 381aea6b..3fad41e4 100644 --- a/src/Flame/Composer/Manager.php +++ b/src/Flame/Composer/Manager.php @@ -269,6 +269,12 @@ protected function restoreComposerFiles(): void protected function getProcess(array $command, array $env = []): Process { + foreach (['HOME', 'COMPOSER_HOME'] as $key) { + if (!isset($env[$key]) && !getenv($key)) { + $env[$key] = $this->storagePath; + } + } + return (new Process($command, $this->workingPath, $env))->setTimeout(null); } diff --git a/tests/src/Flame/Composer/ManagerTest.php b/tests/src/Flame/Composer/ManagerTest.php index 82a9393a..14a5b508 100644 --- a/tests/src/Flame/Composer/ManagerTest.php +++ b/tests/src/Flame/Composer/ManagerTest.php @@ -8,8 +8,10 @@ use Exception; use Igniter\Flame\Composer\Manager; use Igniter\Flame\Support\Facades\File; +use ReflectionMethod; use RuntimeException; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\Process; it('loads package version correctly', function() { $version = resolve(Manager::class)->getPackageVersion('some-package'); @@ -267,3 +269,69 @@ $manager = new Manager(__DIR__, '/storage'); expect(fn() => $manager->assertSchema())->toThrow(RuntimeException::class); }); + +it('injects HOME and COMPOSER_HOME when the parent process has neither set', function() { + $originalHome = getenv('HOME'); + $originalComposerHome = getenv('COMPOSER_HOME'); + putenv('HOME'); + putenv('COMPOSER_HOME'); + + try { + $manager = new Manager('/root', '/storage'); + $method = new ReflectionMethod(Manager::class, 'getProcess'); + $method->setAccessible(true); + + /** @var Process $process */ + $process = $method->invoke($manager, ['composer', 'install'], ['COMPOSER_MEMORY_LIMIT' => '-1']); + + expect($process)->toBeInstanceOf(Process::class) + ->and($process->getEnv())->toMatchArray([ + 'HOME' => '/storage', + 'COMPOSER_HOME' => '/storage', + 'COMPOSER_MEMORY_LIMIT' => '-1', + ]); + } finally { + $originalHome === false ? putenv('HOME') : putenv('HOME='.$originalHome); + $originalComposerHome === false ? putenv('COMPOSER_HOME') : putenv('COMPOSER_HOME='.$originalComposerHome); + } +}); + +it('does not override HOME or COMPOSER_HOME inherited from the parent process', function() { + $originalHome = getenv('HOME'); + $originalComposerHome = getenv('COMPOSER_HOME'); + putenv('HOME=/parent/home'); + putenv('COMPOSER_HOME=/parent/composer'); + + try { + $manager = new Manager('/root', '/storage'); + $method = new ReflectionMethod(Manager::class, 'getProcess'); + $method->setAccessible(true); + + /** @var Process $process */ + $process = $method->invoke($manager, ['composer', 'install']); + + expect($process->getEnv()) + ->not->toHaveKey('HOME') + ->and($process->getEnv())->not->toHaveKey('COMPOSER_HOME'); + } finally { + $originalHome === false ? putenv('HOME') : putenv('HOME='.$originalHome); + $originalComposerHome === false ? putenv('COMPOSER_HOME') : putenv('COMPOSER_HOME='.$originalComposerHome); + } +}); + +it('allows callers to override HOME and COMPOSER_HOME on the spawned composer process', function() { + $manager = new Manager('/root', '/storage'); + $method = new ReflectionMethod(Manager::class, 'getProcess'); + $method->setAccessible(true); + + /** @var Process $process */ + $process = $method->invoke($manager, ['composer', 'install'], [ + 'HOME' => '/custom/home', + 'COMPOSER_HOME' => '/custom/composer', + ]); + + expect($process->getEnv())->toMatchArray([ + 'HOME' => '/custom/home', + 'COMPOSER_HOME' => '/custom/composer', + ]); +});