From 2feec79a5ae2b4394b765bf861523ef62d9a5328 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Thu, 5 Dec 2024 17:30:47 -0500 Subject: [PATCH] [5.x] Add new `starter-kit:init` command (#11215) --- .../MigratesLegacyStarterKitConfig.php | 67 ++ src/Console/Commands/StarterKitExport.php | 56 +- src/Console/Commands/StarterKitInit.php | 303 ++++++++ src/Console/Commands/StarterKitInstall.php | 8 +- .../starter-kits/ServiceProvider.php.stub | 13 + .../stubs/starter-kits/composer.json.stub | 9 - src/Providers/ConsoleServiceProvider.php | 1 + tests/StarterKits/InitTest.php | 661 ++++++++++++++++++ 8 files changed, 1062 insertions(+), 56 deletions(-) create mode 100644 src/Console/Commands/Concerns/MigratesLegacyStarterKitConfig.php create mode 100644 src/Console/Commands/StarterKitInit.php create mode 100644 src/Console/Commands/stubs/starter-kits/ServiceProvider.php.stub delete mode 100644 src/Console/Commands/stubs/starter-kits/composer.json.stub create mode 100644 tests/StarterKits/InitTest.php diff --git a/src/Console/Commands/Concerns/MigratesLegacyStarterKitConfig.php b/src/Console/Commands/Concerns/MigratesLegacyStarterKitConfig.php new file mode 100644 index 0000000000..4f2cb34b14 --- /dev/null +++ b/src/Console/Commands/Concerns/MigratesLegacyStarterKitConfig.php @@ -0,0 +1,67 @@ +isUsingLegacyExporterConventions()) { + return $this; + } + + if ($this->input->isInteractive()) { + if (! confirm('Config should now live in the [package] folder. Would you like Statamic to move it for you?', true)) { + return $this; + } + } + + if (! File::exists($dir = base_path('package'))) { + File::makeDirectory($dir, 0755, true); + } + + if (File::exists($starterKitConfig = base_path('starter-kit.yaml'))) { + File::move($starterKitConfig, base_path('package/starter-kit.yaml')); + $this->components->info('Starter kit config moved to [package/starter-kit.yaml].'); + } + + if (File::exists($postInstallHook = base_path('StarterKitPostInstall.php'))) { + File::move($postInstallHook, base_path('package/StarterKitPostInstall.php')); + $this->components->info('Starter kit post-install hook moved to [package/StarterKitPostInstall.php].'); + } + + if ($exportPath && File::exists($packageComposerJson = $exportPath.'/composer.json')) { + File::move($packageComposerJson, base_path('package/composer.json')); + $this->components->info('Composer package config moved to [package/composer.json].'); + } + + $this->migrated = true; + + return $this; + } + + /** + * Check if migration logic was ran. + */ + protected function migratedLegacyConfig(): bool + { + return $this->migrated; + } +} diff --git a/src/Console/Commands/StarterKitExport.php b/src/Console/Commands/StarterKitExport.php index 9777ea4cda..74df3e99a1 100644 --- a/src/Console/Commands/StarterKitExport.php +++ b/src/Console/Commands/StarterKitExport.php @@ -3,6 +3,7 @@ namespace Statamic\Console\Commands; use Illuminate\Console\Command; +use Statamic\Console\Commands\Concerns\MigratesLegacyStarterKitConfig; use Statamic\Console\RunsInPlease; use Statamic\Facades\File; use Statamic\Facades\Path; @@ -13,7 +14,7 @@ class StarterKitExport extends Command { - use RunsInPlease; + use MigratesLegacyStarterKitConfig, RunsInPlease; /** * The name and signature of the console command. @@ -36,11 +37,11 @@ class StarterKitExport extends Command */ public function handle() { - if ($this->isUsingLegacyExporterConventions()) { - $this->askToMigrateToPackageFolder(); - } + $path = $this->getAbsolutePath(); + + $this->migrateLegacyConfig($path); - if (! File::exists($path = $this->getAbsolutePath())) { + if (! File::exists($path)) { $this->askToCreateExportPath($path); } @@ -55,7 +56,11 @@ public function handle() return 1; } - $this->components->info("Starter kit was successfully exported to [$path]."); + if (version_compare(app()->version(), '11', '<')) { + return $this->components->info("Starter kit was successfully exported to [$path]."); + } + + $this->components->success("Starter kit was successfully exported to [$path]."); } /** @@ -85,43 +90,4 @@ protected function askToCreateExportPath(string $path): void $this->components->info("A new directory has been created at [{$path}]."); } - - /** - * Determine if dev sandbox has starter-kit.yaml at root and/or customized composer.json at target path. - */ - protected function isUsingLegacyExporterConventions(): bool - { - return File::exists(base_path('starter-kit.yaml')); - } - - /** - * Determine if dev sandbox has starter-kit.yaml at root and/or customized composer.json at target path. - */ - protected function askToMigrateToPackageFolder(): void - { - if ($this->input->isInteractive()) { - if (! confirm('Config should now live in the [package] folder. Would you like Statamic to move it for you?', true)) { - return; - } - } - - if (! File::exists($dir = base_path('package'))) { - File::makeDirectory($dir, 0755, true); - } - - if (File::exists($starterKitConfig = base_path('starter-kit.yaml'))) { - File::move($starterKitConfig, base_path('package/starter-kit.yaml')); - $this->components->info('Starter kit config moved to [package/starter-kit.yaml].'); - } - - if (File::exists($postInstallHook = base_path('StarterKitPostInstall.php'))) { - File::move($postInstallHook, base_path('package/StarterKitPostInstall.php')); - $this->components->info('Starter kit post-install hook moved to [package/StarterKitPostInstall.php].'); - } - - if (File::exists($packageComposerJson = $this->getAbsolutePath().'/composer.json')) { - File::move($packageComposerJson, base_path('package/composer.json')); - $this->components->info('Composer package config moved to [package/composer.json].'); - } - } } diff --git a/src/Console/Commands/StarterKitInit.php b/src/Console/Commands/StarterKitInit.php new file mode 100644 index 0000000000..e26f02d729 --- /dev/null +++ b/src/Console/Commands/StarterKitInit.php @@ -0,0 +1,303 @@ +package = $this->getKitPackage(); + $this->kitName = $this->getKitName(); + $this->kitDescription = $this->getKitDescription(); + $this->updatable = $this->getKitUpdatable(); + } catch (StarterKitException $exception) { + return 1; + } + + if (! $this->package || ! $this->kitName || ! $this->kitDescription) { + $this->components->info('You can manage your starter kit\'s package config in [package/composer.json] at any time.'); + } + + $this + ->migrateLegacyConfig() + ->createFolder() + ->createConfig() + ->createComposerJson() + ->createServiceProvider(); + + if (version_compare(app()->version(), '11', '<')) { + return $this->components->info('Your starter kit config was successfully created in your project\'s [package] folder.'); + } + + $this->components->success('Your starter kit config was successfully created in your project\'s [package] folder.'); + } + + /** + * Get starter kit package (optional). + */ + protected function getKitPackage(bool $promptingAgain = false): ?string + { + $promptText = 'Starter Kit Package (eg. hasselhoff/kung-fury)'; + + if ($promptingAgain) { + $package = text($promptText); + } elseif ($this->input->isInteractive()) { + $package = $this->argument('package') ?: text($promptText); + } else { + $package = $this->argument('package'); + } + + if ($package) { + $fails = $this->validationFails($package, new ComposerPackage); + } + + if ($package && $fails && $this->input->isInteractive()) { + return $this->getKitPackage(true); + } elseif ($package && $fails) { + throw new StarterKitException; + } + + return $package; + } + + /** + * Get starter kit name (optional). + */ + protected function getKitName(): ?string + { + if (! $this->input->isInteractive()) { + return $this->option('name'); + } + + return $this->option('name') ?: text('Starter Kit Name (eg. Kung Fury)'); + } + + /** + * Get starter kit description (optional). + */ + protected function getKitDescription(): ?string + { + if (! $this->input->isInteractive()) { + return $this->option('description'); + } + + return $this->option('description') ?: text('Starter Kit Description'); + } + + /** + * Get whether the starter kit is to be updatable (optional). + */ + protected function getKitUpdatable(): bool + { + if (! $this->input->isInteractive()) { + return $this->option('updatable'); + } + + return $this->option('updatable') ?: confirm( + label: 'Would you like to make this starter-kit updatable?', + default: false, + hint: 'Read more: https://statamic.dev/starter-kits/creating-a-starter-kit#making-starter-kits-updatable', + ); + } + + /** + * Create composer.json config from stub. + */ + protected function createFolder($dir = null): self + { + $dir ??= base_path('package'); + + if (! File::exists($dir)) { + File::makeDirectory($dir, 0755, true); + } + + return $this; + } + + /** + * Create starter-kit.yaml config from stub. + */ + protected function createConfig(): self + { + if ($this->migratedLegacyConfig()) { + return $this; + } + + $contents = File::get(__DIR__.'/stubs/starter-kits/starter-kit.yaml.stub'); + + $targetPath = base_path('package/starter-kit.yaml'); + + if ($this->input->isInteractive() && File::exists($targetPath) && ! $this->option('force')) { + if (! confirm('A [starter-kit.yaml] config already exists. Would you like to overwrite it?', false)) { + return $this; + } + } + + if ($this->updatable) { + $contents = "updatable: true\n".$contents; + } + + File::put($targetPath, $contents); + + return $this; + } + + /** + * Create composer.json config. + */ + protected function createComposerJson(): self + { + $targetPath = base_path('package/composer.json'); + + if ($this->input->isInteractive() && File::exists($targetPath) && ! $this->option('force')) { + if (! confirm('A [composer.json] config already exists. Would you like to overwrite it?', false)) { + return $this; + } + } + + $json = [ + 'name' => 'example/starter-kit-package', + 'extra' => [ + 'statamic' => [ + 'name' => 'Example Name', + 'description' => 'A description of your starter kit', + ], + ], + ]; + + if ($this->package) { + Arr::set($json, 'name', $this->package); + } + + if ($this->kitName) { + Arr::set($json, 'extra.statamic.name', $this->kitName); + } + + if ($this->kitDescription) { + Arr::set($json, 'extra.statamic.description', $this->kitDescription); + } + + if ($this->updatable && $namespace = $this->kitNamespace()) { + Arr::set($json, 'autoload.psr-4', [$namespace.'\\' => 'src']); + Arr::set($json, 'autoload-dev.psr-4', ['Tests\\' => 'tests']); + Arr::set($json, 'extra.laravel.providers', [$namespace.'\\ServiceProvider']); + } + + File::put($targetPath, json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + + return $this; + } + + /** + * Create service provider. + */ + protected function createServiceProvider(): self + { + if (! $this->updatable) { + return $this; + } + + $this->createFolder(base_path('package/src')); + + $contents = File::get(__DIR__.'/stubs/starter-kits/ServiceProvider.php.stub'); + + $targetPath = base_path('package/src/ServiceProvider.php'); + + if ($this->input->isInteractive() && File::exists($targetPath) && ! $this->option('force')) { + if (! confirm('A service provider already exists at [src/ServiceProvider.php]. Would you like to overwrite it?', false)) { + return $this; + } + } + + $contents = str_replace('DummyNamespace', $this->kitNamespace(), $contents); + + File::put($targetPath, $contents); + + return $this; + } + + /** + * Create kit namespace from name and input if possible. + */ + public function kitNamespace(): string + { + $vendor = 'Example'; + $namespace = 'StarterKitNamespace'; + + if ($this->package) { + [$vendor, $namespace] = explode('/', $this->package); + } + + if ($this->kitName) { + $namespace = $this->kitName; + } + + return Str::upperCamelize($vendor).'\\'.Str::upperCamelize($namespace); + } +} diff --git a/src/Console/Commands/StarterKitInstall.php b/src/Console/Commands/StarterKitInstall.php index cfb0c6576a..3dc63df088 100644 --- a/src/Console/Commands/StarterKitInstall.php +++ b/src/Console/Commands/StarterKitInstall.php @@ -49,7 +49,7 @@ public function handle() [$package, $branch] = $this->getPackageAndBranch(); if ($this->validationFails($package, new ComposerPackage)) { - return; + return 1; } $licenseManager = StarterKitLicenseManager::validate($package, $this->option('license'), $this, $this->input->isInteractive()); @@ -90,7 +90,11 @@ public function handle() $this->comment('composer global update statamic/cli'.PHP_EOL); } - $this->components->info("Starter kit [$package] was successfully installed."); + if (version_compare(app()->version(), '11', '<')) { + return $this->components->info("Starter kit [$package] was successfully installed."); + } + + $this->components->success("Starter kit [$package] was successfully installed."); } /** diff --git a/src/Console/Commands/stubs/starter-kits/ServiceProvider.php.stub b/src/Console/Commands/stubs/starter-kits/ServiceProvider.php.stub new file mode 100644 index 0000000000..6a98bebb9d --- /dev/null +++ b/src/Console/Commands/stubs/starter-kits/ServiceProvider.php.stub @@ -0,0 +1,13 @@ +files = app(Filesystem::class); + $this->packagePath = base_path('package'); + + $this->cleanUp(); + } + + public function tearDown(): void + { + $this->cleanUp(); + + parent::tearDown(); + } + + private function cleanUp() + { + if ($this->files->exists($this->packagePath)) { + $this->files->deleteDirectory($this->packagePath); + } + } + + #[Test] + public function it_can_init_basic_config() + { + $this->assertFileDoesNotExist($this->packagePath()); + + $this + ->initStarterKit() + ->expectsOutputToContain('You can manage your starter kit\'s package config in [package/composer.json] at any time.') + ->expectsOutputToContain('Your starter kit config was successfully created in your project\'s [package] folder.') + ->assertOk(); + + $this->assertCount(2, $this->files->allFiles($this->packagePath())); + + $this->assertFileExists($configPath = $this->packagePath('starter-kit.yaml')); + $this->assertFileExists($composerJsonPath = $this->packagePath('composer.json')); + + $this->assertStringEqualsStringIgnoringLineEndings(<<<'YAML' +# export_paths: +# - content +# - config/filesystems.php +# - config/statamic/assets.php +# - resources/blueprints +# - resources/css/site.css +# - resources/views +# - public/build +# - package.json +# - tailwind.config.js +# - vite.config.js +YAML, trim($this->files->get($configPath))); + + $this->assertStringEqualsStringIgnoringLineEndings(<<<'JSON' +{ + "name": "example/starter-kit-package", + "extra": { + "statamic": { + "name": "Example Name", + "description": "A description of your starter kit" + } + } +} +JSON, trim($this->files->get($composerJsonPath))); + } + + #[Test] + public function it_can_interactively_init_basic_config() + { + $this->assertFileDoesNotExist($this->packagePath()); + + $this + ->initStarterKitInteractively() + ->expectsQuestion('Starter Kit Package (eg. hasselhoff/kung-fury)', null) + ->expectsQuestion('Starter Kit Name (eg. Kung Fury)', null) + ->expectsQuestion('Starter Kit Description', null) + ->expectsConfirmation('Would you like to make this starter-kit updatable?', 'no') + ->expectsOutputToContain('You can manage your starter kit\'s package config in [package/composer.json] at any time.') + ->expectsOutputToContain('Your starter kit config was successfully created in your project\'s [package] folder.') + ->assertOk(); + + $this->assertCount(2, $this->files->allFiles($this->packagePath())); + + $this->assertFileExists($configPath = $this->packagePath('starter-kit.yaml')); + $this->assertFileExists($composerJsonPath = $this->packagePath('composer.json')); + + $this->assertStringEqualsStringIgnoringLineEndings(<<<'YAML' +# export_paths: +# - content +# - config/filesystems.php +# - config/statamic/assets.php +# - resources/blueprints +# - resources/css/site.css +# - resources/views +# - public/build +# - package.json +# - tailwind.config.js +# - vite.config.js +YAML, trim($this->files->get($configPath))); + + $this->assertStringEqualsStringIgnoringLineEndings(<<<'JSON' +{ + "name": "example/starter-kit-package", + "extra": { + "statamic": { + "name": "Example Name", + "description": "A description of your starter kit" + } + } +} +JSON, trim($this->files->get($composerJsonPath))); + } + + #[Test] + public function it_can_init_with_custom_package_info() + { + $this->assertFileDoesNotExist($this->packagePath()); + + $this + ->initStarterKit([ + 'package' => 'statamic/starter-kit-cool-writings', + '--name' => 'Cool Writings', + '--description' => 'A Cool Runnings inspired starter kit.', + ]) + ->expectsOutputToContain('Your starter kit config was successfully created in your project\'s [package] folder.') + ->assertOk(); + + $this->assertCount(2, $this->files->allFiles($this->packagePath())); + + $this->assertFileExists($configPath = $this->packagePath('starter-kit.yaml')); + $this->assertFileExists($composerJsonPath = $this->packagePath('composer.json')); + + $this->assertStringEqualsStringIgnoringLineEndings(<<<'JSON' +{ + "name": "statamic/starter-kit-cool-writings", + "extra": { + "statamic": { + "name": "Cool Writings", + "description": "A Cool Runnings inspired starter kit." + } + } +} +JSON, trim($this->files->get($composerJsonPath))); + } + + #[Test] + public function it_validates_package() + { + $this->assertFileDoesNotExist($this->packagePath()); + + $this + ->initStarterKit([ + 'package' => 'statamic', + ]) + ->expectsOutputToContain('Must be a valid composer package name (eg. hasselhoff/kung-fury).') + ->doesntExpectOutputToContain('Your starter kit config was successfully created in your project\'s [package] folder.') + ->assertFailed(); + + $this->assertFileDoesNotExist($this->packagePath()); + } + + #[Test] + public function it_can_interactively_init_with_custom_package_info() + { + $this->assertFileDoesNotExist($this->packagePath()); + + $this + ->initStarterKitInteractively() + ->expectsQuestion('Starter Kit Package (eg. hasselhoff/kung-fury)', 'statamic/starter-kit-cool-writings') + ->expectsQuestion('Starter Kit Name (eg. Kung Fury)', 'Cool Writings') + ->expectsQuestion('Starter Kit Description', 'A Cool Runnings inspired starter kit.') + ->expectsConfirmation('Would you like to make this starter-kit updatable?', 'no') + ->expectsOutputToContain('Your starter kit config was successfully created in your project\'s [package] folder.') + ->assertOk(); + + $this->assertCount(2, $this->files->allFiles($this->packagePath())); + + $this->assertFileExists($configPath = $this->packagePath('starter-kit.yaml')); + $this->assertFileExists($composerJsonPath = $this->packagePath('composer.json')); + + $this->assertStringEqualsStringIgnoringLineEndings(<<<'JSON' +{ + "name": "statamic/starter-kit-cool-writings", + "extra": { + "statamic": { + "name": "Cool Writings", + "description": "A Cool Runnings inspired starter kit." + } + } +} +JSON, trim($this->files->get($composerJsonPath))); + } + + #[Test] + public function it_can_init_updatable_kit() + { + $this->assertFileDoesNotExist($this->packagePath()); + + $this + ->initStarterKit(['--updatable' => true]) + ->expectsOutputToContain('Your starter kit config was successfully created in your project\'s [package] folder.') + ->assertOk(); + + $this->assertCount(3, $this->files->allFiles($this->packagePath())); + + $this->assertFileExists($configPath = $this->packagePath('starter-kit.yaml')); + $this->assertFileExists($composerJsonPath = $this->packagePath('composer.json')); + $this->assertFileExists($serviceProviderPath = $this->packagePath('src/ServiceProvider.php')); + + $this->assertStringEqualsStringIgnoringLineEndings(<<<'YAML' +updatable: true +# export_paths: +# - content +# - config/filesystems.php +# - config/statamic/assets.php +# - resources/blueprints +# - resources/css/site.css +# - resources/views +# - public/build +# - package.json +# - tailwind.config.js +# - vite.config.js +YAML, trim($this->files->get($configPath))); + + $this->assertStringEqualsStringIgnoringLineEndings(<<<'JSON' +{ + "name": "example/starter-kit-package", + "extra": { + "statamic": { + "name": "Example Name", + "description": "A description of your starter kit" + }, + "laravel": { + "providers": [ + "Example\\StarterKitNamespace\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Example\\StarterKitNamespace\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests" + } + } +} +JSON, trim($this->files->get($composerJsonPath))); + + $this->assertStringEqualsStringIgnoringLineEndings(<<<'PHP' +files->get($serviceProviderPath))); + } + + #[Test] + public function it_can_interactively_init_updatable_kit() + { + $this->assertFileDoesNotExist($this->packagePath()); + + $this + ->initStarterKitInteractively() + ->expectsQuestion('Starter Kit Package (eg. hasselhoff/kung-fury)', null) + ->expectsQuestion('Starter Kit Name (eg. Kung Fury)', null) + ->expectsQuestion('Starter Kit Description', null) + ->expectsConfirmation('Would you like to make this starter-kit updatable?', 'yes') + ->expectsOutputToContain('Your starter kit config was successfully created in your project\'s [package] folder.') + ->assertOk(); + + $this->assertCount(3, $this->files->allFiles($this->packagePath())); + + $this->assertFileExists($configPath = $this->packagePath('starter-kit.yaml')); + $this->assertFileExists($composerJsonPath = $this->packagePath('composer.json')); + $this->assertFileExists($serviceProviderPath = $this->packagePath('src/ServiceProvider.php')); + + $this->assertStringEqualsStringIgnoringLineEndings(<<<'YAML' +updatable: true +# export_paths: +# - content +# - config/filesystems.php +# - config/statamic/assets.php +# - resources/blueprints +# - resources/css/site.css +# - resources/views +# - public/build +# - package.json +# - tailwind.config.js +# - vite.config.js +YAML, trim($this->files->get($configPath))); + + $this->assertStringEqualsStringIgnoringLineEndings(<<<'JSON' +{ + "name": "example/starter-kit-package", + "extra": { + "statamic": { + "name": "Example Name", + "description": "A description of your starter kit" + }, + "laravel": { + "providers": [ + "Example\\StarterKitNamespace\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Example\\StarterKitNamespace\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests" + } + } +} +JSON, trim($this->files->get($composerJsonPath))); + + $this->assertStringEqualsStringIgnoringLineEndings(<<<'PHP' +files->get($serviceProviderPath))); + } + + #[Test] + public function it_can_init_updatable_kit_with_custom_package_info() + { + $this->assertFileDoesNotExist($this->packagePath()); + + $this + ->initStarterKit([ + 'package' => 'statamic-rad-pack/starter-kit-cool-writings', + '--name' => 'Cool Writings', + '--description' => 'A Cool Runnings inspired starter kit.', + '--updatable' => true, + ]) + ->expectsOutputToContain('Your starter kit config was successfully created in your project\'s [package] folder.') + ->assertOk(); + + $this->assertCount(3, $this->files->allFiles($this->packagePath())); + + $this->assertFileExists($configPath = $this->packagePath('starter-kit.yaml')); + $this->assertFileExists($composerJsonPath = $this->packagePath('composer.json')); + $this->assertFileExists($serviceProviderPath = $this->packagePath('src/ServiceProvider.php')); + + $this->assertStringEqualsStringIgnoringLineEndings(<<<'JSON' +{ + "name": "statamic-rad-pack/starter-kit-cool-writings", + "extra": { + "statamic": { + "name": "Cool Writings", + "description": "A Cool Runnings inspired starter kit." + }, + "laravel": { + "providers": [ + "StatamicRadPack\\CoolWritings\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "StatamicRadPack\\CoolWritings\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests" + } + } +} +JSON, trim($this->files->get($composerJsonPath))); + + $this->assertStringEqualsStringIgnoringLineEndings(<<<'PHP' +files->get($serviceProviderPath))); + } + + #[Test] + public function it_can_interactively_init_updatable_kit_with_custom_package_info() + { + $this->assertFileDoesNotExist($this->packagePath()); + + $this + ->initStarterKitInteractively() + ->expectsQuestion('Starter Kit Package (eg. hasselhoff/kung-fury)', 'statamic-rad-pack/starter-kit-cool-writings') + ->expectsQuestion('Starter Kit Name (eg. Kung Fury)', 'Cool Writings') + ->expectsQuestion('Starter Kit Description', 'A Cool Runnings inspired starter kit.') + ->expectsConfirmation('Would you like to make this starter-kit updatable?', 'yes') + ->expectsOutputToContain('Your starter kit config was successfully created in your project\'s [package] folder.') + ->assertOk(); + + $this->assertCount(3, $this->files->allFiles($this->packagePath())); + + $this->assertFileExists($configPath = $this->packagePath('starter-kit.yaml')); + $this->assertFileExists($composerJsonPath = $this->packagePath('composer.json')); + $this->assertFileExists($serviceProviderPath = $this->packagePath('src/ServiceProvider.php')); + + $this->assertStringEqualsStringIgnoringLineEndings(<<<'JSON' +{ + "name": "statamic-rad-pack/starter-kit-cool-writings", + "extra": { + "statamic": { + "name": "Cool Writings", + "description": "A Cool Runnings inspired starter kit." + }, + "laravel": { + "providers": [ + "StatamicRadPack\\CoolWritings\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "StatamicRadPack\\CoolWritings\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests" + } + } +} +JSON, trim($this->files->get($composerJsonPath))); + + $this->assertStringEqualsStringIgnoringLineEndings(<<<'PHP' +files->get($serviceProviderPath))); + } + + #[Test] + public function it_properly_camel_cases_namespace() + { + $this->assertFileDoesNotExist($this->packagePath()); + + $this + ->initStarterKit([ + '--name' => 'Cool-Writings_Kit ABC', + '--updatable' => true, + ]) + ->expectsOutputToContain('Your starter kit config was successfully created in your project\'s [package] folder.') + ->assertOk(); + + $this->assertCount(3, $this->files->allFiles($this->packagePath())); + + $this->assertFileExists($composerJsonPath = $this->packagePath('composer.json')); + $this->assertFileExists($serviceProviderPath = $this->packagePath('src/ServiceProvider.php')); + + $this->assertStringContainsString('"Example\\\\CoolWritingsKitABC\\\\": "src"', $this->files->get($composerJsonPath)); + $this->assertStringContainsString('"Example\\\\CoolWritingsKitABC\\\\ServiceProvider"', $this->files->get($composerJsonPath)); + $this->assertStringContainsString('namespace Example\\CoolWritingsKitABC;', $this->files->get($serviceProviderPath)); + } + + #[Test] + public function it_asks_to_overwrite_existing_files_interactively() + { + $this->files->makeDirectory($this->packagePath()); + $this->files->put($configPath = $this->packagePath('starter-kit.yaml'), $config = 'existing starter-kit.yaml!'); + $this->files->put($composerJsonPath = $this->packagePath('composer.json'), $composerJson = 'existing composer.json!'); + + $this + ->initStarterKitInteractively() + ->expectsQuestion('Starter Kit Package (eg. hasselhoff/kung-fury)', 'statamic/starter-kit-cool-writings') + ->expectsQuestion('Starter Kit Name (eg. Kung Fury)', 'Cool Writings') + ->expectsQuestion('Starter Kit Description', 'A Cool Runnings inspired starter kit.') + ->expectsConfirmation('Would you like to make this starter-kit updatable?', 'no') + ->expectsConfirmation('A [starter-kit.yaml] config already exists. Would you like to overwrite it?', 'no') + ->expectsConfirmation('A [composer.json] config already exists. Would you like to overwrite it?', 'no') + ->expectsOutputToContain('Your starter kit config was successfully created in your project\'s [package] folder.') + ->assertOk(); + + $this->assertCount(2, $this->files->allFiles($this->packagePath())); + + $this->assertFileExists($configPath = $this->packagePath('starter-kit.yaml')); + $this->assertFileExists($composerJsonPath = $this->packagePath('composer.json')); + + $this->assertEquals($config, trim($this->files->get($configPath))); + $this->assertEquals($composerJson, trim($this->files->get($composerJsonPath))); + } + + #[Test] + public function it_can_overwrite_existing_files_interactively() + { + $this->files->makeDirectory($this->packagePath()); + $this->files->put($configPath = $this->packagePath('starter-kit.yaml'), $config = 'existing starter-kit.yaml!'); + $this->files->put($composerJsonPath = $this->packagePath('composer.json'), $composerJson = 'existing composer.json!'); + + $this + ->initStarterKitInteractively() + ->expectsQuestion('Starter Kit Package (eg. hasselhoff/kung-fury)', 'statamic/starter-kit-cool-writings') + ->expectsQuestion('Starter Kit Name (eg. Kung Fury)', 'Cool Writings') + ->expectsQuestion('Starter Kit Description', 'A Cool Runnings inspired starter kit.') + ->expectsConfirmation('Would you like to make this starter-kit updatable?', 'no') + ->expectsConfirmation('A [starter-kit.yaml] config already exists. Would you like to overwrite it?', 'yes') + ->expectsConfirmation('A [composer.json] config already exists. Would you like to overwrite it?', 'yes') + ->expectsOutputToContain('Your starter kit config was successfully created in your project\'s [package] folder.') + ->assertOk(); + + $this->assertCount(2, $this->files->allFiles($this->packagePath())); + + $this->assertFileExists($configPath = $this->packagePath('starter-kit.yaml')); + $this->assertFileExists($composerJsonPath = $this->packagePath('composer.json')); + + $this->assertStringEqualsStringIgnoringLineEndings(<<<'YAML' +# export_paths: +# - content +# - config/filesystems.php +# - config/statamic/assets.php +# - resources/blueprints +# - resources/css/site.css +# - resources/views +# - public/build +# - package.json +# - tailwind.config.js +# - vite.config.js +YAML, trim($this->files->get($configPath))); + + $this->assertStringEqualsStringIgnoringLineEndings(<<<'JSON' +{ + "name": "statamic/starter-kit-cool-writings", + "extra": { + "statamic": { + "name": "Cool Writings", + "description": "A Cool Runnings inspired starter kit." + } + } +} +JSON, trim($this->files->get($composerJsonPath))); + } + + #[Test] + public function it_always_overwrites_existing_files_when_run_non_interactively() + { + $this->files->makeDirectory($this->packagePath()); + $this->files->put($configPath = $this->packagePath('starter-kit.yaml'), $config = 'existing starter-kit.yaml!'); + $this->files->put($composerJsonPath = $this->packagePath('composer.json'), $composerJson = 'existing composer.json!'); + + $this + ->initStarterKit(['package' => 'statamic/starter-kit-cool-writings']) + ->expectsOutputToContain('Your starter kit config was successfully created in your project\'s [package] folder.') + ->assertOk(); + + $this->assertCount(2, $this->files->allFiles($this->packagePath())); + + $this->assertFileExists($configPath = $this->packagePath('starter-kit.yaml')); + $this->assertFileExists($composerJsonPath = $this->packagePath('composer.json')); + + $this->assertStringEqualsStringIgnoringLineEndings(<<<'YAML' +# export_paths: +# - content +# - config/filesystems.php +# - config/statamic/assets.php +# - resources/blueprints +# - resources/css/site.css +# - resources/views +# - public/build +# - package.json +# - tailwind.config.js +# - vite.config.js +YAML, trim($this->files->get($configPath))); + + $this->assertStringEqualsStringIgnoringLineEndings(<<<'JSON' +{ + "name": "statamic/starter-kit-cool-writings", + "extra": { + "statamic": { + "name": "Example Name", + "description": "A description of your starter kit" + } + } +} +JSON, trim($this->files->get($composerJsonPath))); + } + + private function packagePath($path = null) + { + return collect([$this->packagePath, $path])->filter()->implode('/'); + } + + private function initStarterKit($options = []) + { + return $this->artisan('statamic:starter-kit:init', array_merge([ + '--no-interaction' => true, + ], $options)); + } + + private function initStarterKitInteractively($options = []) + { + return $this->artisan('statamic:starter-kit:init', $options); + } + + private function assertFileHasContent($expected, $path) + { + $this->assertFileExists($path); + + $this->assertStringContainsString($expected, $this->files->get($path)); + } +}