From 3473321546360dbb66c24a30bb2620149c9a8f5d Mon Sep 17 00:00:00 2001 From: Daniel Polito Date: Thu, 9 May 2019 14:25:32 -0300 Subject: [PATCH 1/4] Improving UX of Start and Reset --- app/Commands/Reset.php | 105 ++++++++++++++++++---------- app/Commands/Start.php | 73 +++++++++++++++++-- app/Commands/Traits/ArtisanCall.php | 11 +++ app/Commands/Traits/RunTask.php | 17 +++++ app/Process.php | 44 ++++++++---- 5 files changed, 195 insertions(+), 55 deletions(-) create mode 100644 app/Commands/Traits/RunTask.php diff --git a/app/Commands/Reset.php b/app/Commands/Reset.php index 473c30f..bc06274 100644 --- a/app/Commands/Reset.php +++ b/app/Commands/Reset.php @@ -4,12 +4,13 @@ use App\Process; use App\Environment; +use App\Commands\Traits\RunTask; use App\Commands\Traits\ArtisanCall; use LaravelZero\Framework\Commands\Command; class Reset extends Command { - use ArtisanCall; + use ArtisanCall, RunTask; /** * The signature of the command. @@ -74,94 +75,122 @@ public function handle(Environment $environment, Process $process) protected function composerInstall() { - return $this->artisanCall('composer', ['install']); + return $this->runTask('Composer Install', function() { + return $this->artisanCallNoOutput('composer', ['install']); + }); } protected function mysqlDropDatabase() { - return $this->artisanCall('mysql-raw', [ - '-e', - sprintf('drop database if exists %s', env('DB_DATABASE')), - ]); + return $this->runTask('MySQL Drop Database', function() { + return $this->artisanCallNoOutput('mysql-raw', [ + '-e', + sprintf('drop database if exists %s', env('DB_DATABASE')), + ]); + }); } protected function mysqlCreateDatabase() { - return $this->artisanCall('mysql-raw', [ - '-e', - sprintf('create database %s', env('DB_DATABASE')), - ]); + return $this->runTask('MySQL Create Database', function() { + return $this->artisanCallNoOutput('mysql-raw', [ + '-e', + sprintf('create database %s', env('DB_DATABASE')), + ]); + }); } protected function mysqlGrantDatabase() { - return $this->artisanCall('mysql-raw', ['-e', sprintf( - 'grant all on %s.* to %s@"%%"', - env('DB_DATABASE'), - env('DB_USERNAME') - )]); + return $this->runTask('MySQL Grant Privileges', function() { + return $this->artisanCallNoOutput('mysql-raw', ['-e', sprintf( + 'grant all on %s.* to %s@"%%"', + env('DB_DATABASE'), + env('DB_USERNAME') + )]); + }); } protected function artisanMigrateFresh(Environment $environment, Process $process) { - return $process->dockerComposeExec( - sprintf('-e DB_DATABASE=%s', env('DB_DATABASE')), - sprintf('-e DB_USERNAME=%s', env('DB_USERNAME')), - sprintf('-e DB_PASSWORD=%s', env('DB_PASSWORD')), - 'app php artisan migrate:fresh' - ); + return $this->runTask('Migrate Fresh', function() use ($process) { + return $process->dockerComposeExec( + sprintf('-e DB_DATABASE=%s', env('DB_DATABASE')), + sprintf('-e DB_USERNAME=%s', env('DB_USERNAME')), + sprintf('-e DB_PASSWORD=%s', env('DB_PASSWORD')), + 'app php artisan migrate:fresh' + ); + }); } protected function artisanMigrateFreshSeed(Environment $environment, Process $process) { - return $process->dockerComposeExec( - sprintf('-e DB_DATABASE=%s', env('DB_DATABASE')), - sprintf('-e DB_USERNAME=%s', env('DB_USERNAME')), - sprintf('-e DB_PASSWORD=%s', env('DB_PASSWORD')), - 'app php artisan migrate:fresh --seed' - ); + return $this->runTask('Migrate Fresh Seed', function() use ($process) { + return $process->dockerComposeExecNoOutput( + sprintf('-e DB_DATABASE=%s', env('DB_DATABASE')), + sprintf('-e DB_USERNAME=%s', env('DB_USERNAME')), + sprintf('-e DB_PASSWORD=%s', env('DB_PASSWORD')), + 'app php artisan migrate:fresh --seed' + ); + }); } protected function yarnInstall() { - return $this->artisanCall('yarn', ['install']); + return $this->runTask('Yarn Install', function() { + return $this->artisanCallNoOutput('yarn', ['install']); + }); } protected function yarnDev() { - return $this->artisanCall('yarn', ['dev']); + return $this->runTask('Yarn Dev', function() { + return $this->artisanCallNoOutput('yarn', ['dev']); + }); } protected function clearCompiled() { - return $this->artisanCall('artisan', ['clear-compiled']); + return $this->runTask('Clear Compiled', function() { + return $this->artisanCallNoOutput('artisan', ['clear-compiled']); + }); } protected function clearCache() { - return $this->artisanCall('artisan', ['cache:clear']); + return $this->runTask('Clear Cache', function() { + return $this->artisanCallNoOutput('artisan', ['cache:clear']); + }); } protected function clearConfig() { - return $this->artisanCall('artisan', ['config:clear']); + return $this->runTask('Clear Config', function() { + return $this->artisanCallNoOutput('artisan', ['config:clear']); + }); } protected function clearRoute() { - return $this->artisanCall('artisan', ['route:clear']); + return $this->runTask('Clear Route', function() { + return $this->artisanCallNoOutput('artisan', ['route:clear']); + }); } protected function clearView() { - return $this->artisanCall('artisan', ['view:clear']); + return $this->runTask('Clear View', function() { + return $this->artisanCallNoOutput('artisan', ['view:clear']); + }); } protected function clearLogs(Environment $environment, Process $process) { - return $process->dockerComposeExec( - 'app rm -f', - $environment->getContextFile('storage/logs/*.log') - ); + return $this->runTask('Clear Logs', function() use ($environment, $process) { + return $process->dockerComposeExecNoOutput( + 'app rm -f', + $environment->getContextFile('storage/logs/*.log') + ); + }); } } diff --git a/app/Commands/Start.php b/app/Commands/Start.php index 4bb07fc..6654bc3 100644 --- a/app/Commands/Start.php +++ b/app/Commands/Start.php @@ -2,17 +2,22 @@ namespace App\Commands; -use App\Process; +use App\Commands\Traits\RunTask; +use App\Commands\Traits\ArtisanCall; use LaravelZero\Framework\Commands\Command; class Start extends Command { + use ArtisanCall, RunTask; + /** * The signature of the command. * * @var string */ - protected $signature = 'start'; + protected $signature = 'start + {--no-wait : Do not wait for Docker and MySQL to become available} + {--timeout=60 : The number of seconds to wait}'; /** * The description of the command. @@ -21,13 +26,73 @@ class Start extends Command */ protected $description = 'Start fwd environment containers.'; + protected $seconds = 0; + /** * Execute the console command. * * @return mixed */ - public function handle(Process $process) + public function handle() + { + $commands = [ + [$this, 'dockerComposePs'], + [$this, 'dockerComposeUpD'], + [$this, 'mysql'], + ]; + + // Run commands, first that isn't success (0) stops and return that exitCode + foreach ($commands as $command) { + if ($exitCode = call_user_func($command)) { + return $exitCode; + } + } + } + + protected function dockerComposePs() { - return $process->dockerCompose('up', '-d'); + return $this->runTask('Checking Docker', function() { + return $this->runCommand(function () { + return $this->artisanCallNoOutput('ps'); + }); + }); + } + + protected function dockerComposeUpD() + { + return $this->runTask('Starting fwd', function() { + return $this->artisanCall('up', ['-d']); + }); + } + + protected function mysql() + { + return $this->runTask('Checking MySQL', function() { + return $this->runCommand(function () { + return $this->artisanCallNoOutput('mysql-raw', ['-e', 'SELECT 1']); + }); + }); + } + + protected function runCommand(\Closure $closure) + { + return ! $this->option('no-wait') + ? $this->waitForCommand($closure) + : $closure(); + } + + protected function waitForCommand(\Closure $closure) + { + while ($exitCode = $closure()) { + if ($this->seconds++ > $this->option('timeout')) { + $this->error('Timed out waiting the command to finish'); + + return 1; + } + + sleep(1); + } + + return $exitCode; } } diff --git a/app/Commands/Traits/ArtisanCall.php b/app/Commands/Traits/ArtisanCall.php index def4176..f033146 100644 --- a/app/Commands/Traits/ArtisanCall.php +++ b/app/Commands/Traits/ArtisanCall.php @@ -4,6 +4,7 @@ use Illuminate\Support\Facades\Artisan; use LaravelZero\Framework\Providers\CommandRecorder\CommandRecorderRepository; +use App\Process; trait ArtisanCall { @@ -15,4 +16,14 @@ public function artisanCall($command, array $arguments = []) return Artisan::call($command, $arguments); } + + public function artisanCallNoOutput($command, array $arguments = []) + { + $process = app(Process::class); + $process->disableOutput(); + $exitCode = $this->artisanCall($command, $arguments); + $process->enableOutput(); + + return $exitCode; + } } diff --git a/app/Commands/Traits/RunTask.php b/app/Commands/Traits/RunTask.php new file mode 100644 index 0000000..8f760e6 --- /dev/null +++ b/app/Commands/Traits/RunTask.php @@ -0,0 +1,17 @@ +task($title, function() use (&$exitCode, $task) { + return ! ($exitCode = $task()); + }); + + return $exitCode; + } +} diff --git a/app/Process.php b/app/Process.php index b411701..1bc27c5 100644 --- a/app/Process.php +++ b/app/Process.php @@ -7,6 +7,7 @@ class Process protected $commands = []; protected $cwd = null; protected $asUser = null; + protected $output = true; public function setAsUser($user) { @@ -20,6 +21,20 @@ public function asFWDUser() return $this->setAsUser(env('FWD_ASUSER')); } + public function enableOutput() + { + $this->output = true; + + return $this; + } + + public function disableOutput() + { + $this->output = false; + + return $this; + } + public function dockerRun(...$command) : int { $commandPrefix = [ @@ -50,6 +65,15 @@ public function dockerComposeExec(...$command) : int return $this->dockerCompose(...$params, ...$command); } + public function dockerComposeExecNoOutput(...$command) : int + { + $this->disableOutput(); + $exitCode = $this->dockerComposeExec(...$command); + $this->enableOutput(); + + return $exitCode; + } + public function docker(...$command) : int { $commandPrefix = [ @@ -119,7 +143,7 @@ protected function run(string $command) : int $pipes = []; $proc = proc_open( $command, - [STDIN, STDOUT, STDERR], + $this->getDescriptors(), $pipes, $this->cwd, null, @@ -129,21 +153,15 @@ protected function run(string $command) : int return proc_close($proc); } - protected function getCallback() + protected function getDescriptors() : array { - return $this->callback ?: function ($type, $buffer) { - $buffer = trim($buffer); + if ($this->output || env('FWD_VERBOSE')) { + return [STDIN, STDOUT, STDERR]; + } - switch ($type) { - case 'err': - $this->print($buffer); - break; + $devNull = fopen('/dev/null', 'w'); - case 'out': - $this->print($buffer); - break; - } - }; + return [STDIN, $devNull, $devNull]; } protected function print($line) From a6b6d396d4edcfce3cef9c2ef65c4d90848a68a6 Mon Sep 17 00:00:00 2001 From: Daniel Polito Date: Thu, 9 May 2019 14:28:31 -0300 Subject: [PATCH 2/4] StyleCI + Test --- app/Commands/Reset.php | 28 ++++++++++++++-------------- app/Commands/Start.php | 6 +++--- app/Commands/Traits/ArtisanCall.php | 2 +- app/Commands/Traits/RunTask.php | 2 +- tests/Feature/DuskTest.php | 1 - 5 files changed, 19 insertions(+), 20 deletions(-) diff --git a/app/Commands/Reset.php b/app/Commands/Reset.php index bc06274..26ff422 100644 --- a/app/Commands/Reset.php +++ b/app/Commands/Reset.php @@ -75,14 +75,14 @@ public function handle(Environment $environment, Process $process) protected function composerInstall() { - return $this->runTask('Composer Install', function() { + return $this->runTask('Composer Install', function () { return $this->artisanCallNoOutput('composer', ['install']); }); } protected function mysqlDropDatabase() { - return $this->runTask('MySQL Drop Database', function() { + return $this->runTask('MySQL Drop Database', function () { return $this->artisanCallNoOutput('mysql-raw', [ '-e', sprintf('drop database if exists %s', env('DB_DATABASE')), @@ -92,7 +92,7 @@ protected function mysqlDropDatabase() protected function mysqlCreateDatabase() { - return $this->runTask('MySQL Create Database', function() { + return $this->runTask('MySQL Create Database', function () { return $this->artisanCallNoOutput('mysql-raw', [ '-e', sprintf('create database %s', env('DB_DATABASE')), @@ -102,7 +102,7 @@ protected function mysqlCreateDatabase() protected function mysqlGrantDatabase() { - return $this->runTask('MySQL Grant Privileges', function() { + return $this->runTask('MySQL Grant Privileges', function () { return $this->artisanCallNoOutput('mysql-raw', ['-e', sprintf( 'grant all on %s.* to %s@"%%"', env('DB_DATABASE'), @@ -113,7 +113,7 @@ protected function mysqlGrantDatabase() protected function artisanMigrateFresh(Environment $environment, Process $process) { - return $this->runTask('Migrate Fresh', function() use ($process) { + return $this->runTask('Migrate Fresh', function () use ($process) { return $process->dockerComposeExec( sprintf('-e DB_DATABASE=%s', env('DB_DATABASE')), sprintf('-e DB_USERNAME=%s', env('DB_USERNAME')), @@ -125,7 +125,7 @@ protected function artisanMigrateFresh(Environment $environment, Process $proces protected function artisanMigrateFreshSeed(Environment $environment, Process $process) { - return $this->runTask('Migrate Fresh Seed', function() use ($process) { + return $this->runTask('Migrate Fresh Seed', function () use ($process) { return $process->dockerComposeExecNoOutput( sprintf('-e DB_DATABASE=%s', env('DB_DATABASE')), sprintf('-e DB_USERNAME=%s', env('DB_USERNAME')), @@ -137,56 +137,56 @@ protected function artisanMigrateFreshSeed(Environment $environment, Process $pr protected function yarnInstall() { - return $this->runTask('Yarn Install', function() { + return $this->runTask('Yarn Install', function () { return $this->artisanCallNoOutput('yarn', ['install']); }); } protected function yarnDev() { - return $this->runTask('Yarn Dev', function() { + return $this->runTask('Yarn Dev', function () { return $this->artisanCallNoOutput('yarn', ['dev']); }); } protected function clearCompiled() { - return $this->runTask('Clear Compiled', function() { + return $this->runTask('Clear Compiled', function () { return $this->artisanCallNoOutput('artisan', ['clear-compiled']); }); } protected function clearCache() { - return $this->runTask('Clear Cache', function() { + return $this->runTask('Clear Cache', function () { return $this->artisanCallNoOutput('artisan', ['cache:clear']); }); } protected function clearConfig() { - return $this->runTask('Clear Config', function() { + return $this->runTask('Clear Config', function () { return $this->artisanCallNoOutput('artisan', ['config:clear']); }); } protected function clearRoute() { - return $this->runTask('Clear Route', function() { + return $this->runTask('Clear Route', function () { return $this->artisanCallNoOutput('artisan', ['route:clear']); }); } protected function clearView() { - return $this->runTask('Clear View', function() { + return $this->runTask('Clear View', function () { return $this->artisanCallNoOutput('artisan', ['view:clear']); }); } protected function clearLogs(Environment $environment, Process $process) { - return $this->runTask('Clear Logs', function() use ($environment, $process) { + return $this->runTask('Clear Logs', function () use ($environment, $process) { return $process->dockerComposeExecNoOutput( 'app rm -f', $environment->getContextFile('storage/logs/*.log') diff --git a/app/Commands/Start.php b/app/Commands/Start.php index 6654bc3..cdeadc4 100644 --- a/app/Commands/Start.php +++ b/app/Commands/Start.php @@ -51,7 +51,7 @@ public function handle() protected function dockerComposePs() { - return $this->runTask('Checking Docker', function() { + return $this->runTask('Checking Docker', function () { return $this->runCommand(function () { return $this->artisanCallNoOutput('ps'); }); @@ -60,14 +60,14 @@ protected function dockerComposePs() protected function dockerComposeUpD() { - return $this->runTask('Starting fwd', function() { + return $this->runTask('Starting fwd', function () { return $this->artisanCall('up', ['-d']); }); } protected function mysql() { - return $this->runTask('Checking MySQL', function() { + return $this->runTask('Checking MySQL', function () { return $this->runCommand(function () { return $this->artisanCallNoOutput('mysql-raw', ['-e', 'SELECT 1']); }); diff --git a/app/Commands/Traits/ArtisanCall.php b/app/Commands/Traits/ArtisanCall.php index f033146..2a56cfc 100644 --- a/app/Commands/Traits/ArtisanCall.php +++ b/app/Commands/Traits/ArtisanCall.php @@ -2,9 +2,9 @@ namespace App\Commands\Traits; +use App\Process; use Illuminate\Support\Facades\Artisan; use LaravelZero\Framework\Providers\CommandRecorder\CommandRecorderRepository; -use App\Process; trait ArtisanCall { diff --git a/app/Commands/Traits/RunTask.php b/app/Commands/Traits/RunTask.php index 8f760e6..9db840f 100644 --- a/app/Commands/Traits/RunTask.php +++ b/app/Commands/Traits/RunTask.php @@ -8,7 +8,7 @@ public function runTask(string $title, \Closure $task) { $exitCode = null; - $this->task($title, function() use (&$exitCode, $task) { + $this->task($title, function () use (&$exitCode, $task) { return ! ($exitCode = $task()); }); diff --git a/tests/Feature/DuskTest.php b/tests/Feature/DuskTest.php index 19ca533..265825f 100644 --- a/tests/Feature/DuskTest.php +++ b/tests/Feature/DuskTest.php @@ -10,7 +10,6 @@ public function testDusk() { $this->artisan('dusk')->assertExitCode(0); - $this->assertCommandCalled('prepare-dusk'); $this->asFWDUser()->assertDockerComposeExec('app php artisan dusk'); } } From 43a9b7a361eebae4573fa8fd1c2c9e9289574197 Mon Sep 17 00:00:00 2001 From: Daniel Polito Date: Thu, 9 May 2019 14:29:21 -0300 Subject: [PATCH 3/4] Tweak Start --- app/Commands/Start.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Commands/Start.php b/app/Commands/Start.php index cdeadc4..82e1b63 100644 --- a/app/Commands/Start.php +++ b/app/Commands/Start.php @@ -61,7 +61,7 @@ protected function dockerComposePs() protected function dockerComposeUpD() { return $this->runTask('Starting fwd', function () { - return $this->artisanCall('up', ['-d']); + return $this->artisanCallNoOutput('up', ['-d']); }); } From 9508a1ae7c98c10ba20063b34ae66ed6dd5b96f1 Mon Sep 17 00:00:00 2001 From: Daniel Polito Date: Thu, 9 May 2019 14:33:37 -0300 Subject: [PATCH 4/4] Tweak --- app/Commands/Reset.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Commands/Reset.php b/app/Commands/Reset.php index 26ff422..095ca6e 100644 --- a/app/Commands/Reset.php +++ b/app/Commands/Reset.php @@ -114,7 +114,7 @@ protected function mysqlGrantDatabase() protected function artisanMigrateFresh(Environment $environment, Process $process) { return $this->runTask('Migrate Fresh', function () use ($process) { - return $process->dockerComposeExec( + return $process->dockerComposeExecNoOutput( sprintf('-e DB_DATABASE=%s', env('DB_DATABASE')), sprintf('-e DB_USERNAME=%s', env('DB_USERNAME')), sprintf('-e DB_PASSWORD=%s', env('DB_PASSWORD')),