diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
new file mode 100644
index 0000000..970c0ec
--- /dev/null
+++ b/.github/workflows/cd.yml
@@ -0,0 +1,17 @@
+name: Laravel.php Supervisor
+
+on: push
+
+jobs:
+ cd:
+ runs-on: ubuntu-latest
+ steps:
+ - name: cd
+ uses: tripteki/cd-package@1.0.0
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ repotoken: ${{ secrets.REPOSITORY_TOKEN }}
+ repouser: tripteki
+ repository: https://packagist.org
+ language: php
+ artifact: composer.json
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..061b3a5
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Trip Teknologi
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..08cd23e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,118 @@
+
Supervisor
+
+This package provides *supervisor-like*, *async-like*, *child-process-like*, etc to build event-listener driven with little painless for your Laravel Project.
+
+Getting Started
+---
+
+Installation :
+
+```
+$ composer require tripteki/laravelphp-supervisor
+```
+
+```
+$ npm install pm2 && npm install --save-dev chokidar
+```
+
+```
+$ pecl install swoole
+```
+
+How to use :
+
+- Publish config file into your project's directory with running :
+
+```
+php artisan vendor:publish --tag=tripteki-laravelphp-supervisor
+```
+
+Usage
+---
+
+`php artisan supervisor:`
+
+Option
+---
+
+- `start ...` : Start the supervisor.
+ - foreground *(default)*
+ - background
+- `reload` : Reload the background supervisor.
+- `stop` : Stop the background supervisor / `ctrl + c` for foreground supervisor.
+- `status` : Show the status of background supervisor.
+- `startup` : Generate `ecosystem.json` supervisor startup configuration, do not forget to stop your supervisor process perproject, then see [this](https://pm2.keymetrics.io/docs/usage/startup) to know how to get started.
+
+Snippet
+---
+
+```php
+/** Use asynchronous? */
+
+__async__(function () {
+
+ Model::truncate();
+});
+```
+
+```php
+/** Use asynchronous await-like to get variable? */
+
+[ $model, ] = __async__(fn () => Model::all());
+```
+
+```php
+/** Use setInterval? */
+
+__setInterval__(function () {
+
+ Model::truncate();
+
+}, 2000);
+```
+
+```php
+/** Use setImmediate? */
+
+__setImmediate__(function () {
+
+ Model::truncate();
+
+}, 2000);
+```
+
+```php
+/** Use exec as replace temporary process? */
+
+$os = __exec__("uname -a");
+```
+
+```php
+/** Use spawn as one way communication child process? */
+
+/** Stdin stream handler... */
+$stdin = fopen("php://temporary", "w+");
+fwrite($stdin, "Foo...");
+fwrite($stdin, "Bar...");
+fwrite($stdin, "Baz...");
+fclose($stdin);
+
+/** Stdout handler... */
+$stdout = function ($isError, $data)
+{
+ if ($isError) {
+
+ // $isError //
+ }
+
+ // $data //
+};
+
+__spawn__("python3 example.py", $environment = [], $stdout, $stdin);
+```
+
+Author
+---
+
+- Trip Teknologi ([@tripteki](https://linkedin.com/company/tripteki))
+- Hasby Maulana ([@hsbmaulana](https://linkedin.com/in/hsbmaulana))
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..a5264f1
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,59 @@
+{
+ "name": "tripteki/laravelphp-supervisor",
+ "version": "1.0.0",
+ "description": "Trip Teknologi's Laravel.php Supervisor",
+
+ "readme": "README.md",
+ "license": "MIT",
+ "authors": [ { "name": "Trip Teknologi", "email": "tripteki.company@gmail.com" } ],
+ "homepage": "https://github.com/tripteki/laravelphp-supervisor",
+ "support": { "issues": "https://github.com/tripteki/laravelphp-supervisor/issues" },
+
+ "require": {
+
+ "php": "^8.0.2",
+ "ext-swoole": "*",
+
+ "tripteki/laravelphp-notification": "^1.0.0",
+ "guzzlehttp/guzzle": "^7.5.0",
+ "laravel/octane": "^1.3.9",
+ "beyondcode/laravel-websockets": "^2.0.0-beta.37",
+ "pusher/pusher-php-server": "^7.2.1"
+ },
+
+ "require-dev": {},
+
+ "autoload": {
+
+ "files": [
+
+ "src/Helpers/Async.php",
+ "src/Helpers/setImmediate.php",
+ "src/Helpers/setInterval.php",
+ "src/Helpers/Exec.php",
+ "src/Helpers/Spawn.php"
+ ],
+
+ "psr-4": {
+
+ "Tripteki\\Supervisor\\": "src/"
+ }
+ },
+
+ "autoload-dev": {},
+
+ "extra": {
+
+ "laravel": {
+
+ "dont-discover": [],
+
+ "providers": [
+
+ "Tripteki\\Supervisor\\Providers\\SupervisorServiceProvider"
+ ],
+
+ "aliases": []
+ }
+ }
+}
diff --git a/config/server.php b/config/server.php
new file mode 100644
index 0000000..68f8fb2
--- /dev/null
+++ b/config/server.php
@@ -0,0 +1,157 @@
+ env("SERVER_HOST", "127.0.0.1"),
+ "port" => env("SERVER_PORT", "8000"),
+ "https" => env("SERVER_HTTPS", false),
+
+ "state_file" => storage_path("logs/server-state.json"),
+
+ "swoole" => [
+
+ "options" => [
+
+ "pid_file" => storage_path("logs/server/server.pid"),
+ "log_file" => storage_path("logs/server.log"),
+
+ // "ssl_key_file" => ".key", //
+ // "ssl_cert_file" => ".cert", //
+ ],
+
+ "ssl" => env("SERVER_HTTPS", false),
+ ],
+
+
+
+ "cache" => [
+
+ "rows" => 1000,
+ "bytes" => 10000,
+ ],
+
+ "tables" => [
+
+ //
+ ],
+
+ "watch" => [
+
+ "bin",
+ "src",
+ "app",
+ "bootstrap",
+ "config",
+ "database",
+ "public/**/*.php",
+ "resources/**/*.php",
+ "routes",
+ "composer.lock",
+ ".env",
+ ],
+
+
+
+ "listeners" => [
+
+ WorkerStarting::class => [
+
+ EnsureUploadedFilesAreValid::class,
+ EnsureUploadedFilesCanBeMoved::class,
+ ],
+
+ RequestReceived::class => [
+
+ ...Octane::prepareApplicationForNextOperation(),
+ ...Octane::prepareApplicationForNextRequest(),
+ ],
+
+ RequestHandled::class => [
+
+ //
+ ],
+
+ RequestTerminated::class => [
+
+ // FlushUploadedFiles::class, //
+ ],
+
+ TaskReceived::class => [
+
+ ...Octane::prepareApplicationForNextOperation(),
+ ],
+
+ TaskTerminated::class => [
+
+ //
+ ],
+
+ TickReceived::class => [
+
+ ...Octane::prepareApplicationForNextOperation(),
+ ],
+
+ TickTerminated::class => [
+
+ //
+ ],
+
+ OperationTerminated::class => [
+
+ FlushTemporaryContainerInstances::class,
+ // DisconnectFromDatabases::class, //
+ // CollectGarbage::class, //
+ ],
+
+ WorkerErrorOccurred::class => [
+
+ ReportException::class,
+ StopWorkerIfNecessary::class,
+ ],
+
+ WorkerStopping::class => [
+
+ //
+ ],
+ ],
+
+
+
+ "warm" => [
+
+ ...Octane::defaultServicesToWarm(),
+ ],
+
+ "flush" => [
+
+ //
+ ],
+
+
+
+ "garbage" => 50,
+ "max_execution_time" => 30,
+ "server" => "swoole",
+
+];
diff --git a/config/supervisor.php b/config/supervisor.php
new file mode 100644
index 0000000..745a65b
--- /dev/null
+++ b/config/supervisor.php
@@ -0,0 +1,100 @@
+ storage_path("logs/supervisor.json"),
+
+ "server" => [
+
+ "command:development" => [
+
+ "octane:start",
+ "--host=".env("SERVER_HOST", "0.0.0.0"),
+ "--watch",
+ "--workers=auto",
+ "--task-workers=auto",
+ "--no-interaction",
+ "--no-ansi",
+ "--verbose",
+ ],
+
+ "command:production" => [
+
+ "octane:start",
+ "--host=".env("SERVER_HOST", "0.0.0.0"),
+ "--workers=auto",
+ "--task-workers=auto",
+ "--no-interaction",
+ "--no-ansi",
+ "--verbose",
+ ],
+
+ "process" => 1,
+ "increment" => [ "SERVER_PORT" => env("SERVER_PORT", "8000"), ],
+ "stdout" => storage_path("logs/supervisor-server-stdout.log"),
+ "stderr" => storage_path("logs/supervisor-server-stderr.log"),
+ ],
+
+ "websockets" => [
+
+ "command" => [
+
+ "websockets:serve",
+ "--host=".env("WEBSOCKET_HOST", "127.0.0.1"),
+ "--port=".env("WEBSOCKET_PORT", "6001"),
+ "--no-interaction",
+ "--no-ansi",
+ "--verbose",
+ ],
+
+ "process" => 1,
+ "stdout" => storage_path("logs/supervisor-websockets-stdout.log"),
+ "stderr" => storage_path("logs/supervisor-websockets-stderr.log"),
+ ],
+
+ "ssr" => [
+
+ "interpreter" => "node",
+
+ "command" => [
+
+ base_path("bootstrap/ssr/ssr.mjs"),
+ ],
+
+ "process" => 1,
+ "stdout" => storage_path("logs/supervisor-ssr-stdout.log"),
+ "stderr" => storage_path("logs/supervisor-ssr-stderr.log"),
+ ],
+
+ "schedule" => [
+
+ "command" => [
+
+ "schedule:work",
+ "--no-interaction",
+ "--no-ansi",
+ "--verbose",
+ ],
+
+ "process" => 2,
+ "stdout" => storage_path("logs/supervisor-schedule-stdout.log"),
+ "stderr" => storage_path("logs/supervisor-schedule-stderr.log"),
+ ],
+
+ "queue" => [
+
+ "command" => [
+
+ "queue:work",
+ "--queue=high,low",
+ "--no-interaction",
+ "--no-ansi",
+ "--verbose",
+ ],
+
+ "process" => 2,
+ "stdout" => storage_path("logs/supervisor-queue-stdout.log"),
+ "stderr" => storage_path("logs/supervisor-queue-stderr.log"),
+ ],
+
+];
diff --git a/config/websockets.php b/config/websockets.php
new file mode 100644
index 0000000..e46cc85
--- /dev/null
+++ b/config/websockets.php
@@ -0,0 +1,102 @@
+ [
+
+ [
+ "name" => env("APP_NAME"),
+ "id" => env("WEBSOCKET_ID"),
+ "host" => env("WEBSOCKET_HOST"),
+ "key" => env("WEBSOCKET_KEY"),
+ "secret" => env("WEBSOCKET_SECRET"),
+ "path" => env("WEBSOCKET_PATH"),
+ "allowed_origins" => [],
+ "capacity" => null,
+ "enable_client_messages" => false,
+ "enable_statistics" => true,
+ ],
+ ],
+
+ "ssl" => [
+
+ "local_pk" => env("WEBSOCKET_SSL_LOCAL_PK", null),
+ "local_cert" => env("WEBSOCKET_SSL_LOCAL_CERT", null),
+ "capath" => env("WEBSOCKET_SSL_CA", null),
+ "passphrase" => env("WEBSOCKET_SSL_PASSPHRASE", null),
+ "verify_peer" => env("APP_ENV") === "production",
+ "allow_self_signed" => env("APP_ENV") !== "production",
+ ],
+
+ "dashboard" => [
+
+ "domain" => env("WEBSOCKET_DASHBOARD_DOMAIN"),
+ "path" => env("WEBSOCKET_DASHBOARD_PATH", "websocket"),
+ "port" => env("SERVER_PORT", "6001"),
+
+ "middleware" => [
+
+ "web",
+ \BeyondCode\LaravelWebSockets\Dashboard\Http\Middleware\Authorize::class,
+ ],
+ ],
+
+ "managers" => [
+
+ "app" => \BeyondCode\LaravelWebSockets\Apps\ConfigAppManager::class,
+
+ "sqlite" => [
+
+ "database" => env("DB_DATABASE", database_path("websocket.sqlite")),
+ ],
+
+ "mysql" => [
+
+ "connection" => env("DB_CONNECTION", "mysql"),
+ "table" => "websocket_apps",
+ ],
+ ],
+
+ "statistics" => [
+
+ "store" => \BeyondCode\LaravelWebSockets\Statistics\Stores\DatabaseStore::class,
+ "interval_in_seconds" => 60,
+ "delete_statistics_older_than_days" => 60,
+ ],
+
+ "replication" => [
+
+ "mode" => env("WEBSOCKET_REPLICATION_MODE", "local"),
+
+ "modes" => [
+
+ "local" => [
+
+ "channel_manager" => \BeyondCode\LaravelWebSockets\ChannelManagers\LocalChannelManager::class,
+ "collector" => \BeyondCode\LaravelWebSockets\Statistics\Collectors\MemoryCollector::class,
+ ],
+
+ "redis" => [
+
+ "connection" => env("WEBSOCKET_REDIS_REPLICATION_CONNECTION", "broadcasting"),
+ "channel_manager" => \BeyondCode\LaravelWebSockets\ChannelManagers\RedisChannelManager::class,
+ "collector" => \BeyondCode\LaravelWebSockets\Statistics\Collectors\RedisCollector::class,
+ ],
+ ],
+ ],
+
+ "handlers" => [
+
+ "websocket" => \BeyondCode\LaravelWebSockets\Server\WebSocketHandler::class,
+ "health" => \BeyondCode\LaravelWebSockets\Server\HealthHandler::class,
+ "trigger_event" => \BeyondCode\LaravelWebSockets\API\TriggerEvent::class,
+ "fetch_users" => \BeyondCode\LaravelWebSockets\API\FetchUsers::class,
+ "fetch_channel" => \BeyondCode\LaravelWebSockets\API\FetchChannel::class,
+ "fetch_channels" => \BeyondCode\LaravelWebSockets\API\FetchChannels::class,
+ ],
+
+ "max_request_size_in_kb" => 250,
+
+ "promise_resolver" => \React\Promise\FulfilledPromise::class,
+
+];
diff --git a/src/Concerns/LiveTrait.php b/src/Concerns/LiveTrait.php
new file mode 100644
index 0000000..0077c99
--- /dev/null
+++ b/src/Concerns/LiveTrait.php
@@ -0,0 +1,14 @@
+environment("production");
+ }
+};
diff --git a/src/Console/Commands/Supervisor.php b/src/Console/Commands/Supervisor.php
new file mode 100644
index 0000000..c068dda
--- /dev/null
+++ b/src/Console/Commands/Supervisor.php
@@ -0,0 +1,107 @@
+supervisor = $supervisor;
+ $this->supervisor->setConfiguration(Arr::except(config("supervisor") ?? [], "state_file"));
+ $this->supervisor->setPath(config("supervisor.state_file") ?? storage_path("logs/supervisor.json"));
+ }
+
+ /**
+ * @param string $command
+ * @param string $mode
+ * @return \Symfony\Component\Process\Process|void
+ */
+ public function exec($command, $mode)
+ {
+ $requirements = json_decode(__exec__("npm list pm2 chokidar --depth=0 --json"), true);
+
+ if (! Arr::exists($requirements, "dependencies") ||
+ ! Arr::exists($requirements["dependencies"], "pm2") ||
+ ! Arr::exists($requirements["dependencies"], "chokidar")) {
+
+ $this->error("You need to install (pm2) and (chokidar) locally first and not as globally!");
+
+ return;
+ }
+
+ $supervisor = "npx"." ".$this->{$mode}()." ".$command." ".$this->supervisor->getPath();
+ $path = [ "PM2_HOME" => $this->supervisor->supervisorPath(), ];
+
+ if ($mode == "foreground") {
+
+ $supervisor = __spawn__($supervisor, $path, function ($isError, $data) {
+
+ if ($isError) {
+
+ file_put_contents("php://stderr", $data, FILE_APPEND);
+
+ } else {
+
+ file_put_contents("php://stdout", $data, FILE_APPEND);
+ }
+ });
+
+ } else if ($mode == "background") {
+
+ $supervisor = __spawn__($supervisor, $path);
+
+ if ($command == "start") {
+
+ $this->info("Supervisor started!");
+
+ } else if ($command == "reload") {
+
+ $this->info("Supervisor reloaded!");
+
+ } else if ($command == "stop") {
+
+ $this->info("Supervisor stopped!");
+
+ } else if ($command == "status") {
+
+ $this->newLine();
+ $this->table([ "Process Name", "Process Id", ], $this->supervisor->list());
+ $this->newLine();
+ }
+ }
+
+ return $supervisor;
+ }
+
+ /**
+ * @return string
+ */
+ protected function foreground()
+ {
+ return "pm2-runtime";
+ }
+
+ /**
+ * @return string
+ */
+ protected function background()
+ {
+ return "pm2";
+ }
+};
diff --git a/src/Console/Commands/SupervisorReloadCommand.php b/src/Console/Commands/SupervisorReloadCommand.php
new file mode 100644
index 0000000..9974778
--- /dev/null
+++ b/src/Console/Commands/SupervisorReloadCommand.php
@@ -0,0 +1,27 @@
+supervisor->write()) {
+
+ $this->exec("reload", "background");
+ }
+ }
+};
diff --git a/src/Console/Commands/SupervisorStartCommand.php b/src/Console/Commands/SupervisorStartCommand.php
new file mode 100644
index 0000000..18b20c1
--- /dev/null
+++ b/src/Console/Commands/SupervisorStartCommand.php
@@ -0,0 +1,75 @@
+argument("mode");
+
+ switch ($mode) {
+
+ case "foreground":
+ $this->createForegroundProcess();
+ break;
+
+ case "background":
+ $this->createBackgroundProcess();
+ break;
+
+ default:
+ $this->createForegroundProcess();
+ }
+ }
+
+ /**
+ * @return void
+ */
+ public function createForegroundProcess()
+ {
+ $supervisor = null;
+
+ $this->trap([ SIGINT, SIGTERM, SIGQUIT, ], function () use (&$supervisor) {
+
+ if ($supervisor) {
+
+ $supervisor->stop(3, SIGINT);
+ $supervisor->stop(15, SIGTERM);
+ }
+
+ $this->supervisor->delete();
+ });
+
+ if ($this->supervisor->write()) {
+
+ $supervisor = $this->exec("start", "foreground");
+ }
+ }
+
+ /**
+ * @return void
+ */
+ public function createBackgroundProcess()
+ {
+ if ($this->supervisor->write()) {
+
+ $this->exec("start", "background");
+ }
+ }
+};
diff --git a/src/Console/Commands/SupervisorStartupCommand.php b/src/Console/Commands/SupervisorStartupCommand.php
new file mode 100644
index 0000000..2d6666d
--- /dev/null
+++ b/src/Console/Commands/SupervisorStartupCommand.php
@@ -0,0 +1,49 @@
+supervisor = $supervisor;
+ $this->supervisor->setConfiguration(Arr::except(config("supervisor") ?? [], "state_file"));
+ $this->supervisor->setPath(base_path("ecosystem.json"));
+ }
+
+ /**
+ * @return int
+ */
+ public function handle()
+ {
+ if ($this->supervisor->write()) {
+
+ $this->info("Supervisor startup configuration generated.");
+ }
+ }
+};
diff --git a/src/Console/Commands/SupervisorStatusCommand.php b/src/Console/Commands/SupervisorStatusCommand.php
new file mode 100644
index 0000000..3936f4e
--- /dev/null
+++ b/src/Console/Commands/SupervisorStatusCommand.php
@@ -0,0 +1,24 @@
+exec("status", "background");
+ }
+};
diff --git a/src/Console/Commands/SupervisorStopCommand.php b/src/Console/Commands/SupervisorStopCommand.php
new file mode 100644
index 0000000..6cbde90
--- /dev/null
+++ b/src/Console/Commands/SupervisorStopCommand.php
@@ -0,0 +1,29 @@
+supervisor->write()) {
+
+ $this->exec("stop", "background");
+
+ $this->supervisor->delete();
+ }
+ }
+};
diff --git a/src/Helpers/Async.php b/src/Helpers/Async.php
new file mode 100644
index 0000000..37a4792
--- /dev/null
+++ b/src/Helpers/Async.php
@@ -0,0 +1,18 @@
+explode(" ")->toArray();
+
+ $executor = (new ExecutableFinder)->find(Arr::first($commands)) ?: throw new \Exception("The executor cannot be found.");
+ $executeble = Arr::prepend(Arr::except($commands, 0), $executor);
+
+ $process = shell_exec(implode(" ", $executeble));
+
+ return $process;
+ };
+}
diff --git a/src/Helpers/Spawn.php b/src/Helpers/Spawn.php
new file mode 100644
index 0000000..9dde0e6
--- /dev/null
+++ b/src/Helpers/Spawn.php
@@ -0,0 +1,58 @@
+explode(" ")->toArray();
+
+ $executor = (new ExecutableFinder)->find(Arr::first($commands)) ?: throw new \Exception("The executor cannot be found.");
+ $executeble = Arr::prepend(Arr::except($commands, 0), $executor);
+
+ $subprocess = new Process($executeble, null, $environments);
+ $subprocess->setTimeout(null);
+ $subprocess->setIdleTimeout(null);
+
+ if ($stdin) {
+
+ $subprocess->setInput($stdin);
+ }
+
+ // $subprocess->run(); // // "Sync" //
+ $subprocess->start(); // "ASync" //
+
+ if ($fnstdout instanceof \Closure) {
+
+ $subprocess->wait(function ($type, $data) use ($fnstdout) {
+
+ $isError = false;
+
+ if ($type === Process::ERR) {
+
+ $isError = true;
+ }
+
+ $fnstdout($isError, $data);
+ });
+
+ } else {
+
+ $subprocess->wait();
+ }
+
+ return $subprocess;
+ };
+}
diff --git a/src/Helpers/setImmediate.php b/src/Helpers/setImmediate.php
new file mode 100644
index 0000000..c6fd0fb
--- /dev/null
+++ b/src/Helpers/setImmediate.php
@@ -0,0 +1,17 @@
+seconds($delay / 1000)->immediate();
+ };
+}
diff --git a/src/Helpers/setInterval.php b/src/Helpers/setInterval.php
new file mode 100644
index 0000000..b867b3b
--- /dev/null
+++ b/src/Helpers/setInterval.php
@@ -0,0 +1,17 @@
+seconds($delay / 1000);
+ };
+}
diff --git a/src/Providers/SupervisorServiceProvider.php b/src/Providers/SupervisorServiceProvider.php
new file mode 100644
index 0000000..e7b3f8b
--- /dev/null
+++ b/src/Providers/SupervisorServiceProvider.php
@@ -0,0 +1,106 @@
+registerPublishers();
+ $this->registerCommands();
+ $this->registerMigrations();
+ }
+
+ /**
+ * @return void
+ */
+ protected function registerConfigs()
+ {
+ $this->mergeConfigFrom(__DIR__."/../../config/supervisor.php", "supervisor");
+ }
+
+ /**
+ * @return void
+ */
+ protected function registerMigrations()
+ {
+ if ($this->app->runningInConsole() && static::shouldRunMigrations()) {
+
+ $this->loadMigrationsFrom(__DIR__."/../../../../beyondcode/laravel-websockets/database/migrations");
+ }
+ }
+
+ /**
+ * @return void
+ */
+ protected function registerCommands()
+ {
+ if ($this->app->runningInConsole()) {
+
+ $this->commands(
+ [
+ SupervisorStartCommand::class,
+ SupervisorReloadCommand::class,
+ SupervisorStopCommand::class,
+ SupervisorStatusCommand::class,
+ SupervisorStartupCommand::class,
+ ]);
+ }
+ }
+
+ /**
+ * @return void
+ */
+ protected function registerPublishers()
+ {
+ $this->publishes(
+ [
+ __DIR__."/../../config/supervisor.php" => config_path("supervisor.php"),
+ __DIR__."/../../config/server.php" => config_path("octane.php"),
+ __DIR__."/../../config/websockets.php" => config_path("websockets.php"),
+ ],
+
+ "tripteki-laravelphp-supervisor");
+
+ if (! static::shouldRunMigrations()) {
+
+ $this->publishes(
+ [
+ __DIR__."/../../../../beyondcode/laravel-websockets/database/migrations" => database_path("migrations"),
+ ],
+
+ "tripteki-laravelphp-supervisor-migrations");
+ }
+ }
+};
diff --git a/src/Supervisor/StateFile.php b/src/Supervisor/StateFile.php
new file mode 100644
index 0000000..bc57116
--- /dev/null
+++ b/src/Supervisor/StateFile.php
@@ -0,0 +1,184 @@
+path = $path;
+ }
+
+ /**
+ * @param array $configuration
+ * @return void
+ */
+ public function setConfiguration($configuration)
+ {
+ $this->configuration = $configuration;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * @return array
+ */
+ public function getConfiguration()
+ {
+ return $this->configuration;
+ }
+
+ /**
+ * @return string
+ */
+ public function supervisorPath()
+ {
+ return storage_path("logs/supervisor");
+ }
+
+ /**
+ * @return bool
+ */
+ public function delete()
+ {
+ $isDeleted = false;
+
+ Arr::map(Arr::pluck($this->read()["apps"], "script"), function ($script) {
+
+ __exec__("pkill -f \"{$script}\"");
+ });
+
+ if (File::exists($this->getPath())) {
+
+ File::deleteDirectory($this->supervisorPath(), true);
+ File::delete($this->getPath());
+
+ $isDeleted = true;
+ }
+
+ return $isDeleted;
+ }
+
+ /**
+ * @return int
+ */
+ public function write()
+ {
+ return File::put($this->getPath(), json_encode($this->read(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
+ }
+
+ /**
+ * @return array
+ */
+ public function read()
+ {
+ $supervisor = [];
+ $configurations = $this->getConfiguration();
+
+ foreach ($configurations as $processname => $configuration) {
+
+ $isLive = $this->isLive();
+
+ $interpreter = $configuration["interpreter"] ?? ($executeable = (new PhpExecutableFinder)->find());
+ $commandDevelopment = $configuration["command:development"] ?? null;
+ $commandProduction = $configuration["command:production"] ?? null;
+ $command = $configuration["command"] ?? ($isLive ? $commandProduction : $commandDevelopment);
+ $process = $configuration["process"] ?? 1;
+ $delay = $configuration["delay"] ?? 0;
+ $stdout = $configuration["stdout"] ?? "/dev/stdout";
+ $stderr = $configuration["stderr"] ?? "/dev/stderr";
+ $increment = $configuration["increment"] ?? [];
+
+ $context = [
+
+ "name" => $processname,
+ "instances" => $process != "auto" ? $process : "max",
+ "exec_mode" => "fork", // "cluster" //
+ "script" => $command,
+ "out_file" => $stdout,
+ "error_file" => $stderr,
+ "restart_delay" => $delay,
+ "cwd" => base_path(),
+ "env" => [ "NODE_ENV" => $isLive ? "production" : "development", ],
+ ];
+
+ $artisan = defined("ARTISAN_BINARY") ? ARTISAN_BINARY : "artisan";
+
+ if (Arr::isAssoc($increment)) {
+
+ $key = array_key_first($increment);
+ $value = $increment[$key];
+
+ $context["env"][$key] = $value;
+ $context["increment_var"] = $key;
+ }
+
+ if ($interpreter == $executeable && Arr::first($context["script"]) != $artisan) {
+
+ $context["script"] = Arr::prepend($context["script"], $artisan);
+ }
+
+ $context["script"] = $interpreter." ".implode(" ", $context["script"]);
+
+ $supervisor[] = $context;
+ }
+
+ return [ "apps" => $supervisor, ];
+ }
+
+ /**
+ * @return array
+ */
+ public function list()
+ {
+ $processes = [];
+
+ if (File::exists($pm2pid = $this->supervisorPath()."/pm2.pid") && File::exists($pids = $this->supervisorPath()."/pids")) {
+
+ $processes[] = [
+
+ "name" => "manager",
+ "id" => File::get($pm2pid),
+ ];
+
+ Arr::map(File::files($pids), function ($file) use (&$processes, $pids) {
+
+ $processes[] = [
+
+ "name" => Str::of($file->getFileName())->replaceMatches("/-[0-9]+\.pid$/", ""),
+ "id" => File::get($pids."/".$file->getFileName()),
+ ];
+ });
+ }
+
+ return $processes;
+ }
+};