build: M1 Dockerized development stack with PHP skeleton, config/logging, Tailwind assets, and wrappers
Implements MeshVault M1 (issues #6–11): - Four-service Docker Compose stack (nginx, PHP-FPM 8.3, PostgreSQL 17, Node assets) - Slim 4 + PHP-DI routing with escaped PHP views and production/dev error handling - Immutable validated AppConfig with redacted diagnostics - JSON stdout Monolog logging with job-scoped context - Tailwind CSS v4 deterministic locked builds with template scanning - POSIX and PowerShell dev wrappers (up/down/test/lint/assets/watch) - Startup config validation fails before FPM serves as unprivileged www-data - migrate/seed explicitly unavailable until M2 (#12); #10 remains open Verified via clean-start acceptance on remote host (keith@192.168.2.23).
This commit is contained in:
1 parent
c2747a5762
commit
d3e8cf8fae
39 files changed
+5291
-38
No files matched your search
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MeshVault\Tests\Integration;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class TaskRunnerTest extends TestCase
|
||||
{
|
||||
public function testCommandsFromAnotherWorkingDirectory(): void
|
||||
{
|
||||
$root = dirname(__DIR__, 2);
|
||||
$temporary = sys_get_temp_dir() . '/meshvault-runner-' . bin2hex(random_bytes(6));
|
||||
mkdir($temporary);
|
||||
$windows = PHP_OS_FAMILY === 'Windows';
|
||||
$fake = $temporary . ($windows ? '/docker.cmd' : '/docker');
|
||||
file_put_contents($fake, $windows
|
||||
? "@echo off\r\necho cwd=%CD%\r\necho %*\r\nexit /b 37\r\n"
|
||||
: "#!/bin/sh\nprintf 'cwd=%s\\n' \"\$PWD\"\nprintf '%s\\n' \"\$@\"\nexit 37\n");
|
||||
chmod($fake, 0755);
|
||||
$environment = getenv();
|
||||
$environment[$windows ? 'Path' : 'PATH'] = $temporary . PATH_SEPARATOR . getenv('PATH');
|
||||
$runner = $windows
|
||||
? ['powershell.exe', '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $root . '/dev.ps1']
|
||||
: ['sh', $root . '/dev.sh'];
|
||||
try {
|
||||
foreach (['up', 'down', 'test', 'lint', 'assets', 'watch'] as $command) {
|
||||
[$code, $output] = $this->invoke(
|
||||
[...$runner, $command, '--probe', 'two words'],
|
||||
$temporary,
|
||||
$environment
|
||||
);
|
||||
self::assertSame(37, $code, $command . ': ' . $output);
|
||||
self::assertStringContainsString('compose', $output);
|
||||
self::assertStringContainsString('--probe', $output);
|
||||
self::assertStringContainsString('two words', $output);
|
||||
self::assertStringContainsString(str_replace('\\', '/', $root), str_replace('\\', '/', $output));
|
||||
}
|
||||
foreach (['unknown', 'migrate', 'seed'] as $command) {
|
||||
[$code, $output] = $this->invoke([...$runner, $command], $temporary, $environment);
|
||||
self::assertNotSame(0, $code);
|
||||
self::assertStringNotContainsString('cwd=', $output);
|
||||
}
|
||||
foreach (['-v', '--volumes', '--volumes=true'] as $flag) {
|
||||
[$code, $output] = $this->invoke([...$runner, 'down', $flag], $temporary, $environment);
|
||||
self::assertNotSame(0, $code);
|
||||
self::assertStringNotContainsString('cwd=', $output);
|
||||
}
|
||||
} finally {
|
||||
unlink($fake);
|
||||
rmdir($temporary);
|
||||
}
|
||||
}
|
||||
|
||||
private function invoke(array $command, string $cwd, array $environment): array
|
||||
{
|
||||
$process = proc_open($command, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes, $cwd, $environment);
|
||||
self::assertIsResource($process);
|
||||
$output = stream_get_contents($pipes[1]) . stream_get_contents($pipes[2]);
|
||||
fclose($pipes[1]);
|
||||
fclose($pipes[2]);
|
||||
return [proc_close($process), $output];
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user