diff --git a/app/Commands/LocalDocker/Start.php b/app/Commands/LocalDocker/Start.php index 1deff2a2..5ed842e7 100644 --- a/app/Commands/LocalDocker/Start.php +++ b/app/Commands/LocalDocker/Start.php @@ -10,12 +10,14 @@ use App\Services\ComposerVersion; use App\Services\Config\Env; use App\Services\Config\Github; +use App\Services\Config\PhpStormMeta; use App\Services\Docker\Container; use App\Services\Docker\Local\Config; use App\Services\Docker\SystemClock; use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Facades\Artisan; use M1\Env\Parser; +use Throwable; /** * Local docker start command @@ -52,6 +54,7 @@ class Start extends BaseLocalDocker { * @param \Illuminate\Filesystem\Filesystem $filesystem * @param \App\Services\Config\Github $github * @param \App\Services\Config\Env $env + * @param \App\Services\Config\PhpStormMeta $phpStormMeta * @param \App\Services\Docker\SystemClock $clock * @param \App\Services\ComposerVersion $composerVersion * @param \App\Services\Docker\Container $container @@ -65,6 +68,7 @@ public function handle( Filesystem $filesystem, Github $github, Env $env, + PhpStormMeta $phpStormMeta, SystemClock $clock, ComposerVersion $composerVersion, Container $container @@ -85,6 +89,7 @@ public function handle( } $this->prepareComposer( $config, $filesystem, $github ); + $this->copyPhpStormMetaFile( $config, $phpStormMeta ); // Start global containers if ( ! $this->option( 'skip-global') ) { @@ -166,7 +171,7 @@ protected function prepareComposer( Config $config, Filesystem $filesystem, Gith $this->secret( 'We have detected you have not configured a GitHub oAuth token. Please go to https://github.com/settings/tokens/new?scopes=repo and create one or enter an existing token' ); // Save the default token to the so config directory. - $github->save( $token ); + $github->save( (string) $token ); // Copy to local project. $github->copy( $composerDirectory ); @@ -317,4 +322,19 @@ protected function addEnvFile( Config $config, Env $env, Filesystem $filesystem $this->info( sprintf( '.env file created at %s', $file ) ); } + protected function copyPhpStormMetaFile( Config $config, PhpStormMeta $phpStormMeta ): void { + $projectRoot = $config->getProjectRoot(); + $warning = 'Unable to copy .phpstorm.meta.php file'; + + if ( ! $phpStormMeta->existsInProject( $projectRoot ) ) { + try { + if ( ! $phpStormMeta->copy( $projectRoot ) ) { + $this->warn( $warning ); + } + } catch ( Throwable $e ) { + $this->warn( $warning ); + } + } + } + } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index fd3034b9..b01b2fb0 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -27,6 +27,7 @@ use App\Services\Certificate\Trust\Strategies\MacOs; use App\Services\Config\Env; use App\Services\Config\Github; +use App\Services\Config\PhpStormMeta; use App\Services\Docker\Dns\Factory; use App\Services\Docker\Dns\Handler; use App\Services\Docker\Dns\OsSupport\BaseSupport; @@ -235,6 +236,10 @@ public function register(): void { ->needs( '$directory' ) ->give( config( 'squareone.config-dir' ) ); + $this->app->when( PhpStormMeta::class ) + ->needs( '$directory' ) + ->give( storage_path() ); + $this->app->bind( SpinnerInterface::class, Spinner::class ); $this->app->when( MigrationChecker::class ) diff --git a/app/Services/Config/PhpStormMeta.php b/app/Services/Config/PhpStormMeta.php new file mode 100644 index 00000000..d7974138 --- /dev/null +++ b/app/Services/Config/PhpStormMeta.php @@ -0,0 +1,54 @@ +metaFile = $this->directory . self::PHAR_META_FILE; + } + + /** + * Check if this project has a .phpstorm.meta.php file. + * + * @param string $projectRoot + * + * @return bool + */ + public function existsInProject( string $projectRoot ): bool { + return $this->filesystem->exists( $projectRoot . self::META_FILE ); + } + + /** + * Copy the .phpstorm.meta.php file to the project root. + * + * @param string $projectRoot The root path of the local project. + * + * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException + * + * @return bool + */ + public function copy( string $projectRoot ): bool { + $content = $this->filesystem->get( $this->metaFile ); + + return (bool) $this->filesystem->put( $projectRoot . self::META_FILE, $content ); + } + +} diff --git a/config/app.php b/config/app.php index 68a12968..f83e60f0 100644 --- a/config/app.php +++ b/config/app.php @@ -27,7 +27,7 @@ */ //'version' => app('git.version'), - 'version' => '5.6.5', + 'version' => '5.7.0', /* |-------------------------------------------------------------------------- diff --git a/storage/defaults/phpstorm.meta.php b/storage/defaults/phpstorm.meta.php new file mode 100644 index 00000000..ee295b99 --- /dev/null +++ b/storage/defaults/phpstorm.meta.php @@ -0,0 +1,18 @@ + '@', + ] ) ); + override( \DI\Container::get( 0 ), map( [ + '' => '@', + ] ) ); + override( \DI\FactoryInterface::make( 0 ), map( [ + '' => '@', + ] ) ); + override( \DI\Container::make( 0 ), map( [ + '' => '@', + ] ) ); +} diff --git a/tests/Feature/Commands/LocalDocker/StartTest.php b/tests/Feature/Commands/LocalDocker/StartTest.php index 4b3217eb..deefd4e6 100644 --- a/tests/Feature/Commands/LocalDocker/StartTest.php +++ b/tests/Feature/Commands/LocalDocker/StartTest.php @@ -9,11 +9,13 @@ use App\Commands\DockerCompose; use App\Services\Config\Github; use App\Commands\LocalDocker\Start; +use App\Services\Config\PhpStormMeta; use App\Services\Docker\Container; use Illuminate\Console\OutputStyle; use App\Services\Docker\SystemClock; use App\Services\Certificate\Handler; use App\Services\Docker\Local\Config; +use Illuminate\Contracts\Filesystem\FileNotFoundException; use Illuminate\Filesystem\Filesystem; use App\Commands\LocalDocker\Composer; use Illuminate\Support\Facades\Artisan; @@ -33,14 +35,14 @@ private function getDefaultEnv(): string { return "WP_PLUGIN_ACF_KEY='123456'" . PHP_EOL . "WP_PLUGIN_GF_KEY='123456'" . PHP_EOL . "WP_PLUGIN_GF_TOKEN='123456'" . PHP_EOL; } - public function test_it_can_start_a_project_with_standard_default_env_file() { + public function test_it_can_start_a_project_with_standard_default_env_file(): void { $config = $this->mock( Config::class ); $config->shouldReceive( 'getComposerVolume' ) ->twice() ->andReturn( storage_path( 'tests/dev/docker/composer' ) ); $config->shouldReceive( 'getProjectName' )->times( 3 )->andReturn( 'squareone' ); - $config->shouldReceive( 'getProjectRoot' )->times( 5 )->andReturn( storage_path( 'tests' ) ); + $config->shouldReceive( 'getProjectRoot' )->andReturn( storage_path( 'tests' ) ); $config->shouldReceive( 'getDockerDir' )->once()->andReturn( storage_path( 'tests/dev/docker' ) ); $config->shouldReceive( 'getProjectUrl' )->once()->andReturn( 'https://squareone.tribe' ); $config->shouldReceive( 'setPath' ) @@ -85,6 +87,17 @@ public function test_it_can_start_a_project_with_standard_default_env_file() { ->with( storage_path( 'tests/dev/docker/composer' ) ) ->andReturnTrue(); + $phpStormMeta = $this->mock( PhpStormMeta::class ); + + $phpStormMeta->shouldReceive( 'existsInProject' ) + ->once() + ->andReturnFalse(); + + $phpStormMeta->shouldReceive( 'copy' ) + ->once() + ->with( storage_path( 'tests' ) ) + ->andReturnTrue(); + $filesystem = $this->mock( Filesystem::class ); $filesystem->shouldReceive( 'get' ) @@ -181,17 +194,17 @@ public function test_it_can_start_a_project_with_standard_default_env_file() { $this->assertSame( 0, $tester->getStatusCode() ); $this->assertStringContainsString( 'Starting squareone', $tester->getDisplay() ); $this->assertStringContainsString( 'Project started: https://squareone.tribe', $tester->getDisplay() ); - $this->assertStringContainsString( 'Project started: https://squareone.tribe', $tester->getDisplay() ); + $this->assertStringNotContainsString( 'Unable to copy .phpstorm.meta.php file', $tester->getDisplay() ); } - public function test_it_can_start_a_project_with_custom_env_file_with_defaults_and_composer_v2() { + public function test_it_can_start_a_project_with_custom_env_file_with_defaults_and_composer_v2(): void { $config = $this->mock( Config::class ); $config->shouldReceive( 'getComposerVolume' ) ->once() ->andReturn( storage_path( 'tests/dev/docker/composer' ) ); $config->shouldReceive( 'getProjectName' )->times( 3 )->andReturn( 'squareone' ); - $config->shouldReceive( 'getProjectRoot' )->times( 4 )->andReturn( storage_path( 'tests' ) ); + $config->shouldReceive( 'getProjectRoot' )->andReturn( storage_path( 'tests' ) ); $config->shouldReceive( 'getDockerDir' )->once()->andReturn( storage_path( 'tests/dev/docker' ) ); $config->shouldReceive( 'getProjectUrl' )->once()->andReturn( 'https://squareone.tribe' ); $config->shouldReceive( 'setPath' ) @@ -240,6 +253,12 @@ public function test_it_can_start_a_project_with_custom_env_file_with_defaults_a ->with( storage_path( 'tests/dev/docker/composer' ) ) ->andReturnTrue(); + $phpStormMeta = $this->mock( PhpStormMeta::class ); + + $phpStormMeta->shouldReceive( 'existsInProject' ) + ->once() + ->andReturnTrue(); + $filesystem = $this->mock( Filesystem::class ); $filesystem->shouldReceive( 'get' ) @@ -321,17 +340,17 @@ public function test_it_can_start_a_project_with_custom_env_file_with_defaults_a $this->assertSame( 0, $tester->getStatusCode() ); $this->assertStringContainsString( 'Starting squareone', $tester->getDisplay() ); $this->assertStringContainsString( 'Project started: https://squareone.tribe', $tester->getDisplay() ); - $this->assertStringContainsString( 'Project started: https://squareone.tribe', $tester->getDisplay() ); + $this->assertStringNotContainsString( 'Unable to copy .phpstorm.meta.php file', $tester->getDisplay() ); } - public function test_it_can_start_a_project_with_custom_env_file_with_custom_env_vars() { + public function test_it_can_start_a_project_with_custom_env_file_with_custom_env_vars(): void { $config = $this->mock( Config::class ); $config->shouldReceive( 'getComposerVolume' ) ->twice() ->andReturn( storage_path( 'tests/dev/docker/composer' ) ); $config->shouldReceive( 'getProjectName' )->times( 3 )->andReturn( 'squareone' ); - $config->shouldReceive( 'getProjectRoot' )->times( 4 )->andReturn( storage_path( 'tests' ) ); + $config->shouldReceive( 'getProjectRoot' )->andReturn( storage_path( 'tests' ) ); $config->shouldReceive( 'getDockerDir' )->once()->andReturn( storage_path( 'tests/dev/docker' ) ); $config->shouldReceive( 'getProjectUrl' )->once()->andReturn( 'https://squareone.tribe' ); $config->shouldReceive( 'setPath' ) @@ -379,6 +398,12 @@ public function test_it_can_start_a_project_with_custom_env_file_with_custom_env ->with( storage_path( 'tests/dev/docker/composer' ) ) ->andReturnTrue(); + $phpStormMeta = $this->mock( PhpStormMeta::class ); + + $phpStormMeta->shouldReceive( 'existsInProject' ) + ->once() + ->andReturnTrue(); + $filesystem = $this->mock( Filesystem::class ); $filesystem->shouldReceive( 'get' ) @@ -476,10 +501,175 @@ public function test_it_can_start_a_project_with_custom_env_file_with_custom_env 'Enter your license key for WP_CUSTOM_PLUGIN_KEY (input is hidden)' => '123456', ] ); + $this->assertSame( 0, $tester->getStatusCode() ); + $this->assertStringContainsString( 'Starting squareone', $tester->getDisplay() ); + $this->assertStringContainsString( 'Project started: https://squareone.tribe', $tester->getDisplay() ); + $this->assertStringNotContainsString( 'Unable to copy .phpstorm.meta.php file', $tester->getDisplay() ); + } + + public function test_it_warns_the_user_if_phpstorm_meta_copy_failed(): void { + $config = $this->mock( Config::class ); + $config->shouldReceive( 'getComposerVolume' ) + ->twice() + ->andReturn( storage_path( 'tests/dev/docker/composer' ) ); + + $config->shouldReceive( 'getProjectName' )->times( 3 )->andReturn( 'squareone' ); + $config->shouldReceive( 'getProjectRoot' )->andReturn( storage_path( 'tests' ) ); + $config->shouldReceive( 'getDockerDir' )->once()->andReturn( storage_path( 'tests/dev/docker' ) ); + $config->shouldReceive( 'getProjectUrl' )->once()->andReturn( 'https://squareone.tribe' ); + $config->shouldReceive( 'setPath' ) + ->once() + ->with( storage_path( 'tests' ) ) + ->andReturnSelf(); + + $certHandler = $this->mock( Handler::class ); + $certHandler->shouldReceive( 'caExists' )->once()->andReturnTrue(); + $certHandler->shouldReceive( 'createCertificate' )->once()->with( 'squareone.tribe' ); + $certHandler->shouldReceive( 'createCertificate' )->once()->with( 'squareonetest.tribe' ); + + $env = $this->mock( Env::class ); + + $env->shouldReceive( 'exists' ) + ->once() + ->andReturnFalse(); + + $env->shouldReceive( 'save' ) + ->once() + ->with( $this->getDefaultEnv() ); + + // No difference between env files + $env->shouldReceive( 'diff' ) + ->once() + ->with( storage_path( 'tests/.env.sample' ) ) + ->andReturn( [] ); + + $env->shouldReceive( 'copy' ) + ->once() + ->with( storage_path( 'tests' ) ) + ->andReturnTrue(); + + $github = $this->mock( Github::class ); + + $github->shouldReceive( 'exists' ) + ->once() + ->andReturnTrue(); + + $github->shouldReceive( 'copy' ) + ->once() + ->with( storage_path( 'tests/dev/docker/composer' ) ) + ->andReturnTrue(); + + $phpStormMeta = $this->mock( PhpStormMeta::class ); + + $phpStormMeta->shouldReceive( 'existsInProject' ) + ->once() + ->andReturnFalse(); + + // Exception thrown on copy + $phpStormMeta->shouldReceive( 'copy' ) + ->once() + ->with( storage_path( 'tests' ) ) + ->andThrow( FileNotFoundException::class ); + + $filesystem = $this->mock( Filesystem::class ); + + $filesystem->shouldReceive( 'get' ) + ->once() + ->with( storage_path( 'defaults/env' ) ) + ->andReturn( $this->getDefaultEnv() ); + + $filesystem->shouldReceive( 'exists' ) + ->once() + ->with( storage_path( 'tests/.env.sample' ) ) + ->andReturnTrue(); + + $filesystem->shouldReceive( 'missing' ) + ->once() + ->with( storage_path( 'tests/.env' ) ) + ->andReturnTrue(); + + $filesystem->shouldReceive( 'missing' ) + ->once() + ->with( storage_path( 'tests/dev/docker/composer/auth.json' ) ) + ->andReturnTrue(); + + $filesystem->shouldReceive( 'missing' ) + ->once() + ->with( storage_path( 'tests/dev/docker/composer/composer.lock' ) ) + ->andReturnTrue(); + + // Assert vm time sync runs. + $clock = $this->mock( SystemClock::class ); + $clock->shouldReceive( 'sync' )->once(); + + // Composer v1 + $composerVersion = $this->mock( ComposerVersion::class ); + $composerVersion->shouldReceive( 'isVersionOne' )->once()->andReturnTrue(); + + // Assert global would start. + Artisan::shouldReceive( 'call' ) + ->once() + ->with( GlobalStart::class, [], OutputStyle::class ); + + // Assert the local project would start. + Artisan::shouldReceive( 'call' ) + ->once() + ->with( DockerCompose::class, [ + '--project-name', + 'squareone', + 'up', + '-d', + '--force-recreate', + '--remove-orphans', + ] ); + + $container = $this->mock( Container::class ); + $container->shouldReceive( 'getId' )->once()->andReturn( 'php-fpm-container-id' ); + + // Assert prestissimo is installed in the php-fpm container + Artisan::shouldReceive( 'call' ) + ->once() + ->with( Docker::class, [ + 'exec', + '--tty', + 'php-fpm-container-id', + 'composer', + 'global', + 'require', + 'hirak/prestissimo', + ] ); + + // Assert composer install would be run. + Artisan::shouldReceive( 'call' )->once() + ->with( Composer::class, [ + 'args' => [ + 'install', + ], + ], OutputStyle::class ); + + // Assert open command is called when passed. + Artisan::shouldReceive( 'call' ) + ->once() + ->with( Open::class, [ + 'url' => 'https://squareone.tribe', + ] ); + + $command = $this->app->make( Start::class ); + + // Run command pass a git token when requested. + $tester = $this->runCommand( $command, [ '--browser' => true, '--path' => storage_path( 'tests' ), '--remove-orphans' => true ], [ + 'Enter your license key for WP_PLUGIN_ACF_KEY (input is hidden)' => '123456', + 'Enter your license key for WP_PLUGIN_GF_KEY (input is hidden)' => '123456', + 'Enter your license key for WP_PLUGIN_GF_TOKEN (input is hidden)' => '123456', + 'Enter your license key for WP_CUSTOM_PLUGIN_KEY (input is hidden)' => '123456', + ] ); + $this->assertSame( 0, $tester->getStatusCode() ); $this->assertStringContainsString( 'Starting squareone', $tester->getDisplay() ); $this->assertStringContainsString( 'Project started: https://squareone.tribe', $tester->getDisplay() ); $this->assertStringContainsString( 'Project started: https://squareone.tribe', $tester->getDisplay() ); + // PhpStorm meta copy warning message + $this->assertStringContainsString( 'Unable to copy .phpstorm.meta.php file', $tester->getDisplay() ); } } diff --git a/tests/Unit/Services/Config/PhpstormMetaTest.php b/tests/Unit/Services/Config/PhpstormMetaTest.php new file mode 100644 index 00000000..ef23726c --- /dev/null +++ b/tests/Unit/Services/Config/PhpstormMetaTest.php @@ -0,0 +1,58 @@ +filesystem = $this->mock( Filesystem::class ); + $this->phpStormMeta = new PhpStormMeta( $this->filesystem, storage_path( 'tests' ) ); + } + + public function test_it_finds_existing_meta_in_project(): void { + $this->filesystem->shouldReceive( 'exists' ) + ->once() + ->with( sprintf( '/tmp/local-project%s', PhpStormMeta::META_FILE ) ) + ->andReturnTrue(); + + $this->assertTrue( $this->phpStormMeta->existsInProject( '/tmp/local-project' ) ); + } + + public function test_it_copies_phpstorm_meta_file(): void { + $this->filesystem->shouldReceive( 'exists' ) + ->once() + ->with( sprintf( '/tmp/local-project%s', PhpStormMeta::META_FILE ) ) + ->andReturnFalse(); + + $this->filesystem->shouldReceive( 'get' ) + ->once() + ->with( storage_path( sprintf( 'tests/defaults%s', PhpStormMeta::PHAR_META_FILE ) ) ) + ->andReturn( 'mock .phpstorm.meta.php content' ); + + $this->filesystem->shouldReceive( 'put' ) + ->once() + ->with( sprintf( '/tmp/local-project%s', PhpStormMeta::META_FILE ), 'mock .phpstorm.meta.php content' ) + ->andReturnTrue(); + + $this->assertFalse( $this->phpStormMeta->existsInProject( '/tmp/local-project' ) ); + $this->assertTrue( $this->phpStormMeta->copy( '/tmp/local-project' ) ); + + } + +}