From d7afca21f9552e59e1f2a249e48052b118083a57 Mon Sep 17 00:00:00 2001 From: Artyom Osepyan Date: Tue, 14 Oct 2025 21:37:12 +0300 Subject: [PATCH 01/13] feat: first step of refactoring readme generation --- src/Commands/InitCommand.php | 313 +++++++++++------------------ src/Generators/ReadmeGenerator.php | 91 +++++++++ tests/InitCommandTest.php | 30 +-- 3 files changed, 226 insertions(+), 208 deletions(-) create mode 100644 src/Generators/ReadmeGenerator.php diff --git a/src/Commands/InitCommand.php b/src/Commands/InitCommand.php index 675a941..3c5ab1e 100644 --- a/src/Commands/InitCommand.php +++ b/src/Commands/InitCommand.php @@ -14,11 +14,10 @@ use RonasIT\ProjectInitializator\Enums\RoleEnum; use RonasIT\ProjectInitializator\Extensions\ConfigWriter\ArrayFile; use Winter\LaravelConfigWriter\EnvFile; +use RonasIT\ProjectInitializator\Generators\ReadmeGenerator; class InitCommand extends Command implements Isolatable { - public const string TEMPLATES_PATH = 'vendor/ronasit/laravel-project-initializator/resources/md/readme'; - public const array RESOURCES_ITEMS = [ 'issue_tracker' => 'Issue Tracker', 'figma' => 'Figma', @@ -59,8 +58,6 @@ class InitCommand extends Command implements Isolatable protected array $emptyValuesList = []; - protected string $readmeContent = ''; - protected array $shellCommands = [ 'composer require laravel/ui', 'composer require ronasit/laravel-helpers', @@ -86,6 +83,14 @@ class InitCommand extends Command implements Isolatable protected AppTypeEnum $appType; + protected $readmeGenerator; + + public function __construct(ReadmeGenerator $readme) + { + parent::__construct(); + $this->readmeGenerator = $readme; + } + public function handle(): void { $this->prepareAppName(); @@ -176,16 +181,16 @@ public function handle(): void } if ($shouldGenerateReadme = $this->confirm('Do you want to generate a README file?', true)) { - $this->fillReadme(); + $this->readmeGenerator->fillReadme($this->appName, $this->appType->value); if ($this->confirm('Do you need a `Resources & Contacts` part?', true)) { - $this->fillResourcesAndContacts(); + $this->readmeGenerator->fillResourcesAndContacts(); $this->fillResources(); $this->fillContacts(); } if ($this->confirm('Do you need a `Prerequisites` part?', true)) { - $this->fillPrerequisites(); + $this->readmeGenerator->fillPrerequisites(); } if ($this->confirm('Do you need a `Getting Started` part?', true)) { @@ -193,18 +198,18 @@ public function handle(): void } if ($this->confirm('Do you need an `Environments` part?', true)) { - $this->fillEnvironments(); + $this->readmeGenerator->fillEnvironments($this->appUrl); } if ($this->confirm('Do you need a `Credentials and Access` part?', true)) { $this->fillCredentialsAndAccess($kebabName); if ($this->authType === AuthTypeEnum::Clerk) { - $this->fillClerkAuthType(); + $this->readmeGenerator->fillClerkAuthType(); } } - $this->saveReadme(); + $this->readmeGenerator->saveReadme(); $this->info('README generated successfully!'); @@ -221,9 +226,9 @@ public function handle(): void $this->saveRenovateJSON(); if ($shouldGenerateReadme) { - $this->fillRenovate(); + $this->readmeGenerator->fillRenovate(); - $this->saveReadme(); + $this->readmeGenerator->saveReadme(); } } @@ -262,107 +267,9 @@ public function handle(): void $this->runMigrations(); } - protected function setupComposerHooks(): void - { - $path = base_path('composer.json'); - - $content = file_get_contents($path); - - $data = json_decode($content, true); - - $this->addArrayItemIfMissing($data, 'extra.hooks.config.stop-on-failure', 'pre-commit'); - $this->addArrayItemIfMissing($data, 'extra.hooks.pre-commit', 'docker compose up -d php && docker compose exec -T nginx vendor/bin/pint --repair'); - $this->addArrayItemIfMissing($data, 'scripts.post-install-cmd', '[ $COMPOSER_DEV_MODE -eq 0 ] || cghooks add --ignore-lock'); - $this->addArrayItemIfMissing($data, 'scripts.post-update-cmd', 'cghooks update'); - - $resultData = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"; - - file_put_contents($path, $resultData); - } - - protected function addArrayItemIfMissing(array &$data, string $path, string $value): void - { - $current = Arr::get($data, $path, []); - - if (!in_array($value, $current)) { - $current[] = $value; - - Arr::set($data, $path, $current); - } - } - - protected function setAutoDocContactEmail(string $email): void - { - $config = ArrayFile::open(base_path('config/auto-doc.php')); - - $config->set('info.contact.email', $email); - - $config->write(); - } - - protected function runMigrations(): void - { - config([ - 'database.default' => $this->dbConnection, - 'database.connections.pgsql' => [ - 'driver' => $this->dbConnection, - 'host' => $this->dbHost, - 'port' => $this->dbPort, - 'database' => $this->dbName, - 'username' => $this->dbUserName, - 'password' => '', - ], - ]); - - shell_exec('php artisan migrate --ansi'); - } - - protected function createAdminUser(string $kebabName): void - { - $defaultPassword = substr(md5(uniqid()), 0, 8); - - $this->adminCredentials = [ - 'email' => $this->ask('Please enter an admin email', "admin@{$kebabName}.com"), - 'password' => $this->ask('Please enter an admin password', $defaultPassword), - ]; - - if ($this->authType === AuthTypeEnum::Clerk) { - $this->publishMigration( - view: view('initializator::admins_create_table')->with($this->adminCredentials), - migrationName: 'admins_create_table', - ); - } else { - $this->adminCredentials['name'] = $this->ask('Please enter an admin name', 'Admin'); - $this->adminCredentials['role_id'] = $this->ask('Please enter an admin role id', RoleEnum::Admin->value); - - $this->publishMigration( - view: view('initializator::add_default_user')->with($this->adminCredentials), - migrationName: 'add_default_user', - ); - } - } - - protected function fillReadme(): void - { - $file = $this->loadReadmePart('README.md'); - - $this->setReadmeValue($file, 'project_name', $this->appName); - - $this->setReadmeValue($file, 'type', $this->appType->value); - - $this->readmeContent = $file; - } - - protected function fillResourcesAndContacts(): void - { - $filePart = $this->loadReadmePart('RESOURCES_AND_CONTACTS.md'); - - $this->updateReadmeFile($filePart); - } - protected function fillResources(): void { - $filePart = $this->loadReadmePart('RESOURCES.md'); + $filePart = $this->readmeGenerator->loadReadmePart('RESOURCES.md'); $laterText = '(will be added later)'; foreach (self::RESOURCES_ITEMS as $key => $title) { @@ -378,82 +285,67 @@ protected function fillResources(): void if ($link === 'later') { $this->emptyValuesList[] = "{$title} link"; - $this->setReadmeValue($filePart, "{$key}_link"); - $this->setReadmeValue($filePart, "{$key}_later", $laterText); + $this->readmeGenerator->setReadmeValue($filePart, "{$key}_link"); + $this->readmeGenerator->setReadmeValue($filePart, "{$key}_later", $laterText); } elseif ($link !== 'no') { - $this->setReadmeValue($filePart, "{$key}_link", $link); - $this->setReadmeValue($filePart, "{$key}_later"); + $this->readmeGenerator->setReadmeValue($filePart, "{$key}_link", $link); + $this->readmeGenerator->setReadmeValue($filePart, "{$key}_later"); } $this->resources[$key] = ($link !== 'no'); - $this->removeTag($filePart, $key, $link === 'no'); + $this->readmeGenerator->removeTag($filePart, $key, $link === 'no'); } - $this->setReadmeValue($filePart, 'api_link', $this->appUrl); - $this->updateReadmeFile($filePart); + $this->readmeGenerator->setReadmeValue($filePart, 'api_link', $this->appUrl); + $this->readmeGenerator->updateReadmeFile($filePart); } protected function fillContacts(): void { - $filePart = $this->loadReadmePart('CONTACTS.md'); + $filePart = $this->readmeGenerator->loadReadmePart('CONTACTS.md'); foreach (self::CONTACTS_ITEMS as $key => $title) { if ($link = $this->ask("Please enter a {$title}'s email", '')) { - $this->setReadmeValue($filePart, "{$key}_link", $link); + $this->readmeGenerator->setReadmeValue($filePart, "{$key}_link", $link); } else { $this->emptyValuesList[] = "{$title}'s email"; } - $this->removeTag($filePart, $key); + $this->readmeGenerator->removeTag($filePart, $key); } - $this->setReadmeValue($filePart, 'team_lead_link', $this->codeOwnerEmail); - - $this->updateReadmeFile($filePart); - } - - protected function fillPrerequisites(): void - { - $filePart = $this->loadReadmePart('PREREQUISITES.md'); + $this->readmeGenerator->setReadmeValue($filePart, 'team_lead_link', $this->codeOwnerEmail); - $this->updateReadmeFile($filePart); + $this->readmeGenerator->updateReadmeFile($filePart); } protected function fillGettingStarted(): void { $gitProjectPath = trim((string) shell_exec('git ls-remote --get-url origin')); $projectDirectory = basename($gitProjectPath, '.git'); - $filePart = $this->loadReadmePart('GETTING_STARTED.md'); + $filePart = $this->readmeGenerator->loadReadmePart('GETTING_STARTED.md'); - $this->setReadmeValue($filePart, 'git_project_path', $gitProjectPath); - $this->setReadmeValue($filePart, 'project_directory', $projectDirectory); + $this->readmeGenerator->setReadmeValue($filePart, 'git_project_path', $gitProjectPath); + $this->readmeGenerator->setReadmeValue($filePart, 'project_directory', $projectDirectory); - $this->updateReadmeFile($filePart); - } - - protected function fillEnvironments(): void - { - $filePart = $this->loadReadmePart('ENVIRONMENTS.md'); - - $this->setReadmeValue($filePart, 'api_link', $this->appUrl); - $this->updateReadmeFile($filePart); + $this->readmeGenerator->updateReadmeFile($filePart); } protected function fillCredentialsAndAccess(string $kebabName): void { - $filePart = $this->loadReadmePart('CREDENTIALS_AND_ACCESS.md'); + $filePart = $this->readmeGenerator->loadReadmePart('CREDENTIALS_AND_ACCESS.md'); if (!empty($this->adminCredentials)) { - $this->setReadmeValue($filePart, 'admin_email', $this->adminCredentials['email']); - $this->setReadmeValue($filePart, 'admin_password', $this->adminCredentials['password']); + $this->readmeGenerator->setReadmeValue($filePart, 'admin_email', $this->adminCredentials['email']); + $this->readmeGenerator->setReadmeValue($filePart, 'admin_password', $this->adminCredentials['password']); } - $this->removeTag($filePart, 'admin_credentials', !$this->adminCredentials); + $this->readmeGenerator->removeTag($filePart, 'admin_credentials', !$this->adminCredentials); foreach (self::CREDENTIALS_ITEMS as $key => $title) { if (!Arr::get($this->resources, $key)) { - $this->removeTag($filePart, "{$key}_credentials", true); + $this->readmeGenerator->removeTag($filePart, "{$key}_credentials", true); continue; } @@ -468,24 +360,93 @@ protected function fillCredentialsAndAccess(string $kebabName): void $password = $this->ask("Please enter a {$title}'s admin password", $defaultPassword); } - $this->setReadmeValue($filePart, "{$key}_email", $email); - $this->setReadmeValue($filePart, "{$key}_password", $password); - $this->removeTag($filePart, "{$key}_credentials"); + $this->readmeGenerator->setReadmeValue($filePart, "{$key}_email", $email); + $this->readmeGenerator->setReadmeValue($filePart, "{$key}_password", $password); + $this->readmeGenerator->removeTag($filePart, "{$key}_credentials"); + } + + $this->readmeGenerator->updateReadmeFile($filePart); + } + + + protected function setupComposerHooks(): void + { + $path = base_path('composer.json'); + + $content = file_get_contents($path); + + $data = json_decode($content, true); + + $this->addArrayItemIfMissing($data, 'extra.hooks.config.stop-on-failure', 'pre-commit'); + $this->addArrayItemIfMissing($data, 'extra.hooks.pre-commit', 'docker compose up -d php && docker compose exec -T nginx vendor/bin/pint --repair'); + $this->addArrayItemIfMissing($data, 'scripts.post-install-cmd', '[ $COMPOSER_DEV_MODE -eq 0 ] || cghooks add --ignore-lock'); + $this->addArrayItemIfMissing($data, 'scripts.post-update-cmd', 'cghooks update'); + + $resultData = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"; + + file_put_contents($path, $resultData); + } + + protected function addArrayItemIfMissing(array &$data, string $path, string $value): void + { + $current = Arr::get($data, $path, []); + + if (!in_array($value, $current)) { + $current[] = $value; + + Arr::set($data, $path, $current); } + } - $this->updateReadmeFile($filePart); + protected function setAutoDocContactEmail(string $email): void + { + $config = ArrayFile::open(base_path('config/auto-doc.php')); + + $config->set('info.contact.email', $email); + + $config->write(); } - protected function fillClerkAuthType(): void + protected function runMigrations(): void { - $filePart = $this->loadReadmePart('CLERK.md'); + config([ + 'database.default' => $this->dbConnection, + 'database.connections.pgsql' => [ + 'driver' => $this->dbConnection, + 'host' => $this->dbHost, + 'port' => $this->dbPort, + 'database' => $this->dbName, + 'username' => $this->dbUserName, + 'password' => '', + ], + ]); - $this->updateReadmeFile($filePart); + shell_exec('php artisan migrate --ansi'); } - protected function addQuotes($string): string + protected function createAdminUser(string $kebabName): void { - return (Str::contains($string, ' ')) ? "\"{$string}\"" : $string; + $defaultPassword = substr(md5(uniqid()), 0, 8); + + $this->adminCredentials = [ + 'email' => $this->ask('Please enter an admin email', "admin@{$kebabName}.com"), + 'password' => $this->ask('Please enter an admin password', $defaultPassword), + ]; + + if ($this->authType === AuthTypeEnum::Clerk) { + $this->publishMigration( + view: view('initializator::admins_create_table')->with($this->adminCredentials), + migrationName: 'admins_create_table', + ); + } else { + $this->adminCredentials['name'] = $this->ask('Please enter an admin name', 'Admin'); + $this->adminCredentials['role_id'] = $this->ask('Please enter an admin role id', RoleEnum::Admin->value); + + $this->publishMigration( + view: view('initializator::add_default_user')->with($this->adminCredentials), + migrationName: 'add_default_user', + ); + } } protected function publishClass(View $template, string $fileName, string $filePath): void @@ -529,39 +490,6 @@ protected function updateEnvFile(string $fileName, array $data): void $env->write(); } - protected function loadReadmePart(string $fileName): string - { - $file = base_path(DIRECTORY_SEPARATOR . self::TEMPLATES_PATH . DIRECTORY_SEPARATOR . $fileName); - - return file_get_contents($file); - } - - protected function updateReadmeFile(string $filePart): void - { - $filePart = preg_replace('#(\n){3,}#', "\n", $filePart); - - $this->readmeContent .= "\n" . $filePart; - } - - protected function removeTag(string &$text, string $tag, bool $removeWholeString = false): void - { - $regex = ($removeWholeString) - ? "#({{$tag}})(.|\s)*?({/{$tag}})#" - : "# {0,1}{(/*){$tag}}#"; - - $text = preg_replace($regex, '', $text); - } - - protected function setReadmeValue(string &$file, string $key, string $value = ''): void - { - $file = str_replace(":{$key}", $value, $file); - } - - protected function saveReadme(): void - { - file_put_contents('README.md', $this->readmeContent); - } - protected function prepareAppName(): void { $this->appName = $this->argument('application-name'); @@ -606,13 +534,6 @@ protected function validateInput(callable $method, string $field, string|array $ return $value; } - protected function fillRenovate(): void - { - $filePart = $this->loadReadmePart('RENOVATE.md'); - - $this->updateReadmeFile($filePart); - } - protected function enableClerk(): void { array_push( diff --git a/src/Generators/ReadmeGenerator.php b/src/Generators/ReadmeGenerator.php new file mode 100644 index 0000000..f4d64f9 --- /dev/null +++ b/src/Generators/ReadmeGenerator.php @@ -0,0 +1,91 @@ +loadReadmePart('README.md'); + + $this->setReadmeValue($file, 'project_name', $appName); + + $this->setReadmeValue($file, 'type', $appType); + + $this->readmeContent = $file; + } + + public function fillResourcesAndContacts(): void + { + $filePart = $this->loadReadmePart('RESOURCES_AND_CONTACTS.md'); + + $this->updateReadmeFile($filePart); + } + + public function fillPrerequisites(): void + { + $filePart = $this->loadReadmePart('PREREQUISITES.md'); + + $this->updateReadmeFile($filePart); + } + + + public function fillEnvironments(string $appUrl): void + { + $filePart = $this->loadReadmePart('ENVIRONMENTS.md'); + + $this->setReadmeValue($filePart, 'api_link', $appUrl); + $this->updateReadmeFile($filePart); + } + + public function fillClerkAuthType(): void + { + $filePart = $this->loadReadmePart('CLERK.md'); + + $this->updateReadmeFile($filePart); + } + + public function loadReadmePart(string $fileName): string + { + $file = base_path(DIRECTORY_SEPARATOR . self::TEMPLATES_PATH . DIRECTORY_SEPARATOR . $fileName); + + return file_get_contents($file); + } + + public function updateReadmeFile(string $filePart): void + { + $filePart = preg_replace('#(\n){3,}#', "\n", $filePart); + + $this->readmeContent .= "\n" . $filePart; + } + + public function removeTag(string &$text, string $tag, bool $removeWholeString = false): void + { + $regex = ($removeWholeString) + ? "#({{$tag}})(.|\s)*?({/{$tag}})#" + : "# {0,1}{(/*){$tag}}#"; + + $text = preg_replace($regex, '', $text); + } + + public function setReadmeValue(string &$file, string $key, string $value = ''): void + { + $file = str_replace(":{$key}", $value, $file); + } + + public function saveReadme(): void + { + file_put_contents('README.md', $this->readmeContent); + } + + public function fillRenovate(): void + { + $filePart = $this->loadReadmePart('RENOVATE.md'); + + $this->updateReadmeFile($filePart); + } +} diff --git a/tests/InitCommandTest.php b/tests/InitCommandTest.php index 61dda08..5c93c35 100644 --- a/tests/InitCommandTest.php +++ b/tests/InitCommandTest.php @@ -221,24 +221,12 @@ public function testRunWithAdminAndDefaultReadmeCreation() $this->callFileExists('.env', false), $this->callFileExists('.env.development'), - $this->callFileGetContent($this->generateResourcePath('md/readme/README.md'), $this->getReadmeTemplateContent('README.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES_AND_CONTACTS.md'), $this->getReadmeTemplateContent('RESOURCES_AND_CONTACTS.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES.md'), $this->getReadmeTemplateContent('RESOURCES.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/CONTACTS.md'), $this->getReadmeTemplateContent('CONTACTS.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/PREREQUISITES.md'), $this->getReadmeTemplateContent('PREREQUISITES.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/GETTING_STARTED.md'), $this->getReadmeTemplateContent('GETTING_STARTED.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/ENVIRONMENTS.md'), $this->getReadmeTemplateContent('ENVIRONMENTS.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/CREDENTIALS_AND_ACCESS.md'), $this->getReadmeTemplateContent('CREDENTIALS_AND_ACCESS.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/CLERK.md'), $this->getReadmeTemplateContent('CLERK.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/RENOVATE.md'), $this->getReadmeTemplateContent('RENOVATE.md')), $this->callFileGetContent(base_path('composer.json'), $this->getFixture('composer_with_pint_settings.json')), $this->callFilePutContent('database/migrations/2018_11_11_111111_users_add_clerk_id_field.php', $this->getFixture('users_add_clerk_id_field_migration.php')), $this->callFilePutContent('app/Support/Clerk/ClerkUserRepository.php', $this->getFixture('clerk_user_repository.php')), $this->callFilePutContent('database/migrations/2018_11_11_111111_admins_create_table.php', $this->getFixture('admins_table_migration.php')), - $this->callFilePutContent('README.md', $this->getFixture('default_readme.md')), $this->callFilePutContent('renovate.json', $this->getFixture('renovate.json')), - $this->callFilePutContent('README.md', $this->getFixture('default_readme_after_using_renovate.md')), $this->callFilePutContent(base_path('composer.json'), $this->getFixture('composer_with_pint_settings.json')), $this->callFilePutContent(base_path('/routes/web.php'), "\nAuth::routes();\n", FILE_APPEND), @@ -261,6 +249,24 @@ public function testRunWithAdminAndDefaultReadmeCreation() $this->callShellExec('php artisan migrate --ansi'), ); + $this->mockNativeFunction( + 'RonasIT\ProjectInitializator\Generators', + + $this->callFileGetContent($this->generateResourcePath('md/readme/README.md'), $this->getReadmeTemplateContent('README.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES_AND_CONTACTS.md'), $this->getReadmeTemplateContent('RESOURCES_AND_CONTACTS.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES.md'), $this->getReadmeTemplateContent('RESOURCES.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/CONTACTS.md'), $this->getReadmeTemplateContent('CONTACTS.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/PREREQUISITES.md'), $this->getReadmeTemplateContent('PREREQUISITES.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/GETTING_STARTED.md'), $this->getReadmeTemplateContent('GETTING_STARTED.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/ENVIRONMENTS.md'), $this->getReadmeTemplateContent('ENVIRONMENTS.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/CREDENTIALS_AND_ACCESS.md'), $this->getReadmeTemplateContent('CREDENTIALS_AND_ACCESS.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/CLERK.md'), $this->getReadmeTemplateContent('CLERK.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/RENOVATE.md'), $this->getReadmeTemplateContent('RENOVATE.md')), + + $this->callFilePutContent('README.md', $this->getFixture('default_readme.md')), + $this->callFilePutContent('README.md', $this->getFixture('default_readme_after_using_renovate.md')), + ); + $this ->artisan('init "My App"') ->expectsConfirmation('The application name is not in PascalCase, would you like to use MyApp') From ee7519f3c4214d483ec046fec1e466dcde262266 Mon Sep 17 00:00:00 2001 From: Artyom Osepyan Date: Thu, 16 Oct 2025 19:24:17 +0300 Subject: [PATCH 02/13] fix: correct failing tests related to ReadmeGenerator --- tests/InitCommandTest.php | 94 ++++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 37 deletions(-) diff --git a/tests/InitCommandTest.php b/tests/InitCommandTest.php index 5c93c35..6070466 100644 --- a/tests/InitCommandTest.php +++ b/tests/InitCommandTest.php @@ -360,15 +360,8 @@ public function testRunWithAdminAndPartialReadmeCreation() $this->callFileExists('.env', false), $this->callFileExists('.env.development'), - $this->callFileGetContent($this->generateResourcePath('md/readme/README.md'), $this->getReadmeTemplateContent('README.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES_AND_CONTACTS.md'), $this->getReadmeTemplateContent('RESOURCES_AND_CONTACTS.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES.md'), $this->getReadmeTemplateContent('RESOURCES.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/CONTACTS.md'), $this->getReadmeTemplateContent('CONTACTS.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/ENVIRONMENTS.md'), $this->getReadmeTemplateContent('ENVIRONMENTS.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/CREDENTIALS_AND_ACCESS.md'), $this->getReadmeTemplateContent('CREDENTIALS_AND_ACCESS.md')), $this->callFileGetContent(base_path('composer.json'), $this->getFixture('composer_with_pint_settings.json')), - $this->callFilePutContent('README.md', $this->getFixture('partial_readme.md')), $this->callFilePutContent(base_path('composer.json'), $this->getFixture('composer_with_pint_settings.json')), $this->callFilePutContent(base_path('/routes/web.php'), "\nAuth::routes();\n", FILE_APPEND), @@ -388,6 +381,18 @@ public function testRunWithAdminAndPartialReadmeCreation() $this->callShellExec('php artisan migrate --ansi'), ); + $this->mockNativeFunction( + 'RonasIT\ProjectInitializator\Generators', + $this->callFileGetContent($this->generateResourcePath('md/readme/README.md'), $this->getReadmeTemplateContent('README.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES_AND_CONTACTS.md'), $this->getReadmeTemplateContent('RESOURCES_AND_CONTACTS.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES.md'), $this->getReadmeTemplateContent('RESOURCES.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/CONTACTS.md'), $this->getReadmeTemplateContent('CONTACTS.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/ENVIRONMENTS.md'), $this->getReadmeTemplateContent('ENVIRONMENTS.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/CREDENTIALS_AND_ACCESS.md'), $this->getReadmeTemplateContent('CREDENTIALS_AND_ACCESS.md')), + + $this->callFilePutContent('README.md', $this->getFixture('partial_readme.md')), + ); + $this ->artisan('init "My App"') ->expectsConfirmation('The application name is not in PascalCase, would you like to use MyApp') @@ -471,21 +476,10 @@ public function testRunWithAdminAndFullReadmeCreationAndRemovingInitializatorIns $this->callFileExists('.env', false), $this->callFileExists('.env.development'), - $this->callFileGetContent($this->generateResourcePath('md/readme/README.md'), $this->getReadmeTemplateContent('README.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES_AND_CONTACTS.md'), $this->getReadmeTemplateContent('RESOURCES_AND_CONTACTS.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES.md'), $this->getReadmeTemplateContent('RESOURCES.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/CONTACTS.md'), $this->getReadmeTemplateContent('CONTACTS.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/PREREQUISITES.md'), $this->getReadmeTemplateContent('PREREQUISITES.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/GETTING_STARTED.md'), $this->getReadmeTemplateContent('GETTING_STARTED.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/ENVIRONMENTS.md'), $this->getReadmeTemplateContent('ENVIRONMENTS.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/CREDENTIALS_AND_ACCESS.md'), $this->getReadmeTemplateContent('CREDENTIALS_AND_ACCESS.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/RENOVATE.md'), $this->getReadmeTemplateContent('RENOVATE.md')), $this->callFileGetContent(base_path('composer.json'), $this->getFixture('composer_with_pint_settings.json')), $this->callFilePutContent('database/migrations/2018_11_11_111111_add_default_user.php', $this->getFixture('migration.php')), - $this->callFilePutContent('README.md', $this->getFixture('full_readme.md')), $this->callFilePutContent('renovate.json', $this->getFixture('renovate.json')), - $this->callFilePutContent('README.md', $this->getFixture('full_readme_after_using_renovate.md')), $this->callFilePutContent(base_path('composer.json'), $this->getFixture('composer_with_pint_settings.json')), $this->callFilePutContent(base_path('/routes/web.php'), "\nAuth::routes();\n", FILE_APPEND), @@ -508,6 +502,22 @@ public function testRunWithAdminAndFullReadmeCreationAndRemovingInitializatorIns $this->callShellExec('php artisan migrate --ansi'), ); + $this->mockNativeFunction( + 'RonasIT\ProjectInitializator\Generators', + $this->callFileGetContent($this->generateResourcePath('md/readme/README.md'), $this->getReadmeTemplateContent('README.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES_AND_CONTACTS.md'), $this->getReadmeTemplateContent('RESOURCES_AND_CONTACTS.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES.md'), $this->getReadmeTemplateContent('RESOURCES.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/CONTACTS.md'), $this->getReadmeTemplateContent('CONTACTS.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/PREREQUISITES.md'), $this->getReadmeTemplateContent('PREREQUISITES.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/GETTING_STARTED.md'), $this->getReadmeTemplateContent('GETTING_STARTED.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/ENVIRONMENTS.md'), $this->getReadmeTemplateContent('ENVIRONMENTS.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/CREDENTIALS_AND_ACCESS.md'), $this->getReadmeTemplateContent('CREDENTIALS_AND_ACCESS.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/RENOVATE.md'), $this->getReadmeTemplateContent('RENOVATE.md')), + + $this->callFilePutContent('README.md', $this->getFixture('full_readme.md')), + $this->callFilePutContent('README.md', $this->getFixture('full_readme_after_using_renovate.md')), + ); + $this ->artisan('init "My App"') ->expectsConfirmation('The application name is not in PascalCase, would you like to use MyApp') @@ -598,15 +608,8 @@ public function testRunWithoutAdminAndUsingTelescope() $this->callFileExists('.env', false), $this->callFileExists('.env.development'), - $this->callFileGetContent($this->generateResourcePath('md/readme/README.md'), $this->getReadmeTemplateContent('README.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES_AND_CONTACTS.md'), $this->getReadmeTemplateContent('RESOURCES_AND_CONTACTS.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES.md'), $this->getReadmeTemplateContent('RESOURCES.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/CONTACTS.md'), $this->getReadmeTemplateContent('CONTACTS.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/ENVIRONMENTS.md'), $this->getReadmeTemplateContent('ENVIRONMENTS.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/CREDENTIALS_AND_ACCESS.md'), $this->getReadmeTemplateContent('CREDENTIALS_AND_ACCESS.md')), $this->callFileGetContent(base_path('composer.json'), $this->getFixture('composer_with_pint_settings.json')), - $this->callFilePutContent('README.md', $this->getFixture('partial_readme_with_telescope.md')), $this->callFilePutContent(base_path('composer.json'), $this->getFixture('composer_with_pint_settings.json')), $this->callFilePutContent(base_path('/routes/web.php'), "\nAuth::routes();\n", FILE_APPEND), @@ -626,6 +629,18 @@ public function testRunWithoutAdminAndUsingTelescope() $this->callShellExec('php artisan migrate --ansi'), ); + $this->mockNativeFunction( + 'RonasIT\ProjectInitializator\Generators', + $this->callFileGetContent($this->generateResourcePath('md/readme/README.md'), $this->getReadmeTemplateContent('README.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES_AND_CONTACTS.md'), $this->getReadmeTemplateContent('RESOURCES_AND_CONTACTS.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES.md'), $this->getReadmeTemplateContent('RESOURCES.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/CONTACTS.md'), $this->getReadmeTemplateContent('CONTACTS.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/ENVIRONMENTS.md'), $this->getReadmeTemplateContent('ENVIRONMENTS.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/CREDENTIALS_AND_ACCESS.md'), $this->getReadmeTemplateContent('CREDENTIALS_AND_ACCESS.md')), + + $this->callFilePutContent('README.md', $this->getFixture('partial_readme_with_telescope.md')), + ); + $this ->artisan('init "My App"') ->expectsConfirmation('The application name is not in PascalCase, would you like to use MyApp') @@ -715,24 +730,12 @@ public function testRunWithClerkMobileAppWithPintInstalled(): void $this->callFileExists('.env', false), $this->callFileExists('.env.development'), - $this->callFileGetContent($this->generateResourcePath('md/readme/README.md'), $this->getReadmeTemplateContent('README.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES_AND_CONTACTS.md'), $this->getReadmeTemplateContent('RESOURCES_AND_CONTACTS.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES.md'), $this->getReadmeTemplateContent('RESOURCES.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/CONTACTS.md'), $this->getReadmeTemplateContent('CONTACTS.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/PREREQUISITES.md'), $this->getReadmeTemplateContent('PREREQUISITES.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/GETTING_STARTED.md'), $this->getReadmeTemplateContent('GETTING_STARTED.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/ENVIRONMENTS.md'), $this->getReadmeTemplateContent('ENVIRONMENTS.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/CREDENTIALS_AND_ACCESS.md'), $this->getReadmeTemplateContent('CREDENTIALS_AND_ACCESS.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/CLERK.md'), $this->getReadmeTemplateContent('CLERK.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/RENOVATE.md'), $this->getReadmeTemplateContent('RENOVATE.md')), $this->callFileGetContent(base_path('composer.json'), $this->getFixture('composer_with_pint_settings.json')), $this->callFilePutContent('database/migrations/2018_11_11_111111_users_add_clerk_id_field.php', $this->getFixture('users_add_clerk_id_field_migration.php')), $this->callFilePutContent('app/Support/Clerk/ClerkUserRepository.php', $this->getFixture('clerk_user_repository.php')), $this->callFilePutContent('database/migrations/2018_11_11_111111_admins_create_table.php', $this->getFixture('admins_table_migration.php')), - $this->callFilePutContent('README.md', $this->getFixture('default_readme_with_mobile_app.md')), $this->callFilePutContent('renovate.json', $this->getFixture('renovate.json')), - $this->callFilePutContent('README.md', $this->getFixture('default_readme_with_mobile_app_after_using_renovate.md')), $this->callFilePutContent(base_path('composer.json'), $this->getFixture('composer_with_pint_settings.json')), $this->callFilePutContent(base_path('/routes/web.php'), "\nAuth::routes();\n", FILE_APPEND), @@ -755,6 +758,23 @@ public function testRunWithClerkMobileAppWithPintInstalled(): void $this->callShellExec('php artisan migrate --ansi'), ); + $this->mockNativeFunction( + 'RonasIT\ProjectInitializator\Generators', + $this->callFileGetContent($this->generateResourcePath('md/readme/README.md'), $this->getReadmeTemplateContent('README.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES_AND_CONTACTS.md'), $this->getReadmeTemplateContent('RESOURCES_AND_CONTACTS.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES.md'), $this->getReadmeTemplateContent('RESOURCES.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/CONTACTS.md'), $this->getReadmeTemplateContent('CONTACTS.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/PREREQUISITES.md'), $this->getReadmeTemplateContent('PREREQUISITES.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/GETTING_STARTED.md'), $this->getReadmeTemplateContent('GETTING_STARTED.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/ENVIRONMENTS.md'), $this->getReadmeTemplateContent('ENVIRONMENTS.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/CREDENTIALS_AND_ACCESS.md'), $this->getReadmeTemplateContent('CREDENTIALS_AND_ACCESS.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/CLERK.md'), $this->getReadmeTemplateContent('CLERK.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/RENOVATE.md'), $this->getReadmeTemplateContent('RENOVATE.md')), + + $this->callFilePutContent('README.md', $this->getFixture('default_readme_with_mobile_app.md')), + $this->callFilePutContent('README.md', $this->getFixture('default_readme_with_mobile_app_after_using_renovate.md')), + ); + $this ->artisan('init "My App"') ->expectsConfirmation('The application name is not in PascalCase, would you like to use MyApp') From 3776f3123e65035354817338ddefa07ca1b03585 Mon Sep 17 00:00:00 2001 From: Artyom Osepyan Date: Fri, 17 Oct 2025 09:27:18 +0300 Subject: [PATCH 03/13] chore: small refactoring initCommand --- src/Commands/InitCommand.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Commands/InitCommand.php b/src/Commands/InitCommand.php index 3c5ab1e..4c8820e 100644 --- a/src/Commands/InitCommand.php +++ b/src/Commands/InitCommand.php @@ -83,12 +83,10 @@ class InitCommand extends Command implements Isolatable protected AppTypeEnum $appType; - protected $readmeGenerator; - - public function __construct(ReadmeGenerator $readme) - { + public function __construct( + protected ReadmeGenerator $readmeGenerator + ) { parent::__construct(); - $this->readmeGenerator = $readme; } public function handle(): void From 9b85a5dffb428bc46e7d8a2532de9a12ac1cc7a0 Mon Sep 17 00:00:00 2001 From: Artyom Osepyan Date: Fri, 17 Oct 2025 10:28:46 +0300 Subject: [PATCH 04/13] refactor: second stage - InitCommand cleanup Refs: https://github.com/RonasIT/laravel-project-initializator/issues/67 --- src/Commands/InitCommand.php | 528 ++++++++++++++++++----------------- 1 file changed, 270 insertions(+), 258 deletions(-) diff --git a/src/Commands/InitCommand.php b/src/Commands/InitCommand.php index 3c5ab1e..56f20c0 100644 --- a/src/Commands/InitCommand.php +++ b/src/Commands/InitCommand.php @@ -18,6 +18,9 @@ class InitCommand extends Command implements Isolatable { + protected $signature = 'init {application-name : The application name }'; + protected $description = 'Initialize required project parameters to run DEV environment'; + public const array RESOURCES_ITEMS = [ 'issue_tracker' => 'Issue Tracker', 'figma' => 'Figma', @@ -42,20 +45,10 @@ class InitCommand extends Command implements Isolatable 'nova', ]; - protected $signature = 'init {application-name : The application name }'; - - protected $description = 'Initialize required project parameters to run DEV environment'; - - protected string $codeOwnerEmail; - protected array $resources = []; protected array $adminCredentials = []; - protected AuthTypeEnum $authType; - - protected string $appUrl; - protected array $emptyValuesList = []; protected array $shellCommands = [ @@ -72,88 +65,181 @@ class InitCommand extends Command implements Isolatable ]; protected bool $shouldUninstallPackage = false; + protected bool $shouldGenerateReadme; protected string $appName; - - protected string $dbConnection = 'pgsql'; - protected string $dbHost = 'pgsql'; - protected string $dbPort = '5432'; - protected string $dbName = 'postgres'; - protected string $dbUserName = 'postgres'; - + protected string $kebabName; + protected string $appUrl; protected AppTypeEnum $appType; + protected AuthTypeEnum $authType; + protected string $codeOwnerEmail; - protected $readmeGenerator; + protected string $envFile; + protected array $envConfig = [ + 'dbConnection' => 'pgsql', + 'dbHost' => 'pgsql', + 'dbPort' => '5432', + 'dbName' => 'postgres', + 'dbUserName' => 'postgres', + ]; - public function __construct(ReadmeGenerator $readme) - { + public function __construct( + protected ReadmeGenerator $readmeGenerator + ) { parent::__construct(); - $this->readmeGenerator = $readme; } public function handle(): void { $this->prepareAppName(); - $kebabName = Str::kebab($this->appName); - $this->codeOwnerEmail = $this->validateInput( method: fn () => $this->ask('Please specify a Code Owner/Team Lead\'s email'), field: 'email of code owner / team lead', rules: 'required|email', ); - $this->appUrl = $this->ask('Please enter an application URL', "https://api.dev.{$kebabName}.com"); + $this->appUrl = $this->ask('Please enter an application URL', "https://api.dev.{$this->kebabName}.com"); + + $this->updateEnvFile(); + + $this->info('Project initialized successfully!'); + + $this->appType = AppTypeEnum::from( + $this->choice( + question: 'What type of application will your API serve?', + choices: AppTypeEnum::values(), + default: AppTypeEnum::Multiplatform->value, + ), + ); + + $this->authType = AuthTypeEnum::from($this->choice( + question: 'Please choose the authentication type', + choices: AuthTypeEnum::values(), + default: AuthTypeEnum::None->value, + )); + + $this->configureClerk(); + + if ($this->confirm('Do you want to generate an admin user?', true)) { + $this->createAdminUser(); + } + + if ($this->shouldGenerateReadme = $this->confirm('Do you want to generate a README file?', true)) { + $this->generateReadme(); + } + + if ($this->confirm('Would you use Renovate dependabot?', true)) { + $this->saveRenovateJSON(); + + if ($this->shouldGenerateReadme) { + $this->readmeGenerator->fillRenovate(); + + $this->readmeGenerator->saveReadme(); + } + } + + if (!class_exists(\Laravel\Telescope\TelescopeServiceProvider::class)) { + array_push( + $this->shellCommands, + 'composer require ronasit/laravel-telescope-extension', + 'php artisan telescope:install', + ); + } + + if ($this->confirm('Do you want to install media package?')) { + $this->shellCommands[] = 'composer require ronasit/laravel-media'; + } + + if ($this->confirm('Do you want to uninstall project-initializator package?', true)) { + $this->shouldUninstallPackage = true; + } + + $this->setupComposerHooks(); + + $this->changeMiddlewareForTelescopeAuthorization(); + + $this->setAutoDocContactEmail($this->codeOwnerEmail); + + foreach ($this->shellCommands as $shellCommand) { + shell_exec("{$shellCommand} --ansi"); + } + + $this->publishWebLogin(); + + if ($this->shouldUninstallPackage) { + shell_exec('composer remove --dev ronasit/laravel-project-initializator --ansi'); + } + + $this->runMigrations(); + } + + protected function validateInput(callable $method, string $field, string|array $rules): string + { + $value = $method(); + + $validator = Validator::make([$field => $value], [$field => $rules]); + + if ($validator->fails()) { + $this->warn($validator->errors()->first()); + + $value = $this->validateInput($method, $field, $rules); + } + + return $value; + } + + protected function prepareAppName(): void + { + $this->appName = $this->argument('application-name'); + + $pascalCaseAppName = ucfirst(Str::camel($this->appName)); + + if ($this->appName !== $pascalCaseAppName && $this->confirm("The application name is not in PascalCase, would you like to use {$pascalCaseAppName}", true)) { + $this->appName = $pascalCaseAppName; + } - $envFile = (file_exists('.env')) ? '.env' : '.env.example'; + $this->kebabName = Str::kebab($this->appName); + } + + protected function updateEnvFile(): void + { + $this->envFile = (file_exists('.env')) ? '.env' : '.env.example'; $envConfig = [ 'APP_NAME' => $this->appName, - 'DB_CONNECTION' => $this->dbConnection, - 'DB_HOST' => $this->dbHost, - 'DB_PORT' => $this->dbPort, - 'DB_DATABASE' => $this->dbName, - 'DB_USERNAME' => $this->dbUserName, + 'DB_CONNECTION' => $this->envConfig['dbConnection'], + 'DB_HOST' => $this->envConfig['dbHost'], + 'DB_PORT' => $this->envConfig['dbPort'], + 'DB_DATABASE' => $this->envConfig['dbName'], + 'DB_USERNAME' => $this->envConfig['dbUserName'], 'DB_PASSWORD' => '', ]; - $this->updateEnvFile($envFile, $envConfig); + $this->writeEnvFile($this->envFile, $envConfig); if (!file_exists('.env.development')) { copy('.env.example', '.env.development'); } - $this->updateEnvFile('.env.development', [ + $this->writeEnvFile('.env.development', [ 'APP_NAME' => $this->appName, 'APP_URL' => $this->appUrl, 'APP_MAINTENANCE_DRIVER' => 'cache', 'CACHE_STORE' => 'redis', 'QUEUE_CONNECTION' => 'redis', 'SESSION_DRIVER' => 'redis', - 'DB_CONNECTION' => $this->dbConnection, + 'DB_CONNECTION' => $this->envConfig['dbConnection'], 'DB_HOST' => '', 'DB_PORT' => '', 'DB_DATABASE' => '', 'DB_USERNAME' => '', 'DB_PASSWORD' => '', ]); + } - $this->info('Project initialized successfully!'); - - $this->appType = AppTypeEnum::from( - $this->choice( - question: 'What type of application will your API serve?', - choices: AppTypeEnum::values(), - default: AppTypeEnum::Multiplatform->value, - ), - ); - - $this->authType = AuthTypeEnum::from($this->choice( - question: 'Please choose the authentication type', - choices: AuthTypeEnum::values(), - default: AuthTypeEnum::None->value, - )); - + protected function configureClerk(): void + { if ($this->authType === AuthTypeEnum::Clerk) { $this->enableClerk(); @@ -168,20 +254,104 @@ public function handle(): void $data['CLERK_ALLOWED_ORIGINS'] = ''; } - $this->updateEnvFile('.env.development', $data); - $this->updateEnvFile($envFile, $data); + $this->writeEnvFile('.env.development', $data); + $this->writeEnvFile($this->envFile, $data); - if ($envFile !== '.env.example') { - $this->updateEnvFile('.env.example', $data); + if ($this->envFile !== '.env.example') { + $this->writeEnvFile('.env.example', $data); } } + } - if ($this->confirm('Do you want to generate an admin user?', true)) { - $this->createAdminUser($kebabName); + protected function writeEnvFile(string $fileName, array $data): void + { + $env = EnvFile::open($fileName); + + // TODO: After updating wintercms/laravel-config-writer, remove the key comparison check and keep only $env->addEmptyLine(); + $envKeys = array_column($env->getAst(), 'match'); + $dataKeys = array_keys($data); + + $hasMissingKeys = count(array_intersect($dataKeys, $envKeys)) !== count($dataKeys); + + if ($hasMissingKeys) { + $env->addEmptyLine(); + } + + $env->set($data); + + $env->write(); + } + + protected function enableClerk(): void + { + array_push( + $this->shellCommands, + 'composer require ronasit/laravel-clerk', + 'php artisan laravel-clerk:install', + ); + + $this->publishMigration( + view: view('initializator::users_add_clerk_id_field'), + migrationName: 'users_add_clerk_id_field', + ); + + $this->publishClass( + template: view('initializator::clerk_user_repository'), + fileName: 'ClerkUserRepository', + filePath: 'app/Support/Clerk', + ); + } + + protected function createAdminUser(): void + { + $defaultPassword = substr(md5(uniqid()), 0, 8); + + $this->adminCredentials = [ + 'email' => $this->ask('Please enter an admin email', "admin@{$this->kebabName}.com"), + 'password' => $this->ask('Please enter an admin password', $defaultPassword), + ]; + + if ($this->authType === AuthTypeEnum::Clerk) { + $this->publishMigration( + view: view('initializator::admins_create_table')->with($this->adminCredentials), + migrationName: 'admins_create_table', + ); + } else { + $this->adminCredentials['name'] = $this->ask('Please enter an admin name', 'Admin'); + $this->adminCredentials['role_id'] = $this->ask('Please enter an admin role id', RoleEnum::Admin->value); + + $this->publishMigration( + view: view('initializator::add_default_user')->with($this->adminCredentials), + migrationName: 'add_default_user', + ); } + } + + protected function publishMigration(View $view, string $migrationName): void + { + $time = Carbon::now()->format('Y_m_d_His'); + + $migrationName = "{$time}_{$migrationName}"; + + $this->publishClass($view, $migrationName, 'database/migrations'); + } - if ($shouldGenerateReadme = $this->confirm('Do you want to generate a README file?', true)) { - $this->readmeGenerator->fillReadme($this->appName, $this->appType->value); + protected function publishClass(View $template, string $fileName, string $filePath): void + { + $fileName = "{$fileName}.php"; + + if (!is_dir($filePath)) { + mkdir($filePath, 0777, true); + } + + $data = $template->render(); + + file_put_contents("{$filePath}/{$fileName}", "readmeGenerator->fillReadme($this->appName, $this->appType->value); if ($this->confirm('Do you need a `Resources & Contacts` part?', true)) { $this->readmeGenerator->fillResourcesAndContacts(); @@ -202,7 +372,7 @@ public function handle(): void } if ($this->confirm('Do you need a `Credentials and Access` part?', true)) { - $this->fillCredentialsAndAccess($kebabName); + $this->fillCredentialsAndAccess(); if ($this->authType === AuthTypeEnum::Clerk) { $this->readmeGenerator->fillClerkAuthType(); @@ -220,51 +390,6 @@ public function handle(): void $this->warn("- {$value}"); } } - } - - if ($this->confirm('Would you use Renovate dependabot?', true)) { - $this->saveRenovateJSON(); - - if ($shouldGenerateReadme) { - $this->readmeGenerator->fillRenovate(); - - $this->readmeGenerator->saveReadme(); - } - } - - if (!class_exists(\Laravel\Telescope\TelescopeServiceProvider::class)) { - array_push( - $this->shellCommands, - 'composer require ronasit/laravel-telescope-extension', - 'php artisan telescope:install', - ); - } - - if ($this->confirm('Do you want to install media package?')) { - $this->shellCommands[] = 'composer require ronasit/laravel-media'; - } - - if ($this->confirm('Do you want to uninstall project-initializator package?', true)) { - $this->shouldUninstallPackage = true; - } - - $this->setupComposerHooks(); - - $this->changeMiddlewareForTelescopeAuthorization(); - - $this->setAutoDocContactEmail($this->codeOwnerEmail); - - foreach ($this->shellCommands as $shellCommand) { - shell_exec("{$shellCommand} --ansi"); - } - - $this->publishWebLogin(); - - if ($this->shouldUninstallPackage) { - shell_exec('composer remove --dev ronasit/laravel-project-initializator --ansi'); - } - - $this->runMigrations(); } protected function fillResources(): void @@ -332,7 +457,7 @@ protected function fillGettingStarted(): void $this->readmeGenerator->updateReadmeFile($filePart); } - protected function fillCredentialsAndAccess(string $kebabName): void + protected function fillCredentialsAndAccess(): void { $filePart = $this->readmeGenerator->loadReadmePart('CREDENTIALS_AND_ACCESS.md'); @@ -356,7 +481,7 @@ protected function fillCredentialsAndAccess(string $kebabName): void } else { $defaultPassword = substr(md5(uniqid()), 0, 8); - $email = $this->ask("Please enter a {$title}'s admin email", "admin@{$kebabName}.com"); + $email = $this->ask("Please enter a {$title}'s admin email", "admin@{$this->kebabName}.com"); $password = $this->ask("Please enter a {$title}'s admin password", $defaultPassword); } @@ -368,6 +493,23 @@ protected function fillCredentialsAndAccess(string $kebabName): void $this->readmeGenerator->updateReadmeFile($filePart); } + protected function saveRenovateJSON(): void + { + $reviewer = $this->validateInput( + method: fn () => $this->ask('Please type username of the project reviewer', Str::before($this->codeOwnerEmail, '@')), + field: 'username of the project reviewer', + rules: 'required|alpha_dash', + ); + + $data = [ + '$schema' => 'https://docs.renovatebot.com/renovate-schema.json', + 'extends' => ['config:recommended'], + 'enabledManagers' => ['composer'], + 'assignees' => [$reviewer], + ]; + + file_put_contents('renovate.json', json_encode($data, JSON_PRETTY_PRINT)); + } protected function setupComposerHooks(): void { @@ -398,160 +540,26 @@ protected function addArrayItemIfMissing(array &$data, string $path, string $val } } - protected function setAutoDocContactEmail(string $email): void + protected function changeMiddlewareForTelescopeAuthorization(): void { - $config = ArrayFile::open(base_path('config/auto-doc.php')); - - $config->set('info.contact.email', $email); - - $config->write(); - } + $config = ArrayFile::open(base_path('config/telescope.php')); - protected function runMigrations(): void - { - config([ - 'database.default' => $this->dbConnection, - 'database.connections.pgsql' => [ - 'driver' => $this->dbConnection, - 'host' => $this->dbHost, - 'port' => $this->dbPort, - 'database' => $this->dbName, - 'username' => $this->dbUserName, - 'password' => '', - ], + // TODO: add Authorize::class middleware after inplementing an ability to modify functions in the https://github.com/RonasIT/larabuilder package + $config->set('middleware', [ + 'web', + 'auth:web', ]); - shell_exec('php artisan migrate --ansi'); - } - - protected function createAdminUser(string $kebabName): void - { - $defaultPassword = substr(md5(uniqid()), 0, 8); - - $this->adminCredentials = [ - 'email' => $this->ask('Please enter an admin email', "admin@{$kebabName}.com"), - 'password' => $this->ask('Please enter an admin password', $defaultPassword), - ]; - - if ($this->authType === AuthTypeEnum::Clerk) { - $this->publishMigration( - view: view('initializator::admins_create_table')->with($this->adminCredentials), - migrationName: 'admins_create_table', - ); - } else { - $this->adminCredentials['name'] = $this->ask('Please enter an admin name', 'Admin'); - $this->adminCredentials['role_id'] = $this->ask('Please enter an admin role id', RoleEnum::Admin->value); - - $this->publishMigration( - view: view('initializator::add_default_user')->with($this->adminCredentials), - migrationName: 'add_default_user', - ); - } - } - - protected function publishClass(View $template, string $fileName, string $filePath): void - { - $fileName = "{$fileName}.php"; - - if (!is_dir($filePath)) { - mkdir($filePath, 0777, true); - } - - $data = $template->render(); - - file_put_contents("{$filePath}/{$fileName}", "format('Y_m_d_His'); - - $migrationName = "{$time}_{$migrationName}"; - - $this->publishClass($view, $migrationName, 'database/migrations'); - } - - protected function updateEnvFile(string $fileName, array $data): void - { - $env = EnvFile::open($fileName); - - // TODO: After updating wintercms/laravel-config-writer, remove the key comparison check and keep only $env->addEmptyLine(); - $envKeys = array_column($env->getAst(), 'match'); - $dataKeys = array_keys($data); - - $hasMissingKeys = count(array_intersect($dataKeys, $envKeys)) !== count($dataKeys); - - if ($hasMissingKeys) { - $env->addEmptyLine(); - } - - $env->set($data); - - $env->write(); - } - - protected function prepareAppName(): void - { - $this->appName = $this->argument('application-name'); - - $pascalCaseAppName = ucfirst(Str::camel($this->appName)); - - if ($this->appName !== $pascalCaseAppName && $this->confirm("The application name is not in PascalCase, would you like to use {$pascalCaseAppName}", true)) { - $this->appName = $pascalCaseAppName; - } - } - - protected function saveRenovateJSON(): void - { - $reviewer = $this->validateInput( - method: fn () => $this->ask('Please type username of the project reviewer', Str::before($this->codeOwnerEmail, '@')), - field: 'username of the project reviewer', - rules: 'required|alpha_dash', - ); - - $data = [ - '$schema' => 'https://docs.renovatebot.com/renovate-schema.json', - 'extends' => ['config:recommended'], - 'enabledManagers' => ['composer'], - 'assignees' => [$reviewer], - ]; - - file_put_contents('renovate.json', json_encode($data, JSON_PRETTY_PRINT)); - } - - protected function validateInput(callable $method, string $field, string|array $rules): string - { - $value = $method(); - - $validator = Validator::make([$field => $value], [$field => $rules]); - - if ($validator->fails()) { - $this->warn($validator->errors()->first()); - - $value = $this->validateInput($method, $field, $rules); - } - - return $value; + $config->write(); } - protected function enableClerk(): void + protected function setAutoDocContactEmail(string $email): void { - array_push( - $this->shellCommands, - 'composer require ronasit/laravel-clerk', - 'php artisan laravel-clerk:install', - ); + $config = ArrayFile::open(base_path('config/auto-doc.php')); - $this->publishMigration( - view: view('initializator::users_add_clerk_id_field'), - migrationName: 'users_add_clerk_id_field', - ); + $config->set('info.contact.email', $email); - $this->publishClass( - template: view('initializator::clerk_user_repository'), - fileName: 'ClerkUserRepository', - filePath: 'app/Support/Clerk', - ); + $config->write(); } protected function publishWebLogin(): void @@ -561,16 +569,20 @@ protected function publishWebLogin(): void file_put_contents(base_path('routes/web.php'), "\nAuth::routes();\n", FILE_APPEND); } - protected function changeMiddlewareForTelescopeAuthorization(): void + protected function runMigrations(): void { - $config = ArrayFile::open(base_path('config/telescope.php')); - - // TODO: add Authorize::class middleware after inplementing an ability to modify functions in the https://github.com/RonasIT/larabuilder package - $config->set('middleware', [ - 'web', - 'auth:web', + config([ + 'database.default' => $this->envConfig['dbConnection'], + 'database.connections.pgsql' => [ + 'driver' => $this->envConfig['dbConnection'], + 'host' => $this->envConfig['dbHost'], + 'port' => $this->envConfig['dbPort'], + 'database' => $this->envConfig['dbName'], + 'username' => $this->envConfig['dbUserName'], + 'password' => '', + ], ]); - $config->write(); + shell_exec('php artisan migrate --ansi'); } } From c8f6e00ff412f6a54ca1235f087d530cac709fad Mon Sep 17 00:00:00 2001 From: DenTray Date: Thu, 23 Oct 2025 09:58:35 +0600 Subject: [PATCH 05/13] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Commands/InitCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/InitCommand.php b/src/Commands/InitCommand.php index 56f20c0..f4c37ef 100644 --- a/src/Commands/InitCommand.php +++ b/src/Commands/InitCommand.php @@ -202,7 +202,7 @@ protected function prepareAppName(): void $this->kebabName = Str::kebab($this->appName); } - protected function updateEnvFile(): void + protected function updateEnvFile(): void { $this->envFile = (file_exists('.env')) ? '.env' : '.env.example'; From 8d9579c2ddcda57a6c5ef633eeef3805f5aec075 Mon Sep 17 00:00:00 2001 From: DenTray Date: Thu, 23 Oct 2025 10:01:52 +0600 Subject: [PATCH 06/13] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/InitCommandTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/InitCommandTest.php b/tests/InitCommandTest.php index 6070466..ca3453a 100644 --- a/tests/InitCommandTest.php +++ b/tests/InitCommandTest.php @@ -389,7 +389,6 @@ public function testRunWithAdminAndPartialReadmeCreation() $this->callFileGetContent($this->generateResourcePath('md/readme/CONTACTS.md'), $this->getReadmeTemplateContent('CONTACTS.md')), $this->callFileGetContent($this->generateResourcePath('md/readme/ENVIRONMENTS.md'), $this->getReadmeTemplateContent('ENVIRONMENTS.md')), $this->callFileGetContent($this->generateResourcePath('md/readme/CREDENTIALS_AND_ACCESS.md'), $this->getReadmeTemplateContent('CREDENTIALS_AND_ACCESS.md')), - $this->callFilePutContent('README.md', $this->getFixture('partial_readme.md')), ); From fb4793e2f4519cac3d057ce03d1b113a1ab69675 Mon Sep 17 00:00:00 2001 From: DenTray Date: Thu, 23 Oct 2025 10:02:21 +0600 Subject: [PATCH 07/13] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/InitCommandTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/InitCommandTest.php b/tests/InitCommandTest.php index ca3453a..7c718d8 100644 --- a/tests/InitCommandTest.php +++ b/tests/InitCommandTest.php @@ -636,7 +636,6 @@ public function testRunWithoutAdminAndUsingTelescope() $this->callFileGetContent($this->generateResourcePath('md/readme/CONTACTS.md'), $this->getReadmeTemplateContent('CONTACTS.md')), $this->callFileGetContent($this->generateResourcePath('md/readme/ENVIRONMENTS.md'), $this->getReadmeTemplateContent('ENVIRONMENTS.md')), $this->callFileGetContent($this->generateResourcePath('md/readme/CREDENTIALS_AND_ACCESS.md'), $this->getReadmeTemplateContent('CREDENTIALS_AND_ACCESS.md')), - $this->callFilePutContent('README.md', $this->getFixture('partial_readme_with_telescope.md')), ); From 44b4c689088b9a730590d4b7b4770d799a05ac33 Mon Sep 17 00:00:00 2001 From: DenTray Date: Thu, 23 Oct 2025 10:02:41 +0600 Subject: [PATCH 08/13] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/InitCommandTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/InitCommandTest.php b/tests/InitCommandTest.php index 7c718d8..68fb601 100644 --- a/tests/InitCommandTest.php +++ b/tests/InitCommandTest.php @@ -512,7 +512,6 @@ public function testRunWithAdminAndFullReadmeCreationAndRemovingInitializatorIns $this->callFileGetContent($this->generateResourcePath('md/readme/ENVIRONMENTS.md'), $this->getReadmeTemplateContent('ENVIRONMENTS.md')), $this->callFileGetContent($this->generateResourcePath('md/readme/CREDENTIALS_AND_ACCESS.md'), $this->getReadmeTemplateContent('CREDENTIALS_AND_ACCESS.md')), $this->callFileGetContent($this->generateResourcePath('md/readme/RENOVATE.md'), $this->getReadmeTemplateContent('RENOVATE.md')), - $this->callFilePutContent('README.md', $this->getFixture('full_readme.md')), $this->callFilePutContent('README.md', $this->getFixture('full_readme_after_using_renovate.md')), ); From f2689fe72e5479199227467566053ef36af22ed0 Mon Sep 17 00:00:00 2001 From: DenTray Date: Thu, 23 Oct 2025 10:02:58 +0600 Subject: [PATCH 09/13] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/InitCommandTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/InitCommandTest.php b/tests/InitCommandTest.php index 68fb601..3b1a97b 100644 --- a/tests/InitCommandTest.php +++ b/tests/InitCommandTest.php @@ -767,7 +767,6 @@ public function testRunWithClerkMobileAppWithPintInstalled(): void $this->callFileGetContent($this->generateResourcePath('md/readme/CREDENTIALS_AND_ACCESS.md'), $this->getReadmeTemplateContent('CREDENTIALS_AND_ACCESS.md')), $this->callFileGetContent($this->generateResourcePath('md/readme/CLERK.md'), $this->getReadmeTemplateContent('CLERK.md')), $this->callFileGetContent($this->generateResourcePath('md/readme/RENOVATE.md'), $this->getReadmeTemplateContent('RENOVATE.md')), - $this->callFilePutContent('README.md', $this->getFixture('default_readme_with_mobile_app.md')), $this->callFilePutContent('README.md', $this->getFixture('default_readme_with_mobile_app_after_using_renovate.md')), ); From fc6c71e1898c4c8426cf301f7165ca244d4a0d2a Mon Sep 17 00:00:00 2001 From: Artyom Osepyan Date: Thu, 23 Oct 2025 10:49:29 +0300 Subject: [PATCH 10/13] fix: resolve conflicts after merge --- src/Commands/InitCommand.php | 15 +++++++-------- tests/InitCommandTest.php | 25 +++++++++++++------------ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Commands/InitCommand.php b/src/Commands/InitCommand.php index 936f108..f452238 100644 --- a/src/Commands/InitCommand.php +++ b/src/Commands/InitCommand.php @@ -353,24 +353,23 @@ protected function fillCredentialsAndAccess(string $kebabName): void } if (!empty($this->adminCredentials) && $this->confirm("Is {$title}'s admin the same as default one?", true)) { - $email = $this->adminCredentials['email']; - $password = $this->adminCredentials['password']; + $adminCredentials = $this->adminCredentials; } else { - $defaultPassword = substr(md5(uniqid()), 0, 8); + if ($this->authType === AuthTypeEnum::Clerk && !$this->isMigrationExists('admins_create_table')) { + $this->publishAdminsTableMigration(); + } - $email = $this->ask("Please enter a {$title}'s admin email", "admin@{$kebabName}.com"); - $password = $this->ask("Please enter a {$title}'s admin password", $defaultPassword); + $adminCredentials = $this->createAdminUser($kebabName, $key, $title); } - $this->readmeGenerator->setReadmeValue($filePart, "{$key}_email", $email); - $this->readmeGenerator->setReadmeValue($filePart, "{$key}_password", $password); + $this->readmeGenerator->setReadmeValue($filePart, "{$key}_email", $adminCredentials['email']); + $this->readmeGenerator->setReadmeValue($filePart, "{$key}_password", $adminCredentials['password']); $this->readmeGenerator->removeTag($filePart, "{$key}_credentials"); } $this->readmeGenerator->updateReadmeFile($filePart); } - protected function setupComposerHooks(): void { $path = base_path('composer.json'); diff --git a/tests/InitCommandTest.php b/tests/InitCommandTest.php index 499df65..b8e9f6d 100644 --- a/tests/InitCommandTest.php +++ b/tests/InitCommandTest.php @@ -229,7 +229,6 @@ public function testRunWithAdminAndDefaultReadmeCreation() $this->callFilePutContent('database/migrations/2018_11_11_111111_add_default_admin.php', $this->getFixture('admins_add_default_admin.php')), $this->callGlob(base_path('database/migrations/*_admins_create_table.php'), [base_path('database/migrations/2018_11_11_111111_admins_create_table.php')]), $this->callFilePutContent('database/migrations/2018_11_11_111111_add_nova_admin.php', $this->getFixture('admins_add_nova_admin_migration.php')), - $this->callFilePutContent('README.md', $this->getFixture('default_readme.md')), $this->callFilePutContent('renovate.json', $this->getFixture('renovate.json')), $this->callFilePutContent(base_path('composer.json'), $this->getFixture('composer_with_pint_settings.json')), $this->callFilePutContent(base_path('/routes/web.php'), "\nAuth::routes();\n", FILE_APPEND), @@ -486,7 +485,6 @@ public function testRunWithAdminAndFullReadmeCreationAndRemovingInitializatorIns $this->callFilePutContent('database/migrations/2018_11_11_111111_add_default_admin.php', $this->getFixture('migration.php')), $this->callFilePutContent('database/migrations/2018_11_11_111111_add_nova_admin.php', $this->getFixture('nova_users_table_migration.php')), - $this->callFilePutContent('README.md', $this->getFixture('full_readme.md')), $this->callFilePutContent('renovate.json', $this->getFixture('renovate.json')), $this->callFilePutContent(base_path('composer.json'), $this->getFixture('composer_with_pint_settings.json')), $this->callFilePutContent(base_path('/routes/web.php'), "\nAuth::routes();\n", FILE_APPEND), @@ -622,7 +620,6 @@ public function testRunWithoutAdminAndUsingTelescope() $this->callFilePutContent('database/migrations/2018_11_11_111111_add_telescope_admin.php', $this->getFixture('telescope_users_table_migration.php')), $this->callFilePutContent('database/migrations/2018_11_11_111111_add_nova_admin.php', $this->getFixture('nova_users_table_migration.php')), - $this->callFilePutContent('README.md', $this->getFixture('partial_readme_with_telescope.md')), $this->callFilePutContent(base_path('composer.json'), $this->getFixture('composer_with_pint_settings.json')), $this->callFilePutContent(base_path('/routes/web.php'), "\nAuth::routes();\n", FILE_APPEND), @@ -753,7 +750,6 @@ public function testRunWithClerkMobileAppWithPintInstalled(): void $this->callFilePutContent('app/Support/Clerk/ClerkUserRepository.php', $this->getFixture('clerk_user_repository.php')), $this->callFilePutContent('database/migrations/2018_11_11_111111_admins_create_table.php', $this->getFixture('admins_table_migration.php')), $this->callFilePutContent('database/migrations/2018_11_11_111111_add_default_admin.php', $this->getFixture('admins_add_default_admin.php')), - $this->callFilePutContent('README.md', $this->getFixture('default_readme_with_mobile_app.md')), $this->callFilePutContent('renovate.json', $this->getFixture('renovate.json')), $this->callFilePutContent(base_path('composer.json'), $this->getFixture('composer_with_pint_settings.json')), $this->callFilePutContent(base_path('/routes/web.php'), "\nAuth::routes();\n", FILE_APPEND), @@ -889,13 +885,7 @@ public function testRunWithClerkAdditionalAdminsWithoutDefaultAdmin(): void $this->callFileExists('.env', false), $this->callFileExists('.env.development'), - $this->callFileGetContent($this->generateResourcePath('md/readme/README.md'), $this->getReadmeTemplateContent('README.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES_AND_CONTACTS.md'), $this->getReadmeTemplateContent('RESOURCES_AND_CONTACTS.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES.md'), $this->getReadmeTemplateContent('RESOURCES.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/CONTACTS.md'), $this->getReadmeTemplateContent('CONTACTS.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/CREDENTIALS_AND_ACCESS.md'), $this->getReadmeTemplateContent('CREDENTIALS_AND_ACCESS.md')), - $this->callFileGetContent($this->generateResourcePath('md/readme/CLERK.md'), $this->getReadmeTemplateContent('CLERK.md')), - $this->callFileGetContent(base_path('composer.json'), $this->getFixture('composer_with_pint_settings.json')), + $this->callFileGetContent(base_path('composer.json'), $this->getFixture('composer_with_pint_settings.json')), $this->callFilePutContent('database/migrations/2018_11_11_111111_users_add_clerk_id_field.php', $this->getFixture('users_add_clerk_id_field_migration.php')), $this->callFilePutContent('app/Support/Clerk/ClerkUserRepository.php', $this->getFixture('clerk_user_repository.php')), @@ -904,7 +894,6 @@ public function testRunWithClerkAdditionalAdminsWithoutDefaultAdmin(): void $this->callFilePutContent('database/migrations/2018_11_11_111111_add_telescope_admin.php', $this->getFixture('admins_add_telescope_admin_migration.php')), $this->callGlob(base_path('database/migrations/*_admins_create_table.php'), [base_path('database/migrations/2018_11_11_111111_admins_create_table.php')]), $this->callFilePutContent('database/migrations/2018_11_11_111111_add_nova_admin.php', $this->getFixture('admins_add_nova_admin_migration.php')), - $this->callFilePutContent('README.md', $this->getFixture('partial_readme_clerk_with_credentials.md')), $this->callFilePutContent(base_path('composer.json'), $this->getFixture('composer_with_pint_settings.json')), $this->callFilePutContent(base_path('/routes/web.php'), "\nAuth::routes();\n", FILE_APPEND), @@ -926,6 +915,18 @@ public function testRunWithClerkAdditionalAdminsWithoutDefaultAdmin(): void $this->callShellExec('php artisan migrate --ansi'), ); + $this->mockNativeFunction( + 'RonasIT\ProjectInitializator\Generators', + $this->callFileGetContent($this->generateResourcePath('md/readme/README.md'), $this->getReadmeTemplateContent('README.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES_AND_CONTACTS.md'), $this->getReadmeTemplateContent('RESOURCES_AND_CONTACTS.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/RESOURCES.md'), $this->getReadmeTemplateContent('RESOURCES.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/CONTACTS.md'), $this->getReadmeTemplateContent('CONTACTS.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/CREDENTIALS_AND_ACCESS.md'), $this->getReadmeTemplateContent('CREDENTIALS_AND_ACCESS.md')), + $this->callFileGetContent($this->generateResourcePath('md/readme/CLERK.md'), $this->getReadmeTemplateContent('CLERK.md')), + + $this->callFilePutContent('README.md', $this->getFixture('partial_readme_clerk_with_credentials.md')), + ); + $this ->artisan('init "My App"') ->expectsConfirmation('The application name is not in PascalCase, would you like to use MyApp') From 903c4b828b712a438c97256dca55bd2232034c7e Mon Sep 17 00:00:00 2001 From: Artyom Osepyan Date: Thu, 23 Oct 2025 10:52:58 +0300 Subject: [PATCH 11/13] fix: remarks from reviewer --- pint.json | 43 ++++++++++++++++++++++++++++++ src/Commands/InitCommand.php | 6 ++--- src/Generators/ReadmeGenerator.php | 7 +++-- 3 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 pint.json diff --git a/pint.json b/pint.json new file mode 100644 index 0000000..6dcb0eb --- /dev/null +++ b/pint.json @@ -0,0 +1,43 @@ +{ + "preset": "laravel", + "rules": { + "not_operator_with_successor_space": false, + "php_unit_set_up_tear_down_visibility": false, + "php_unit_method_casing": { + "case": "camel_case" + }, + "concat_space": { + "spacing": "one" + }, + "class_attributes_separation": { + "elements": { + "method": "one" + } + }, + "array_syntax": { + "syntax": "short" + }, + "list_syntax": { + "syntax": "long" + }, + "braces": { + "position_after_functions_and_oop_constructs": "next" + }, + "single_line_empty_body": false, + "trailing_comma_in_multiline": { + "elements": [ + "arguments", + "arrays", + "match", + "parameters" + ] + }, + "new_with_parentheses": { + "anonymous_class": false, + "named_class": true + }, + "phpdoc_separation": { + "skip_unlisted_annotations": false + } + } +} \ No newline at end of file diff --git a/src/Commands/InitCommand.php b/src/Commands/InitCommand.php index f452238..b662080 100644 --- a/src/Commands/InitCommand.php +++ b/src/Commands/InitCommand.php @@ -183,7 +183,7 @@ public function handle(): void } if ($shouldGenerateReadme = $this->confirm('Do you want to generate a README file?', true)) { - $this->readmeGenerator->fillReadme($this->appName, $this->appType->value); + $this->readmeGenerator->generate($this->appName, $this->appType->value); if ($this->confirm('Do you need a `Resources & Contacts` part?', true)) { $this->readmeGenerator->fillResourcesAndContacts(); @@ -211,7 +211,7 @@ public function handle(): void } } - $this->readmeGenerator->saveReadme(); + $this->readmeGenerator->save(); $this->info('README generated successfully!'); @@ -230,7 +230,7 @@ public function handle(): void if ($shouldGenerateReadme) { $this->readmeGenerator->fillRenovate(); - $this->readmeGenerator->saveReadme(); + $this->readmeGenerator->save(); } } diff --git a/src/Generators/ReadmeGenerator.php b/src/Generators/ReadmeGenerator.php index f4d64f9..e065e8d 100644 --- a/src/Generators/ReadmeGenerator.php +++ b/src/Generators/ReadmeGenerator.php @@ -8,7 +8,7 @@ class ReadmeGenerator protected string $readmeContent = ''; - public function fillReadme(string $appName, string $appType): void + public function generate(string $appName, string $appType): void { $file = $this->loadReadmePart('README.md'); @@ -33,7 +33,6 @@ public function fillPrerequisites(): void $this->updateReadmeFile($filePart); } - public function fillEnvironments(string $appUrl): void { $filePart = $this->loadReadmePart('ENVIRONMENTS.md'); @@ -51,7 +50,7 @@ public function fillClerkAuthType(): void public function loadReadmePart(string $fileName): string { - $file = base_path(DIRECTORY_SEPARATOR . self::TEMPLATES_PATH . DIRECTORY_SEPARATOR . $fileName); + $file = base_path(self::TEMPLATES_PATH . DIRECTORY_SEPARATOR . $fileName); return file_get_contents($file); } @@ -77,7 +76,7 @@ public function setReadmeValue(string &$file, string $key, string $value = ''): $file = str_replace(":{$key}", $value, $file); } - public function saveReadme(): void + public function save(): void { file_put_contents('README.md', $this->readmeContent); } From 4163e00e02da5d9a08e4dd9ac72932c2883e2a69 Mon Sep 17 00:00:00 2001 From: Artyom Osepyan Date: Thu, 23 Oct 2025 10:54:31 +0300 Subject: [PATCH 12/13] chore: remove unnecessary file --- pint.json | 43 ------------------------------------------- 1 file changed, 43 deletions(-) delete mode 100644 pint.json diff --git a/pint.json b/pint.json deleted file mode 100644 index 6dcb0eb..0000000 --- a/pint.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "preset": "laravel", - "rules": { - "not_operator_with_successor_space": false, - "php_unit_set_up_tear_down_visibility": false, - "php_unit_method_casing": { - "case": "camel_case" - }, - "concat_space": { - "spacing": "one" - }, - "class_attributes_separation": { - "elements": { - "method": "one" - } - }, - "array_syntax": { - "syntax": "short" - }, - "list_syntax": { - "syntax": "long" - }, - "braces": { - "position_after_functions_and_oop_constructs": "next" - }, - "single_line_empty_body": false, - "trailing_comma_in_multiline": { - "elements": [ - "arguments", - "arrays", - "match", - "parameters" - ] - }, - "new_with_parentheses": { - "anonymous_class": false, - "named_class": true - }, - "phpdoc_separation": { - "skip_unlisted_annotations": false - } - } -} \ No newline at end of file From 463c889538206b5791f4ca8b66cc7094b1ddcec7 Mon Sep 17 00:00:00 2001 From: Artyom Osepyan Date: Thu, 23 Oct 2025 11:35:54 +0300 Subject: [PATCH 13/13] style: fix code style --- src/Commands/InitCommand.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Commands/InitCommand.php b/src/Commands/InitCommand.php index bb4187d..2185678 100644 --- a/src/Commands/InitCommand.php +++ b/src/Commands/InitCommand.php @@ -590,6 +590,7 @@ protected function runMigrations(): void shell_exec('php artisan migrate --ansi'); } + protected function publishAdminMigration(array $adminCredentials, ?string $serviceKey): void { $migrationName = (empty($serviceKey)) ? 'add_default_admin' : "add_{$serviceKey}_admin";