diff --git a/app/Cli/Commands/AbstractCommand.php b/app/Cli/Commands/AbstractCommand.php new file mode 100644 index 0000000..54c96af --- /dev/null +++ b/app/Cli/Commands/AbstractCommand.php @@ -0,0 +1,32 @@ +user) { + return $this->user->refresh(); + } + + /** @var User $user */ + $user = User::query()->first(); + + if (!$user) { + error('The application is not setup'); + exit(1); + } + + $this->user = $user; + + return $user; + } +} diff --git a/app/Cli/Commands/InfoCommand.php b/app/Cli/Commands/InfoCommand.php new file mode 100644 index 0000000..f199563 --- /dev/null +++ b/app/Cli/Commands/InfoCommand.php @@ -0,0 +1,18 @@ +info('Version: '.config('app.version')); + $this->info('Timezone: '.config('app.timezone')); + } +} diff --git a/app/Cli/Commands/Projects/ProjectsListCommand.php b/app/Cli/Commands/Projects/ProjectsListCommand.php new file mode 100644 index 0000000..1576f17 --- /dev/null +++ b/app/Cli/Commands/Projects/ProjectsListCommand.php @@ -0,0 +1,29 @@ +map(fn (Project $project) => [ + $project->id, + $project->name, + $project->id === $this->user()->current_project_id ? 'Yes' : 'No', + ])->toArray(), + ); + } +} diff --git a/app/Cli/Commands/Projects/ProjectsSelectCommand.php b/app/Cli/Commands/Projects/ProjectsSelectCommand.php new file mode 100644 index 0000000..3c896fd --- /dev/null +++ b/app/Cli/Commands/Projects/ProjectsSelectCommand.php @@ -0,0 +1,35 @@ +find($this->argument('project')); + + if (! $project) { + error('The project does not exist'); + + return; + } + + $this->user()->update([ + 'current_project_id' => $project->id, + ]); + + info(__('The project [:project] has been selected' , [ + 'project' => $project->name, + ])); + } +} diff --git a/app/Cli/Commands/ServerProviders/ServerProvidersCreateCommand.php b/app/Cli/Commands/ServerProviders/ServerProvidersCreateCommand.php new file mode 100644 index 0000000..e46e261 --- /dev/null +++ b/app/Cli/Commands/ServerProviders/ServerProvidersCreateCommand.php @@ -0,0 +1,28 @@ +filter(fn($provider) => $provider != \App\Enums\ServerProvider::CUSTOM) + ->mapWithKeys(fn($provider) => [$provider => $provider]), + ); + $profile = text( + label: 'What should we call this provider profile?', + required: true, + ); + } +} diff --git a/app/Cli/Commands/ServerProviders/ServerProvidersListCommand.php b/app/Cli/Commands/ServerProviders/ServerProvidersListCommand.php new file mode 100644 index 0000000..3490540 --- /dev/null +++ b/app/Cli/Commands/ServerProviders/ServerProvidersListCommand.php @@ -0,0 +1,32 @@ +user()->serverProviders; + + table( + headers: ['ID', 'Provider', 'Name', 'Created At'], + rows: $providers->map(fn (ServerProvider $provider) => [ + $provider->id, + $provider->provider, + $provider->profile, + $provider->created_at_by_timezone, + ])->toArray(), + ); + } +} diff --git a/app/Cli/Commands/Servers/ServersCreateCommand.php b/app/Cli/Commands/Servers/ServersCreateCommand.php new file mode 100644 index 0000000..e3c1489 --- /dev/null +++ b/app/Cli/Commands/Servers/ServersCreateCommand.php @@ -0,0 +1,27 @@ +mapWithKeys(fn($value) => [$value => $value]), + ); + } +} diff --git a/app/Cli/Commands/Servers/ServersListCommand.php b/app/Cli/Commands/Servers/ServersListCommand.php new file mode 100644 index 0000000..2c596c2 --- /dev/null +++ b/app/Cli/Commands/Servers/ServersListCommand.php @@ -0,0 +1,34 @@ +user()->currentProject->servers; + + table( + headers: ['ID', 'Name', 'IP', 'Provider', 'OS', 'Status', 'Created At'], + rows: $servers->map(fn (Server $server) => [ + $server->id, + $server->name, + $server->ip, + $server->provider, + $server->os, + $server->status, + $server->created_at_by_timezone, + ])->toArray(), + ); + } +} diff --git a/app/Cli/Commands/SetupCommand.php b/app/Cli/Commands/SetupCommand.php new file mode 100644 index 0000000..4d349ad --- /dev/null +++ b/app/Cli/Commands/SetupCommand.php @@ -0,0 +1,81 @@ +prepareStorage(); + + $this->migrate(); + + $this->makeUser(); + + $this->makeProject(); + + info('The application has been setup'); + } + + private function prepareStorage(): void + { + File::ensureDirectoryExists(storage_path()); + } + + private function migrate(): void + { + $this->call('migrate', ['--force' => true]); + } + + private function makeUser(): void + { + $user = User::query()->first(); + if ($user) { + return; + } + + $name = text( + label: 'What is your name?', + required: true, + ); + $email = text( + label: 'What is your email?', + required: true, + ); + + User::query()->create([ + 'name' => $name, + 'email' => $email, + 'password' => bcrypt(str()->random(16)), + ]); + } + + private function makeProject(): void + { + $project = Project::query()->first(); + + if ($project) { + return; + } + + $project = Project::query()->create([ + 'name' => 'default', + ]); + + $user = User::query()->first(); + $user->update([ + 'current_project_id' => $project->id, + ]); + } +} diff --git a/app/Cli/Kernel.php b/app/Cli/Kernel.php new file mode 100644 index 0000000..d85253e --- /dev/null +++ b/app/Cli/Kernel.php @@ -0,0 +1,43 @@ +load(__DIR__ . '/Commands'); + + $this->app->singleton('command.migrate', function ($app) { + return new MigrateCommand($app['migrator'], $app[Dispatcher::class]); + }); + $this->app->singleton('command.migrate.install', function ($app) { + return new InstallCommand($app['migration.repository']); + }); + } + + protected function shouldDiscoverCommands(): false + { + return false; + } + + protected function getArtisan(): ?Artisan + { + return $this->artisan = (new Artisan($this->app, $this->events, $this->app->version())) + ->resolveCommands($this->commands); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 6babec7..f469aef 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -18,6 +18,9 @@ class AppServiceProvider extends ServiceProvider */ public function register(): void { + if ($this->app->runningInConsole()) { + return; + } Fortify::ignoreRoutes(); } @@ -36,6 +39,8 @@ public function boot(): void return new FTP; }); - Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class); + if (! $this->app->runningInConsole()) { + Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class); + } } } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index fc2d894..d68d6c8 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -23,6 +23,9 @@ class RouteServiceProvider extends ServiceProvider */ public function boot(): void { + if ($this->app->runningInConsole()) { + return; + } $this->configureRateLimiting(); } diff --git a/app/Providers/WebServiceProvider.php b/app/Providers/WebServiceProvider.php index d661983..aaa4f05 100644 --- a/app/Providers/WebServiceProvider.php +++ b/app/Providers/WebServiceProvider.php @@ -38,11 +38,18 @@ class WebServiceProvider extends ServiceProvider */ public function register(): void { + if ($this->app->runningInConsole()) { + return; + } Filament::registerPanel($this->panel(Panel::make())); } public function boot(): void { + if ($this->app->runningInConsole()) { + return; + } + FilamentView::registerRenderHook( PanelsRenderHook::SIDEBAR_NAV_START, fn () => Livewire::mount(SelectProject::class) diff --git a/bootstrap/cli-cache/.gitignore b/bootstrap/cli-cache/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/bootstrap/cli-cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/bootstrap/cli.php b/bootstrap/cli.php new file mode 100644 index 0000000..5262383 --- /dev/null +++ b/bootstrap/cli.php @@ -0,0 +1,69 @@ +useStoragePath(getenv('HOME') . '/.vito/storage'); + +/* +|-------------------------------------------------------------------------- +| Bind Important Interfaces +|-------------------------------------------------------------------------- +| +| Next, we need to bind some important interfaces into the container so +| we will be able to resolve them when needed. The kernels serve the +| incoming requests to this application from both the web and CLI. +| +*/ + +// $app->singleton( +// Illuminate\Contracts\Http\Kernel::class, +// App\Http\Kernel::class +// ); + +$app->singleton( + Illuminate\Contracts\Console\Kernel::class, + App\Cli\Kernel::class +); + +$app->singleton( + Illuminate\Contracts\Debug\ExceptionHandler::class, + App\Exceptions\Handler::class +); + +/* +|-------------------------------------------------------------------------- +| Return The Application +|-------------------------------------------------------------------------- +| +| This script returns the application instance. The instance is given to +| the calling script so we can separate the building of the instances +| from the actual running of the application and sending responses. +| +*/ + +return $app; diff --git a/box.json b/box.json new file mode 100644 index 0000000..950c2e4 --- /dev/null +++ b/box.json @@ -0,0 +1,20 @@ +{ + "directories": [ + "app", + "bootstrap", + "config", + "database", + "public", + "resources", + "routes", + "storage", + "vendor" + ], + "files": [ + "artisan" + ], + "main": "cli", + "output": "vito-cli.phar", + "chmod": "0755", + "stub": true +} diff --git a/cli b/cli new file mode 100644 index 0000000..2887924 --- /dev/null +++ b/cli @@ -0,0 +1,53 @@ +#!/usr/bin/env php +make(Illuminate\Contracts\Console\Kernel::class); + +$status = $kernel->handle( + $input = new Symfony\Component\Console\Input\ArgvInput, + new Symfony\Component\Console\Output\ConsoleOutput +); + +/* +|-------------------------------------------------------------------------- +| Shutdown The Application +|-------------------------------------------------------------------------- +| +| Once Artisan has finished running, we will fire off the shutdown events +| so that any final work may be done by the application before we shut +| down the process. This is the last thing to happen to the request. +| +*/ + +$kernel->terminate($input, $status); + +exit($status); diff --git a/composer.json b/composer.json index 8648b33..579ecef 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ "filament/filament": "^3.2", "laravel/fortify": "^1.17", "laravel/framework": "^11.0", + "laravel/prompts": "^0.3.5", "laravel/sanctum": "^4.0", "laravel/tinker": "^2.8", "mobiledetect/mobiledetectlib": "^4.8", diff --git a/composer.lock b/composer.lock index cb6c93c..1d05220 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "be3e63b7efd71f649cbffb0d469ba7c1", + "content-hash": "e211da7974e07c3b74ad59ce245a9446", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -2559,16 +2559,16 @@ }, { "name": "laravel/prompts", - "version": "v0.3.3", + "version": "v0.3.5", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "749395fcd5f8f7530fe1f00dfa84eb22c83d94ea" + "reference": "57b8f7efe40333cdb925700891c7d7465325d3b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/749395fcd5f8f7530fe1f00dfa84eb22c83d94ea", - "reference": "749395fcd5f8f7530fe1f00dfa84eb22c83d94ea", + "url": "https://api.github.com/repos/laravel/prompts/zipball/57b8f7efe40333cdb925700891c7d7465325d3b1", + "reference": "57b8f7efe40333cdb925700891c7d7465325d3b1", "shasum": "" }, "require": { @@ -2582,7 +2582,7 @@ "laravel/framework": ">=10.17.0 <10.25.0" }, "require-dev": { - "illuminate/collections": "^10.0|^11.0", + "illuminate/collections": "^10.0|^11.0|^12.0", "mockery/mockery": "^1.5", "pestphp/pest": "^2.3|^3.4", "phpstan/phpstan": "^1.11", @@ -2612,9 +2612,9 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.3" + "source": "https://github.com/laravel/prompts/tree/v0.3.5" }, - "time": "2024-12-30T15:53:31+00:00" + "time": "2025-02-11T13:34:40+00:00" }, { "name": "laravel/sanctum", diff --git a/config/cli.php b/config/cli.php new file mode 100644 index 0000000..c1aa12f --- /dev/null +++ b/config/cli.php @@ -0,0 +1,22 @@ + [ + Illuminate\Bus\BusServiceProvider::class, + Illuminate\Cache\CacheServiceProvider::class, + Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, + Illuminate\Database\DatabaseServiceProvider::class, + Illuminate\Encryption\EncryptionServiceProvider::class, + Illuminate\Filesystem\FilesystemServiceProvider::class, + Illuminate\Foundation\Providers\FoundationServiceProvider::class, + Illuminate\Hashing\HashServiceProvider::class, + Illuminate\Mail\MailServiceProvider::class, + Illuminate\Notifications\NotificationServiceProvider::class, + Illuminate\Pagination\PaginationServiceProvider::class, + Illuminate\Pipeline\PipelineServiceProvider::class, + Illuminate\Queue\QueueServiceProvider::class, + Illuminate\Redis\RedisServiceProvider::class, + Illuminate\Translation\TranslationServiceProvider::class, + App\Providers\AppServiceProvider::class, + ], +];