diff --git a/src/Commands/InitCommand.php b/src/Commands/InitCommand.php index 0275ff9..c9f176a 100644 --- a/src/Commands/InitCommand.php +++ b/src/Commands/InitCommand.php @@ -13,54 +13,20 @@ use RonasIT\ProjectInitializator\Enums\AuthTypeEnum; use RonasIT\ProjectInitializator\Enums\RoleEnum; use RonasIT\ProjectInitializator\Extensions\ConfigWriter\ArrayFile; +use RonasIT\ProjectInitializator\Generators\ReadmeGenerator; use Winter\LaravelConfigWriter\EnvFile; 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', - 'sentry' => 'Sentry', - 'datadog' => 'DataDog', - 'argocd' => 'ArgoCD', - 'telescope' => 'Laravel Telescope', - 'nova' => 'Laravel Nova', - ]; - - public const array CONTACTS_ITEMS = [ - 'manager' => 'Manager', - ]; - - public const array CREDENTIALS_ITEMS = [ - 'telescope' => 'Laravel Telescope', - 'nova' => 'Laravel Nova', - ]; - - public const array DEFAULT_URLS = [ - 'telescope', - '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 $readmeParts = []; protected array $emptyValuesList = []; - protected string $readmeContent = ''; - protected array $shellCommands = [ 'composer require laravel/ui', 'composer require ronasit/laravel-helpers', @@ -77,61 +43,40 @@ class InitCommand extends Command implements Isolatable protected bool $shouldUninstallPackage = false; protected string $appName; + protected string $kebabName; + protected string $appUrl; + protected AppTypeEnum $appType; + protected AuthTypeEnum $authType; + protected string $codeOwnerEmail; - protected string $dbConnection = 'pgsql'; - protected string $dbHost = 'pgsql'; - protected string $dbPort = '5432'; - protected string $dbName = 'postgres'; - protected string $dbUserName = 'postgres'; + protected string $envFile; + protected array $envConfig = [ + 'dbConnection' => 'pgsql', + 'dbHost' => 'pgsql', + 'dbPort' => '5432', + 'dbName' => 'postgres', + 'dbUserName' => 'postgres', + ]; - protected AppTypeEnum $appType; + public function __construct( + protected ReadmeGenerator $readmeGenerator, + ) { + parent::__construct(); + } 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"); - $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_PASSWORD' => '', - ]; - - $this->updateEnvFile($envFile, $envConfig); - - if (!file_exists('.env.development')) { - copy('.env.example', '.env.development'); - } - - $this->updateEnvFile('.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_HOST' => '', - 'DB_PORT' => '', - 'DB_DATABASE' => '', - 'DB_USERNAME' => '', - 'DB_PASSWORD' => '', - ]); + $this->updateEnvFile(); $this->info('Project initialized successfully!'); @@ -149,66 +94,28 @@ public function handle(): void default: AuthTypeEnum::None->value, )); - if ($this->authType === AuthTypeEnum::Clerk) { - $this->enableClerk(); - - $data = [ - 'AUTH_GUARD' => 'clerk', - 'CLERK_ALLOWED_ISSUER' => '', - 'CLERK_SECRET_KEY' => '', - 'CLERK_SIGNER_KEY_PATH' => '', - ]; - - if ($this->appType !== AppTypeEnum::Mobile) { - $data['CLERK_ALLOWED_ORIGINS'] = ''; - } - - $this->updateEnvFile('.env.development', $data); - $this->updateEnvFile($envFile, $data); - - if ($envFile !== '.env.example') { - $this->updateEnvFile('.env.example', $data); - } - } + $this->configureClerk(); if ($this->confirm('Do you want to generate an admin user?', true)) { if ($this->authType === AuthTypeEnum::Clerk) { $this->publishAdminsTableMigration(); } - $this->createAdminUser($kebabName); + $this->createAdminUser(); } if ($shouldGenerateReadme = $this->confirm('Do you want to generate a README file?', true)) { - $this->fillReadme(); - - if ($this->confirm('Do you need a `Resources & Contacts` part?', true)) { - $this->fillResourcesAndContacts(); - $this->fillResources(); - $this->fillContacts(); - } - - if ($this->confirm('Do you need a `Prerequisites` part?', true)) { - $this->fillPrerequisites(); - } - - if ($this->confirm('Do you need a `Getting Started` part?', true)) { - $this->fillGettingStarted(); - } - - if ($this->confirm('Do you need an `Environments` part?', true)) { - $this->fillEnvironments(); - } + $this->configureReadmeParts(); + } - if ($this->confirm('Do you need a `Credentials and Access` part?', true)) { - $this->fillCredentialsAndAccess($kebabName); + if ($this->confirm('Would you use Renovate dependabot?', true)) { + $this->saveRenovateJSON(); - if ($this->authType === AuthTypeEnum::Clerk) { - $this->fillClerkAuthType(); - } - } + $this->readmeParts[] = 'fillRenovate'; + } - $this->saveReadme(); + if ($shouldGenerateReadme) { + $this->readmeGenerator->generate($this->readmeParts); $this->info('README generated successfully!'); @@ -221,16 +128,6 @@ public function handle(): void } } - if ($this->confirm('Would you use Renovate dependabot?', true)) { - $this->saveRenovateJSON(); - - if ($shouldGenerateReadme) { - $this->fillRenovate(); - - $this->saveReadme(); - } - } - if (!class_exists(\Laravel\Telescope\TelescopeServiceProvider::class)) { array_push( $this->shellCommands, @@ -266,231 +163,167 @@ public function handle(): void $this->runMigrations(); } - protected function setupComposerHooks(): void + protected function validateInput(callable $method, string $field, string|array $rules): string { - $path = base_path('composer.json'); - - $content = file_get_contents($path); + $value = $method(); - $data = json_decode($content, true); + $validator = Validator::make([$field => $value], [$field => $rules]); - $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'); + if ($validator->fails()) { + $this->warn($validator->errors()->first()); - $resultData = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"; + $value = $this->validateInput($method, $field, $rules); + } - file_put_contents($path, $resultData); + return $value; } - protected function addArrayItemIfMissing(array &$data, string $path, string $value): void + protected function prepareAppName(): void { - $current = Arr::get($data, $path, []); + $this->appName = $this->argument('application-name'); - if (!in_array($value, $current)) { - $current[] = $value; + $pascalCaseAppName = ucfirst(Str::camel($this->appName)); - Arr::set($data, $path, $current); + 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 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'); + $this->kebabName = Str::kebab($this->appName); } - protected function createAdminUser(string $kebabName, string $serviceKey = '', string $serviceName = ''): array + protected function updateEnvFile(): void { - $adminEmail = when(empty($serviceKey), "admin@{$kebabName}.com", "admin.{$serviceKey}@{$kebabName}.com"); - $defaultPassword = substr(md5(uniqid()), 0, 8); + $this->envFile = (file_exists('.env')) ? '.env' : '.env.example'; - $serviceLabel = when(!empty($serviceName), " for {$serviceName}"); - - $adminCredentials = [ - 'email' => $this->ask("Please enter admin email{$serviceLabel}", $adminEmail), - 'password' => $this->ask("Please enter admin password{$serviceLabel}", $defaultPassword), + $envConfig = [ + 'APP_NAME' => $this->appName, + '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' => '', ]; - if ($this->authType === AuthTypeEnum::None) { - $adminCredentials['name'] = $this->ask("Please enter admin name{$serviceLabel}", "{$serviceName} Admin"); - $adminCredentials['role_id'] = $this->ask("Please enter admin role id{$serviceLabel}", RoleEnum::Admin->value); - } + $this->writeEnvFile($this->envFile, $envConfig); - if (empty($serviceName)) { - $this->adminCredentials = $adminCredentials; + if (!file_exists('.env.development')) { + copy('.env.example', '.env.development'); } - $this->publishAdminMigration($adminCredentials, $serviceKey); - - return $adminCredentials; - } - - 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; + $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->envConfig['dbConnection'], + 'DB_HOST' => '', + 'DB_PORT' => '', + 'DB_DATABASE' => '', + 'DB_USERNAME' => '', + 'DB_PASSWORD' => '', + ]); } - protected function fillResourcesAndContacts(): void + protected function configureClerk(): void { - $filePart = $this->loadReadmePart('RESOURCES_AND_CONTACTS.md'); - - $this->updateReadmeFile($filePart); - } + if ($this->authType === AuthTypeEnum::Clerk) { + $this->enableClerk(); - protected function fillResources(): void - { - $filePart = $this->loadReadmePart('RESOURCES.md'); - $laterText = '(will be added later)'; - - foreach (self::RESOURCES_ITEMS as $key => $title) { - $defaultAnswer = (in_array($key, self::DEFAULT_URLS)) ? $this->appUrl . "/{$key}" : 'later'; - $text = "Are you going to use {$title}? " - . "Please enter a link or select `later` to do it later, otherwise select `no`."; - - $link = $this->anticipate( - $text, - ['later', 'no'], - $defaultAnswer - ); + $data = [ + 'AUTH_GUARD' => 'clerk', + 'CLERK_ALLOWED_ISSUER' => '', + 'CLERK_SECRET_KEY' => '', + 'CLERK_SIGNER_KEY_PATH' => '', + ]; - if ($link === 'later') { - $this->emptyValuesList[] = "{$title} link"; - $this->setReadmeValue($filePart, "{$key}_link"); - $this->setReadmeValue($filePart, "{$key}_later", $laterText); - } elseif ($link !== 'no') { - $this->setReadmeValue($filePart, "{$key}_link", $link); - $this->setReadmeValue($filePart, "{$key}_later"); + if ($this->appType !== AppTypeEnum::Mobile) { + $data['CLERK_ALLOWED_ORIGINS'] = ''; } - $this->resources[$key] = ($link !== 'no'); + $this->writeEnvFile('.env.development', $data); + $this->writeEnvFile($this->envFile, $data); - $this->removeTag($filePart, $key, $link === 'no'); + if ($this->envFile !== '.env.example') { + $this->writeEnvFile('.env.example', $data); + } } - - $this->setReadmeValue($filePart, 'api_link', $this->appUrl); - $this->updateReadmeFile($filePart); } - protected function fillContacts(): void + protected function writeEnvFile(string $fileName, array $data): void { - $filePart = $this->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); - } else { - $this->emptyValuesList[] = "{$title}'s email"; - } + $env = EnvFile::open($fileName); - $this->removeTag($filePart, $key); - } + // 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); - $this->setReadmeValue($filePart, 'team_lead_link', $this->codeOwnerEmail); + $hasMissingKeys = count(array_intersect($dataKeys, $envKeys)) !== count($dataKeys); - $this->updateReadmeFile($filePart); - } + if ($hasMissingKeys) { + $env->addEmptyLine(); + } - protected function fillPrerequisites(): void - { - $filePart = $this->loadReadmePart('PREREQUISITES.md'); + $env->set($data); - $this->updateReadmeFile($filePart); + $env->write(); } - protected function fillGettingStarted(): void + protected function enableClerk(): void { - $gitProjectPath = trim((string) shell_exec('git ls-remote --get-url origin')); - $projectDirectory = basename($gitProjectPath, '.git'); - $filePart = $this->loadReadmePart('GETTING_STARTED.md'); + array_push( + $this->shellCommands, + 'composer require ronasit/laravel-clerk', + 'php artisan laravel-clerk:install', + ); - $this->setReadmeValue($filePart, 'git_project_path', $gitProjectPath); - $this->setReadmeValue($filePart, 'project_directory', $projectDirectory); + $this->publishMigration( + view: view('initializator::users_add_clerk_id_field'), + migrationName: 'users_add_clerk_id_field', + ); - $this->updateReadmeFile($filePart); + $this->publishClass( + template: view('initializator::clerk_user_repository'), + fileName: 'ClerkUserRepository', + filePath: 'app/Support/Clerk', + ); } - protected function fillEnvironments(): void + protected function createAdminUser(string $serviceKey = '', string $serviceName = ''): array { - $filePart = $this->loadReadmePart('ENVIRONMENTS.md'); + $adminEmail = when(empty($serviceKey), "admin@{$this->kebabName}.com", "admin.{$serviceKey}@{$this->kebabName}.com"); + $defaultPassword = substr(md5(uniqid()), 0, 8); - $this->setReadmeValue($filePart, 'api_link', $this->appUrl); - $this->updateReadmeFile($filePart); - } + $serviceLabel = when(!empty($serviceName), " for {$serviceName}"); - protected function fillCredentialsAndAccess(string $kebabName): void - { - $filePart = $this->loadReadmePart('CREDENTIALS_AND_ACCESS.md'); + $adminCredentials = [ + 'email' => $this->ask("Please enter admin email{$serviceLabel}", $adminEmail), + 'password' => $this->ask("Please enter admin password{$serviceLabel}", $defaultPassword), + ]; - if (!empty($this->adminCredentials)) { - $this->setReadmeValue($filePart, 'admin_email', $this->adminCredentials['email']); - $this->setReadmeValue($filePart, 'admin_password', $this->adminCredentials['password']); + if ($this->authType === AuthTypeEnum::None) { + $adminCredentials['name'] = $this->ask("Please enter admin name{$serviceLabel}", "{$serviceName} Admin"); + $adminCredentials['role_id'] = $this->ask("Please enter admin role id{$serviceLabel}", RoleEnum::Admin->value); } - $this->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); - - continue; - } - - if (!empty($this->adminCredentials) && $this->confirm("Is {$title}'s admin the same as default one?", true)) { - $adminCredentials = $this->adminCredentials; - } else { - if ($this->authType === AuthTypeEnum::Clerk && !$this->isMigrationExists('admins_create_table')) { - $this->publishAdminsTableMigration(); - } - - $adminCredentials = $this->createAdminUser($kebabName, $key, $title); - } - - $this->setReadmeValue($filePart, "{$key}_email", $adminCredentials['email']); - $this->setReadmeValue($filePart, "{$key}_password", $adminCredentials['password']); - $this->removeTag($filePart, "{$key}_credentials"); + if (empty($serviceName)) { + $this->adminCredentials = $adminCredentials; } - $this->updateReadmeFile($filePart); + $this->publishAdminMigration($adminCredentials, $serviceKey); + + return $adminCredentials; } - protected function fillClerkAuthType(): void + protected function publishMigration(View $view, string $migrationName): void { - $filePart = $this->loadReadmePart('CLERK.md'); + $time = Carbon::now()->format('Y_m_d_His'); - $this->updateReadmeFile($filePart); - } + $migrationName = "{$time}_{$migrationName}"; - protected function addQuotes($string): string - { - return (Str::contains($string, ' ')) ? "\"{$string}\"" : $string; + $this->publishClass($view, $migrationName, 'database/migrations'); } protected function publishClass(View $template, string $fileName, string $filePath): void @@ -506,75 +339,103 @@ protected function publishClass(View $template, string $fileName, string $filePa file_put_contents("{$filePath}/{$fileName}", "format('Y_m_d_His'); - - $migrationName = "{$time}_{$migrationName}"; - - $this->publishClass($view, $migrationName, 'database/migrations'); - } + $this->readmeGenerator->appInfo = [ + 'name' => $this->appName, + 'type' => $this->appType->value, + 'url' => $this->appUrl, + 'code_owner_email' => $this->codeOwnerEmail, + ]; - protected function updateEnvFile(string $fileName, array $data): void - { - $env = EnvFile::open($fileName); + if ($this->confirm('Do you need a `Resources & Contacts` part?', true)) { + $this->configureResources(); + $this->configureContacts(); - // 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); + $this->readmeParts[] = 'fillResourcesAndContacts'; + $this->readmeParts[] = 'fillResources'; + $this->readmeParts[] = 'fillContacts'; + } - $hasMissingKeys = count(array_intersect($dataKeys, $envKeys)) !== count($dataKeys); + if ($this->confirm('Do you need a `Prerequisites` part?', true)) { + $this->readmeParts[] = 'fillPrerequisites'; + } - if ($hasMissingKeys) { - $env->addEmptyLine(); + if ($this->confirm('Do you need a `Getting Started` part?', true)) { + $this->readmeParts[] = 'fillGettingStarted'; } - $env->set($data); + if ($this->confirm('Do you need an `Environments` part?', true)) { + $this->readmeParts[] = 'fillEnvironments'; + } - $env->write(); - } + if ($this->confirm('Do you need a `Credentials and Access` part?', true)) { + $this->configureCredentialsAndAccess(); - protected function loadReadmePart(string $fileName): string - { - $file = base_path(DIRECTORY_SEPARATOR . self::TEMPLATES_PATH . DIRECTORY_SEPARATOR . $fileName); + $this->readmeParts[] = 'fillCredentialsAndAccess'; - return file_get_contents($file); + if ($this->authType === AuthTypeEnum::Clerk) { + $this->readmeParts[] = 'fillClerkAuthType'; + } + } } - protected function updateReadmeFile(string $filePart): void + protected function configureResources(): void { - $filePart = preg_replace('#(\n){3,}#', "\n", $filePart); + foreach ($this->readmeGenerator->resourcesItems as $key => $resource) { + $defaultAnswer = (Arr::has($resource, 'default_url')) ? $this->appUrl . "/{$key}" : 'later'; + $text = "Are you going to use {$resource['title']}? " + . 'Please enter a link or select `later` to do it later, otherwise select `no`.'; - $this->readmeContent .= "\n" . $filePart; - } + $link = $this->anticipate($text, ['later', 'no'], $defaultAnswer); - protected function removeTag(string &$text, string $tag, bool $removeWholeString = false): void - { - $regex = ($removeWholeString) - ? "#({{$tag}})(.|\s)*?({/{$tag}})#" - : "# {0,1}{(/*){$tag}}#"; + if ($link === 'later') { + $this->emptyValuesList[] = "{$resource['title']} link"; + } - $text = preg_replace($regex, '', $text); + $this->readmeGenerator->resourcesItems[$key]['link'] = $link; + $this->readmeGenerator->resourcesItems[$key]['active'] = ($link !== 'no'); + } } - protected function setReadmeValue(string &$file, string $key, string $value = ''): void + protected function configureContacts(): void { - $file = str_replace(":{$key}", $value, $file); + foreach ($this->readmeGenerator->contactsItems as $key => $value) { + if ($link = $this->ask("Please enter a {$value['title']}'s email", '')) { + $this->readmeGenerator->contactsItems[$key]['email'] = $link; + } else { + $this->emptyValuesList[] = "{$value['title']}'s email"; + } + } } - protected function saveReadme(): void + protected function configureCredentialsAndAccess(): void { - file_put_contents('README.md', $this->readmeContent); - } + foreach ($this->readmeGenerator->credentialsItems as $key => &$item) { + if (!Arr::get($this->readmeGenerator->resourcesItems, "{$key}.active")) { + continue; + } - protected function prepareAppName(): void - { - $this->appName = $this->argument('application-name'); + if (!empty($this->adminCredentials) && $this->confirm("Is {$item['title']}'s admin the same as default one?", true)) { + $adminCredentials = $this->adminCredentials; + } else { + if ($this->authType === AuthTypeEnum::Clerk && !$this->isMigrationExists('admins_create_table')) { + $this->publishAdminsTableMigration(); + } - $pascalCaseAppName = ucfirst(Str::camel($this->appName)); + $adminCredentials = $this->createAdminUser($key, $item['title']); + } - if ($this->appName !== $pascalCaseAppName && $this->confirm("The application name is not in PascalCase, would you like to use {$pascalCaseAppName}", true)) { - $this->appName = $pascalCaseAppName; + $item['email'] = $adminCredentials['email']; + $item['password'] = $adminCredentials['password']; + } + + if (!empty($this->adminCredentials)) { + $this->readmeGenerator->credentialsItems['admin'] = [ + 'title' => 'Default admin', + 'email' => $this->adminCredentials['email'], + 'password' => $this->adminCredentials['password'], + ]; } } @@ -596,46 +457,55 @@ protected function saveRenovateJSON(): void file_put_contents('renovate.json', json_encode($data, JSON_PRETTY_PRINT)); } - protected function validateInput(callable $method, string $field, string|array $rules): string + protected function setupComposerHooks(): void { - $value = $method(); + $path = base_path('composer.json'); - $validator = Validator::make([$field => $value], [$field => $rules]); + $content = file_get_contents($path); - if ($validator->fails()) { - $this->warn($validator->errors()->first()); + $data = json_decode($content, true); - $value = $this->validateInput($method, $field, $rules); - } + $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'); - return $value; + $resultData = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"; + + file_put_contents($path, $resultData); } - protected function fillRenovate(): void + protected function addArrayItemIfMissing(array &$data, string $path, string $value): void { - $filePart = $this->loadReadmePart('RENOVATE.md'); + $current = Arr::get($data, $path, []); + + if (!in_array($value, $current)) { + $current[] = $value; - $this->updateReadmeFile($filePart); + Arr::set($data, $path, $current); + } } - protected function enableClerk(): void + protected function changeMiddlewareForTelescopeAuthorization(): void { - array_push( - $this->shellCommands, - 'composer require ronasit/laravel-clerk', - 'php artisan laravel-clerk:install', - ); + $config = ArrayFile::open(base_path('config/telescope.php')); - $this->publishMigration( - view: view('initializator::users_add_clerk_id_field'), - migrationName: 'users_add_clerk_id_field', - ); + // 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', + ]); - $this->publishClass( - template: view('initializator::clerk_user_repository'), - fileName: 'ClerkUserRepository', - filePath: 'app/Support/Clerk', - ); + $config->write(); + } + + 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 publishWebLogin(): void @@ -645,17 +515,21 @@ 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'); } protected function publishAdminMigration(array $adminCredentials, ?string $serviceKey): void diff --git a/src/Generators/ReadmeGenerator.php b/src/Generators/ReadmeGenerator.php new file mode 100644 index 0000000..6763d78 --- /dev/null +++ b/src/Generators/ReadmeGenerator.php @@ -0,0 +1,217 @@ + [ + 'title' => 'Issue Tracker', + ], + 'figma' => [ + 'title' => 'Figma', + ], + 'sentry' => [ + 'title' => 'Sentry', + ], + 'datadog' => [ + 'title' => 'DataDog', + ], + 'argocd' => [ + 'title' => 'ArgoCD', + ], + 'telescope' => [ + 'title' => 'Laravel Telescope', + 'default_url' => true, + ], + 'nova' => [ + 'title' => 'Laravel Nova', + 'default_url' => true, + ], + ]; + + public array $contactsItems = [ + 'manager' => [ + 'title' => 'Manager', + ], + ]; + + public array $credentialsItems = [ + 'telescope' => [ + 'title' => 'Laravel Telescope', + ], + 'nova' => [ + 'title' => 'Laravel Nova', + ], + ]; + + protected function prepareReadme(): void + { + $file = $this->loadReadmePart('README.md'); + + $this->setReadmeValue($file, 'project_name', $this->appInfo['name']); + $this->setReadmeValue($file, 'type', $this->appInfo['type']); + + $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'); + $laterText = '(will be added later)'; + + foreach ($this->resourcesItems as $key => $resource) { + if ($resource['link'] === 'later') { + $this->setReadmeValue($filePart, "{$key}_link"); + $this->setReadmeValue($filePart, "{$key}_later", $laterText); + } elseif ($resource['link'] !== 'no') { + $this->setReadmeValue($filePart, "{$key}_link", $resource['link']); + $this->setReadmeValue($filePart, "{$key}_later"); + } + + $this->removeTag($filePart, $key, $resource['link'] === 'no'); + } + + $this->setReadmeValue($filePart, 'api_link', $this->appInfo['url']); + $this->updateReadmeFile($filePart); + } + + protected function fillContacts(): void + { + $filePart = $this->loadReadmePart('CONTACTS.md'); + + foreach ($this->contactsItems as $key => $value) { + if (Arr::has($value, 'email')) { + $this->setReadmeValue($filePart, "{$key}_link", $value['email']); + } + + $this->removeTag($filePart, $key); + } + + $this->setReadmeValue($filePart, 'team_lead_link', $this->appInfo['code_owner_email']); + + $this->updateReadmeFile($filePart); + } + + protected function fillPrerequisites(): void + { + $filePart = $this->loadReadmePart('PREREQUISITES.md'); + + $this->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'); + + $this->setReadmeValue($filePart, 'git_project_path', $gitProjectPath); + $this->setReadmeValue($filePart, 'project_directory', $projectDirectory); + + $this->updateReadmeFile($filePart); + } + + protected function fillEnvironments(): void + { + $filePart = $this->loadReadmePart('ENVIRONMENTS.md'); + + $this->setReadmeValue($filePart, 'api_link', $this->appInfo['url']); + $this->updateReadmeFile($filePart); + } + + protected function fillCredentialsAndAccess(): void + { + $filePart = $this->loadReadmePart('CREDENTIALS_AND_ACCESS.md'); + + foreach ($this->credentialsItems as $key => $item) { + if (Arr::has($item, 'email')) { + $this->setReadmeValue($filePart, "{$key}_email", $item['email']); + $this->setReadmeValue($filePart, "{$key}_password", $item['password']); + $this->removeTag($filePart, "{$key}_credentials"); + } + + $this->removeTag($filePart, "{$key}_credentials", true); + } + + if (!Arr::has($this->credentialsItems, 'admin_credentials')) { + $this->removeTag($filePart, 'admin_credentials', true); + } + $this->updateReadmeFile($filePart); + } + + protected function fillClerkAuthType(): void + { + $filePart = $this->loadReadmePart('CLERK.md'); + + $this->updateReadmeFile($filePart); + } + + protected function fillRenovate(): void + { + $filePart = $this->loadReadmePart('RENOVATE.md'); + + $this->updateReadmeFile($filePart); + } + + protected function saveReadme(): void + { + file_put_contents('README.md', $this->readmeContent); + } + + protected function loadReadmePart(string $fileName): string + { + $file = base_path(self::TEMPLATES_PATH . DIRECTORY_SEPARATOR . $fileName); + + return file_get_contents($file); + } + + protected function setReadmeValue(string &$file, string $key, string $value = ''): void + { + $file = str_replace(":{$key}", $value, $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); + } + + public function generate(array $parts): void + { + $this->prepareReadme(); + + foreach ($parts as $part) { + if (method_exists($this, $part)) { + $this->{$part}(); + } + } + + $this->saveReadme(); + } +} diff --git a/tests/InitCommandTest.php b/tests/InitCommandTest.php index c077c2f..862acf4 100644 --- a/tests/InitCommandTest.php +++ b/tests/InitCommandTest.php @@ -221,16 +221,6 @@ 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')), @@ -239,13 +229,10 @@ 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('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), - $this->callShellExec('git ls-remote --get-url origin', 'https://github.com/ronasit/laravel-helpers.git'), $this->callShellExec('composer require laravel/ui --ansi'), $this->callShellExec('composer require ronasit/laravel-helpers --ansi'), $this->callShellExec('composer require ronasit/laravel-swagger --ansi'), @@ -264,6 +251,25 @@ 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->callShellExec('git ls-remote --get-url origin', 'https://github.com/ronasit/laravel-helpers.git'), + ); + $this ->artisan('init "My App"') ->expectsConfirmation('The application name is not in PascalCase, would you like to use MyApp') @@ -359,15 +365,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), @@ -387,6 +386,17 @@ 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') @@ -470,26 +480,14 @@ 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_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('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), - $this->callShellExec('git ls-remote --get-url origin', 'https://github.com/ronasit/laravel-helpers.git'), $this->callShellExec('composer require laravel/ui --ansi'), $this->callShellExec('composer require ronasit/laravel-helpers --ansi'), $this->callShellExec('composer require ronasit/laravel-swagger --ansi'), @@ -508,6 +506,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->callShellExec('git ls-remote --get-url origin', 'https://github.com/ronasit/laravel-helpers.git'), + ); + $this ->artisan('init "My App"') ->expectsConfirmation('The application name is not in PascalCase, would you like to use MyApp') @@ -600,17 +614,10 @@ 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('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), @@ -630,6 +637,17 @@ 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') @@ -723,29 +741,16 @@ 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('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('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), - $this->callShellExec('git ls-remote --get-url origin', 'https://github.com/ronasit/laravel-helpers.git'), $this->callShellExec('composer require laravel/ui --ansi'), $this->callShellExec('composer require ronasit/laravel-helpers --ansi'), $this->callShellExec('composer require ronasit/laravel-swagger --ansi'), @@ -764,6 +769,24 @@ 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->callShellExec('git ls-remote --get-url origin', 'https://github.com/ronasit/laravel-helpers.git'), + ); + $this ->artisan('init "My App"') ->expectsConfirmation('The application name is not in PascalCase, would you like to use MyApp') @@ -859,13 +882,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')), @@ -874,7 +891,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), @@ -896,6 +912,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') diff --git a/tests/fixtures/InitCommandTest/default_readme.md b/tests/fixtures/InitCommandTest/default_readme.md index 4855614..e340e49 100644 --- a/tests/fixtures/InitCommandTest/default_readme.md +++ b/tests/fixtures/InitCommandTest/default_readme.md @@ -83,3 +83,7 @@ Laravel Nova access: - password `123456` Core auth solution is [Clerk](https://clerk.com) so in `development` environment use any test phones and emails from [this list](https://clerk.com/docs/testing/test-emails-and-phones) + +### Renovate + +The application uses Renovate dependabot for automatically updating dependencies. You can configure it more precisely using the `renovate.json` file. Available configuration described here https://docs.renovatebot.com/config-overview \ No newline at end of file diff --git a/tests/fixtures/InitCommandTest/default_readme_after_using_renovate.md b/tests/fixtures/InitCommandTest/default_readme_after_using_renovate.md deleted file mode 100644 index e340e49..0000000 --- a/tests/fixtures/InitCommandTest/default_readme_after_using_renovate.md +++ /dev/null @@ -1,89 +0,0 @@ -# My App - -This project implements an API for the My App Multiplatform app. - -## Project Resources & Contacts - -This section provides quick links to various resources and contacts associated -with this project. It's here to streamline your navigation and communication -process, so you can efficiently find what you need or reach out to who you need. - -### Resources - -Below are links to tools and services used in this project: -- [Issue Tracker](): Here, you can report any issues or bugs related to the project. (will be added later) -- [Figma](): This is where we maintain all our design assets and mock-ups. (will be added later) -- [Sentry](): To monitor application performance and error tracking. (will be added later) -- [DataDog](): This is where we monitor our logs, and server performance, and receive alerts. (will be added later) -- [ArgoCD](): Is a kubernetes controller which continuously monitors running applications. (will be added later) -- [Laravel Telescope](): This is debug assistant for the Laravel framework. (will be added later) -- [Laravel Nova](): This is admin panel for the Laravel framework. (will be added later) -- [API Documentation](https://mysite.com) - -### Contacts - -Should you need assistance or have questions, feel free to connect with the following individuals: -- Manager: If you have any high-level project concerns, feel free to get in touch with our project manager. [Connect with Manager](mailto::manager_link) -- Code Owner/Team Lead: For specific questions about the codebase or technical aspects, reach out to our team lead. [Connect with Team Lead](mailto:test@example.com) - -Please be mindful of each individual's preferred contact method and office hours. - -## Prerequisites - -To work with this repository, you will need to have the following -installed: - -- [Docker](https://www.docker.com) - -## Getting Started - -To get started with this repository, follow these steps: - -Clone this repository to your local machine: - -```sh -git clone https://github.com/ronasit/laravel-helpers.git -``` - -Open project directory: - -```sh -cd laravel-helpers -``` - -Build and start containers, it may takes some time: - -```sh -docker compose up -d -``` - -## Environments - -This repository by default supports three environments: `local`, `development`, -and `testing`. Each environment is represented by an appropriate environment file: - -| Environment | File | URL | -| --- | --- |--------------------------------------| -| local | .env | [http://localhost](http://localhost) | -| testing | .env.testing | - | -| development | .env.development | [https://mysite.com](https://mysite.com) | - -## Credentials and Access - -Default admin access: -- email `mail@mail.com` -- password `123456` - -Laravel Telescope access: -- email `mail@mail.com` -- password `123456` - -Laravel Nova access: -- email `mail@mail.com` -- password `123456` - -Core auth solution is [Clerk](https://clerk.com) so in `development` environment use any test phones and emails from [this list](https://clerk.com/docs/testing/test-emails-and-phones) - -### Renovate - -The application uses Renovate dependabot for automatically updating dependencies. You can configure it more precisely using the `renovate.json` file. Available configuration described here https://docs.renovatebot.com/config-overview \ No newline at end of file diff --git a/tests/fixtures/InitCommandTest/default_readme_with_mobile_app.md b/tests/fixtures/InitCommandTest/default_readme_with_mobile_app.md index 2d6e6c7..a839b3b 100644 --- a/tests/fixtures/InitCommandTest/default_readme_with_mobile_app.md +++ b/tests/fixtures/InitCommandTest/default_readme_with_mobile_app.md @@ -83,3 +83,7 @@ Laravel Nova access: - password `123456` Core auth solution is [Clerk](https://clerk.com) so in `development` environment use any test phones and emails from [this list](https://clerk.com/docs/testing/test-emails-and-phones) + +### Renovate + +The application uses Renovate dependabot for automatically updating dependencies. You can configure it more precisely using the `renovate.json` file. Available configuration described here https://docs.renovatebot.com/config-overview \ No newline at end of file diff --git a/tests/fixtures/InitCommandTest/default_readme_with_mobile_app_after_using_renovate.md b/tests/fixtures/InitCommandTest/default_readme_with_mobile_app_after_using_renovate.md deleted file mode 100644 index a839b3b..0000000 --- a/tests/fixtures/InitCommandTest/default_readme_with_mobile_app_after_using_renovate.md +++ /dev/null @@ -1,89 +0,0 @@ -# My App - -This project implements an API for the My App Mobile app. - -## Project Resources & Contacts - -This section provides quick links to various resources and contacts associated -with this project. It's here to streamline your navigation and communication -process, so you can efficiently find what you need or reach out to who you need. - -### Resources - -Below are links to tools and services used in this project: -- [Issue Tracker](): Here, you can report any issues or bugs related to the project. (will be added later) -- [Figma](): This is where we maintain all our design assets and mock-ups. (will be added later) -- [Sentry](): To monitor application performance and error tracking. (will be added later) -- [DataDog](): This is where we monitor our logs, and server performance, and receive alerts. (will be added later) -- [ArgoCD](): Is a kubernetes controller which continuously monitors running applications. (will be added later) -- [Laravel Telescope](): This is debug assistant for the Laravel framework. (will be added later) -- [Laravel Nova](): This is admin panel for the Laravel framework. (will be added later) -- [API Documentation](https://mysite.com) - -### Contacts - -Should you need assistance or have questions, feel free to connect with the following individuals: -- Manager: If you have any high-level project concerns, feel free to get in touch with our project manager. [Connect with Manager](mailto::manager_link) -- Code Owner/Team Lead: For specific questions about the codebase or technical aspects, reach out to our team lead. [Connect with Team Lead](mailto:test@example.com) - -Please be mindful of each individual's preferred contact method and office hours. - -## Prerequisites - -To work with this repository, you will need to have the following -installed: - -- [Docker](https://www.docker.com) - -## Getting Started - -To get started with this repository, follow these steps: - -Clone this repository to your local machine: - -```sh -git clone https://github.com/ronasit/laravel-helpers.git -``` - -Open project directory: - -```sh -cd laravel-helpers -``` - -Build and start containers, it may takes some time: - -```sh -docker compose up -d -``` - -## Environments - -This repository by default supports three environments: `local`, `development`, -and `testing`. Each environment is represented by an appropriate environment file: - -| Environment | File | URL | -| --- | --- |--------------------------------------| -| local | .env | [http://localhost](http://localhost) | -| testing | .env.testing | - | -| development | .env.development | [https://mysite.com](https://mysite.com) | - -## Credentials and Access - -Default admin access: -- email `mail@mail.com` -- password `123456` - -Laravel Telescope access: -- email `mail@mail.com` -- password `123456` - -Laravel Nova access: -- email `mail@mail.com` -- password `123456` - -Core auth solution is [Clerk](https://clerk.com) so in `development` environment use any test phones and emails from [this list](https://clerk.com/docs/testing/test-emails-and-phones) - -### Renovate - -The application uses Renovate dependabot for automatically updating dependencies. You can configure it more precisely using the `renovate.json` file. Available configuration described here https://docs.renovatebot.com/config-overview \ No newline at end of file diff --git a/tests/fixtures/InitCommandTest/full_readme.md b/tests/fixtures/InitCommandTest/full_readme.md index 7c8943b..833d428 100644 --- a/tests/fixtures/InitCommandTest/full_readme.md +++ b/tests/fixtures/InitCommandTest/full_readme.md @@ -81,3 +81,7 @@ Laravel Telescope access: Laravel Nova access: - email `nova_mail@mail.com` - password `654321` + +### Renovate + +The application uses Renovate dependabot for automatically updating dependencies. You can configure it more precisely using the `renovate.json` file. Available configuration described here https://docs.renovatebot.com/config-overview \ No newline at end of file diff --git a/tests/fixtures/InitCommandTest/full_readme_after_using_renovate.md b/tests/fixtures/InitCommandTest/full_readme_after_using_renovate.md deleted file mode 100644 index 833d428..0000000 --- a/tests/fixtures/InitCommandTest/full_readme_after_using_renovate.md +++ /dev/null @@ -1,87 +0,0 @@ -# My App - -This project implements an API for the My App Mobile app. - -## Project Resources & Contacts - -This section provides quick links to various resources and contacts associated -with this project. It's here to streamline your navigation and communication -process, so you can efficiently find what you need or reach out to who you need. - -### Resources - -Below are links to tools and services used in this project: -- [Issue Tracker](https://gitlab.com/my-project): Here, you can report any issues or bugs related to the project. -- [Figma](https://figma.com/my-project): This is where we maintain all our design assets and mock-ups. -- [Sentry](https://sentry.com/my-project): To monitor application performance and error tracking. -- [DataDog](https://datadoghq.com/my-project): This is where we monitor our logs, and server performance, and receive alerts. -- [ArgoCD](https://argocd.com/my-project): Is a kubernetes controller which continuously monitors running applications. -- [Laravel Telescope](https://mypsite.com/telescope-link): This is debug assistant for the Laravel framework. -- [Laravel Nova](https://mypsite.com/nova-link): This is admin panel for the Laravel framework. -- [API Documentation](https://mysite.com) - -### Contacts - -Should you need assistance or have questions, feel free to connect with the following individuals: -- Manager: If you have any high-level project concerns, feel free to get in touch with our project manager. [Connect with Manager](mailto:manager@mail.com) -- Code Owner/Team Lead: For specific questions about the codebase or technical aspects, reach out to our team lead. [Connect with Team Lead](mailto:test@example.com) - -Please be mindful of each individual's preferred contact method and office hours. - -## Prerequisites - -To work with this repository, you will need to have the following -installed: - -- [Docker](https://www.docker.com) - -## Getting Started - -To get started with this repository, follow these steps: - -Clone this repository to your local machine: - -```sh -git clone https://github.com/ronasit/laravel-helpers.git -``` - -Open project directory: - -```sh -cd laravel-helpers -``` - -Build and start containers, it may takes some time: - -```sh -docker compose up -d -``` - -## Environments - -This repository by default supports three environments: `local`, `development`, -and `testing`. Each environment is represented by an appropriate environment file: - -| Environment | File | URL | -| --- | --- |--------------------------------------| -| local | .env | [http://localhost](http://localhost) | -| testing | .env.testing | - | -| development | .env.development | [https://mysite.com](https://mysite.com) | - -## Credentials and Access - -Default admin access: -- email `mail@mail.com` -- password `123456` - -Laravel Telescope access: -- email `mail@mail.com` -- password `123456` - -Laravel Nova access: -- email `nova_mail@mail.com` -- password `654321` - -### Renovate - -The application uses Renovate dependabot for automatically updating dependencies. You can configure it more precisely using the `renovate.json` file. Available configuration described here https://docs.renovatebot.com/config-overview \ No newline at end of file