Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/Console/Composer/Json.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace Statamic\Console\Composer;

use Illuminate\Support\Env;
use Statamic\Facades\File;
use Statamic\Facades\Path;
use Statamic\Support\Arr;
Expand All @@ -11,7 +10,12 @@ class Json
{
public static function filename(): string
{
return trim((string) Env::get('COMPOSER')) ?: 'composer.json';
// Read the env var using vanilla PHP so that this can be run in a Composer hook, where
// Composer registers the class autoloader but never runs the `autoload.files` entries.
// That means Laravel's helper functions don't exist, and `Env::get()` relies on `value()`.
$filename = $_ENV['COMPOSER'] ?? $_SERVER['COMPOSER'] ?? getenv('COMPOSER');

return trim((string) $filename) ?: 'composer.json';
}

public static function path(): string
Expand Down
67 changes: 67 additions & 0 deletions tests/Composer/ComposerLockBackupTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

namespace Tests\Composer;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use Statamic\Console\Composer\Lock;
use Symfony\Component\Process\Process;

/**
* Test that we can backup a composer lock file using vanilla PHP so that it can be run in a Composer hook.
Expand All @@ -15,6 +17,7 @@ class ComposerLockBackupTest extends \PHPUnit\Framework\TestCase
protected $customLockPath = './custom/composer.lock';
protected $backupLockPath = './storage/statamic/updater/composer.lock.bak';
protected $customBackupLockPath = './custom/storage/statamic/updater/composer.lock.bak';
protected $tempDir;

public function setUp(): void
{
Expand All @@ -26,6 +29,7 @@ public function setUp(): void
public function tearDown(): void
{
$this->removeLockFiles();
$this->removeTempDir();

unset($_ENV['COMPOSER']);

Expand Down Expand Up @@ -86,6 +90,69 @@ public function it_can_backup_lock_file_from_custom_location()
$this->assertEquals($content, file_get_contents($this->customBackupLockPath));
}

#[Test]
#[DataProvider('composerScriptContextProvider')]
public function it_can_backup_the_lock_file_in_a_composer_script_context($composerEnv, $expected)
{
$dir = $this->makeTempDir();

file_put_contents($dir.'/composer.lock', 'default lock file content');
file_put_contents($dir.'/composer.testing.lock', 'env lock file content');

// Composer builds a class autoloader for script events but never runs the `autoload.files`
// entries, so none of Laravel's or Statamic's helper functions exist. Replicate that here
// to ensure nothing reachable from the hook depends on them.
$script = str_replace('{{ vendor }}', realpath(__DIR__.'/../../vendor'), <<<'EOT'
require '{{ vendor }}/composer/ClassLoader.php';

$loader = new Composer\Autoload\ClassLoader;

foreach (require '{{ vendor }}/composer/autoload_psr4.php' as $namespace => $paths) {
$loader->setPsr4($namespace, $paths);
}

$loader->addClassMap(require '{{ vendor }}/composer/autoload_classmap.php');
$loader->register();

Statamic\Console\Composer\Lock::backup();
EOT);

$process = new Process(['php', '-r', $script], $dir, ['COMPOSER' => $composerEnv]);

$process->run();

$this->assertTrue($process->isSuccessful(), $process->getErrorOutput());
$this->assertEquals($expected, file_get_contents($dir.'/storage/statamic/updater/composer.lock.bak'));
}

public static function composerScriptContextProvider()
{
return [
'without the env var' => [false, 'default lock file content'],
'with the env var' => ['composer.testing.json', 'env lock file content'],
];
}

private function makeTempDir()
{
mkdir($dir = sys_get_temp_dir().'/statamic-composer-hook-'.bin2hex(random_bytes(6)));

return $this->tempDir = $dir;
}

private function removeTempDir($dir = null)
{
if (! ($dir ??= $this->tempDir) || ! is_dir($dir)) {
return;
}

foreach (array_diff(scandir($dir), ['.', '..']) as $item) {
is_dir($path = $dir.'/'.$item) ? $this->removeTempDir($path) : unlink($path);
}

rmdir($dir);
}

private function removeLockFiles()
{
$files = [
Expand Down
Loading