diff --git a/phpstan.neon.dist b/phpstan.neon.dist index a91953b..260b5e1 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -10,5 +10,4 @@ parameters: tmpDir: build/phpstan checkOctaneCompatibility: true checkModelProperties: true - checkMissingIterableValueType: false diff --git a/src/ChildProcess.php b/src/ChildProcess.php new file mode 100644 index 0000000..ff80a07 --- /dev/null +++ b/src/ChildProcess.php @@ -0,0 +1,129 @@ +alias; + + $process = $this->client->get("child-process/get/{$alias}")->json(); + + if (! $process) { + return null; + } + + return $this->fromRuntimeProcess($process); + } + + public function all(): array + { + $processes = $this->client->get('child-process/')->json(); + + if (empty($processes)) { + return []; + } + + $hydrated = []; + + foreach ($processes as $alias => $process) { + $hydrated[$alias] = (new static($this->client)) + ->fromRuntimeProcess($process); + } + + return $hydrated; + } + + public function start( + string|array $cmd, + string $alias, + ?string $cwd = null, + ?array $env = null, + bool $persistent = false + ): static { + + $process = $this->client->post('child-process/start', [ + 'alias' => $alias, + 'cmd' => (array) $cmd, + 'cwd' => $cwd ?? base_path(), + 'env' => $env, + 'persistent' => $persistent, + ])->json(); + + return $this->fromRuntimeProcess($process); + } + + public function php(string|array $cmd, string $alias, ?array $env = null, ?bool $persistent = false): self + { + $cmd = [PHP_BINARY, ...(array) $cmd]; + + return $this->start($cmd, $alias, env: $env, persistent: $persistent); + } + + public function artisan(string|array $cmd, string $alias, ?array $env = null, ?bool $persistent = false): self + { + $cmd = ['artisan', ...(array) $cmd]; + + return $this->php($cmd, $alias, env: $env, persistent: $persistent); + } + + public function stop(?string $alias = null): void + { + $this->client->post('child-process/stop', [ + 'alias' => $alias ?? $this->alias, + ])->json(); + } + + public function restart(?string $alias = null): ?static + { + $process = $this->client->post('child-process/restart', [ + 'alias' => $alias ?? $this->alias, + ])->json(); + + if (! $process) { + return null; + } + + return $this->fromRuntimeProcess($process); + } + + public function message(string $message, ?string $alias = null): static + { + $this->client->post('child-process/message', [ + 'alias' => $alias ?? $this->alias, + 'message' => $message, + ])->json(); + + return $this; + } + + protected function fromRuntimeProcess($process): static + { + if (isset($process['pid'])) { + $this->pid = $process['pid']; + } + + foreach ($process['settings'] as $key => $value) { + $this->{$key} = $value; + } + + return $this; + } +} diff --git a/src/Commands/MigrateCommand.php b/src/Commands/MigrateCommand.php index ebf8522..9093d67 100644 --- a/src/Commands/MigrateCommand.php +++ b/src/Commands/MigrateCommand.php @@ -22,6 +22,6 @@ public function handle() { (new NativeServiceProvider($this->laravel))->rewriteDatabase(); - parent::handle(); + return parent::handle(); } } diff --git a/src/Events/ChildProcess/ErrorReceived.php b/src/Events/ChildProcess/ErrorReceived.php new file mode 100644 index 0000000..65db9c6 --- /dev/null +++ b/src/Events/ChildProcess/ErrorReceived.php @@ -0,0 +1,22 @@ +makePartial() + ->shouldAllowMockingProtectedMethods(); + + $this->instance(ChildProcessImplement::class, $mock->allows([ + 'fromRuntimeProcess' => $mock, + ])); +}); + +it('can start a child process', function () { + ChildProcess::start('foo bar', 'some-alias', 'path/to/dir', ['baz' => 'zah']); + + Http::assertSent(function (Request $request) { + return $request->url() === 'http://localhost:4000/api/child-process/start' && + $request['alias'] === 'some-alias' && + $request['cmd'] === ['foo bar'] && + $request['cwd'] === 'path/to/dir' && + $request['env'] === ['baz' => 'zah']; + }); +}); + +it('can start a php command', function () { + ChildProcess::php("-r 'sleep(5);'", 'some-alias', ['baz' => 'zah']); + + Http::assertSent(function (Request $request) { + return $request->url() === 'http://localhost:4000/api/child-process/start' && + $request['alias'] === 'some-alias' && + $request['cmd'] === [PHP_BINARY, "-r 'sleep(5);'"] && + $request['cwd'] === base_path() && + $request['env'] === ['baz' => 'zah']; + }); +}); + +it('can start a artisan command', function () { + ChildProcess::artisan('foo:bar --verbose', 'some-alias', ['baz' => 'zah']); + + Http::assertSent(function (Request $request) { + return $request->url() === 'http://localhost:4000/api/child-process/start' && + $request['alias'] === 'some-alias' && + $request['cmd'] === [PHP_BINARY, 'artisan', 'foo:bar --verbose'] && + $request['cwd'] === base_path() && + $request['env'] === ['baz' => 'zah']; + }); +}); + +it('accepts either a string or a array as start command argument', function () { + ChildProcess::start('foo bar', 'some-alias'); + Http::assertSent(fn (Request $request) => $request['cmd'] === ['foo bar']); + + ChildProcess::start(['foo', 'baz'], 'some-alias'); + Http::assertSent(fn (Request $request) => $request['cmd'] === ['foo', 'baz']); +}); + +it('accepts either a string or a array as php command argument', function () { + ChildProcess::php("-r 'sleep(5);'", 'some-alias'); + Http::assertSent(fn (Request $request) => $request['cmd'] === [PHP_BINARY, "-r 'sleep(5);'"]); + + ChildProcess::php(['-r', "'sleep(5);'"], 'some-alias'); + Http::assertSent(fn (Request $request) => $request['cmd'] === [PHP_BINARY, '-r', "'sleep(5);'"]); +}); + +it('accepts either a string or a array as artisan command argument', function () { + ChildProcess::artisan('foo:bar', 'some-alias'); + Http::assertSent(fn (Request $request) => $request['cmd'] === [PHP_BINARY, 'artisan', 'foo:bar']); + + ChildProcess::artisan(['foo:baz'], 'some-alias'); + Http::assertSent(fn (Request $request) => $request['cmd'] === [PHP_BINARY, 'artisan', 'foo:baz']); +}); + +it('sets the cwd to the base path if none was given', function () { + ChildProcess::start(['foo', 'bar'], 'some-alias', cwd: 'path/to/dir'); + Http::assertSent(fn (Request $request) => $request['cwd'] === 'path/to/dir'); + + ChildProcess::start(['foo', 'bar'], 'some-alias'); + Http::assertSent(fn (Request $request) => $request['cwd'] === base_path()); +}); + +it('can stop a child process', function () { + ChildProcess::stop('some-alias'); + + Http::assertSent(function (Request $request) { + return $request->url() === 'http://localhost:4000/api/child-process/stop' && + $request['alias'] === 'some-alias'; + }); +}); + +it('can send messages to a child process', function () { + ChildProcess::message('some-message', 'some-alias'); + + Http::assertSent(function (Request $request) { + return $request->url() === 'http://localhost:4000/api/child-process/message' && + $request['alias'] === 'some-alias' && + $request['message'] === 'some-message'; + }); +}); + +it('can mark a process as persistent', function () { + ChildProcess::start('foo bar', 'some-alias', persistent: true); + Http::assertSent(fn (Request $request) => $request['persistent'] === true); +}); + +it('can mark a php command as persistent', function () { + ChildProcess::php("-r 'sleep(5);'", 'some-alias', persistent: true); + Http::assertSent(fn (Request $request) => $request['persistent'] === true); +}); + +it('can mark a artisan command as persistent', function () { + ChildProcess::artisan('foo:bar', 'some-alias', persistent: true); + Http::assertSent(fn (Request $request) => $request['persistent'] === true); +}); + +it('marks the process as non-persistent by default', function () { + ChildProcess::start('foo bar', 'some-alias'); + Http::assertSent(fn (Request $request) => $request['persistent'] === false); +});