From 85491accb08cb78d51f0bdf83dfc85a2307b5c43 Mon Sep 17 00:00:00 2001 From: Jonathon Byrdziak Date: Fri, 20 Mar 2026 13:14:58 -0700 Subject: [PATCH] Unify new/existing project flows and simplify dev init routing Replaces separate flowNewProject and flowExistingProject with a single flowSetupProject. The development init path now: - No .git: asks for repository URL, inits git, runs setup - Has .git, no protocol.json: confirms install, runs setup - Has protocol.json, old schema: offers migration - Has protocol.json, current, no config repo: offers config setup - Has protocol.json, current, has config: "All set!" The unified setup flow checks for docker-compose.yml and only asks about Docker containers when one is missing. Removes deployment strategy, secrets, and config repo questions from the dev setup since those are production concerns. Co-Authored-By: Claude Opus 4.6 --- src/Commands/ProtocolInit.php | 168 +++++++++++++++------------------- 1 file changed, 72 insertions(+), 96 deletions(-) diff --git a/src/Commands/ProtocolInit.php b/src/Commands/ProtocolInit.php index 8a743ba..10df96e 100644 --- a/src/Commands/ProtocolInit.php +++ b/src/Commands/ProtocolInit.php @@ -194,10 +194,36 @@ protected function execute(InputInterface $input, OutputInterface $output): int } // ── Development → auto-detect project state ────────────── + $isGitRepo = Git::isInitializedRepo($repo_dir); $hasProtocolJson = is_file(rtrim($repo_dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'protocol.json'); + // ── No git repo → ask for the repository URL first ─────── + if (!$isGitRepo) { + $output->writeln(''); + $output->writeln(" No git repository detected in this directory."); + $output->writeln(''); + + $question = new Question(' Repository URL: '); + $gitRemote = $helper->ask($input, $output, $question); + + if (!$gitRemote) { + $output->writeln(''); + $output->writeln(' A repository URL is required.'); + return Command::FAILURE; + } + + // Clone the repo into this directory or init + add remote + Shell::run("git -C " . escapeshellarg($repo_dir) . " init"); + Shell::run("git -C " . escapeshellarg($repo_dir) . " remote add origin " . escapeshellarg($gitRemote) . " 2>/dev/null"); + $output->writeln(''); + $output->writeln(" ✓ Repository: {$gitRemote}"); + + // Re-check for protocol.json (it won't exist yet since we just init'd) + return $this->flowSetupProject($repo_dir, $input, $output, $helper, $io); + } + + // ── Has git, no protocol.json → new install ────────────── if (!$hasProtocolJson) { - // No protocol.json — confirm new install $output->writeln(''); $output->writeln(" New Project"); $output->writeln(" Protocol is not installed in this directory."); @@ -213,14 +239,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Command::SUCCESS; } - $isGitRepo = Git::isInitializedRepo($repo_dir); - if ($isGitRepo) { - return $this->flowExistingProject($repo_dir, $input, $output, $helper, $io); - } - return $this->flowNewProject($repo_dir, $input, $output, $helper, $io); + return $this->flowSetupProject($repo_dir, $input, $output, $helper, $io); } - // Has protocol.json — check if migration is needed + // ── Has protocol.json → check schema version ──────────── $currentVersion = (int) Json::read('protocol_version', 0, $repo_dir); $targetVersion = self::SCHEMA_VERSION; @@ -244,120 +266,74 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Command::SUCCESS; } - // Up-to-date protocol project — show update menu - return $this->flowProtocolProject($repo_dir, $input, $output, $helper, $io); - } - - // ─── Flow: New Project ─────────────────────────────────────── + // ── Up-to-date protocol.json → check config repo ──────── + $hasConfigRepo = Json::read('configuration.local', false, $repo_dir); + if (!$hasConfigRepo) { + $output->writeln(''); + $output->writeln(" ✓ Protocol is installed and up to date"); + $output->writeln(" No configuration repository detected."); + $output->writeln(''); - protected function flowNewProject( - string $repo_dir, - InputInterface $input, - OutputInterface $output, - $helper, - SymfonyStyle $io - ): int { - $totalSteps = 4; + $initializers = $this->getAvailableInitializers(); + $initializer = reset($initializers); - // Initialize git silently - if (!Git::isInitializedRepo($repo_dir)) { - Shell::run("git -C " . escapeshellarg($repo_dir) . " init"); + $question = new ConfirmationQuestion( + ' Set up a configuration repository? [y/N] ', false + ); + if ($helper->ask($input, $output, $question)) { + $initializer->initializeConfigRepo($repo_dir, $input, $output, $helper); + } + return Command::SUCCESS; } - // Step 1: Project type + scaffold - $this->writeStep($output, 1, $totalSteps, 'Project Type'); - $output->writeln(" Choose the Docker base image for your project. This determines"); - $output->writeln(" which PHP version, extensions, and tools are available."); + // ── Everything is set up ───────────────────────────────── + $projectName = Json::read('name', basename($repo_dir), $repo_dir); $output->writeln(''); - $selectedInitializer = $this->selectProjectType($input, $output, $helper); - $selectedInitializer->initialize($repo_dir, $input, $output, $helper); - $selectedKey = $this->getInitializerKey($selectedInitializer); - $selectedInitializer->createProtocolJson($repo_dir, $selectedKey, $output, self::SCHEMA_VERSION); - - // Step 2: Deployment strategy - $this->writeStep($output, 2, $totalSteps, 'Deployment Strategy'); - $output->writeln(" This controls how code gets to your servers. Release-based"); - $output->writeln(" creates tagged versions you can roll back. Branch-based"); - $output->writeln(" just follows the tip of a branch."); + $output->writeln(" ✓ All set!"); + $output->writeln(" Project: {$projectName}"); + $output->writeln(" Protocol is installed and configured."); $output->writeln(''); - $this->configureDeploymentStrategy($repo_dir, $input, $output, $helper); - - // Step 3: Secrets - $this->writeStep($output, 3, $totalSteps, 'Secrets Management'); - $this->configureSecrets($repo_dir, $input, $output, $helper); - - // Step 4: Config repo - $this->writeStep($output, 4, $totalSteps, 'Configuration Repository'); - $this->configureConfigRepo($repo_dir, $input, $output, $helper, $selectedInitializer); - - // Done - $this->writeCompletion($output, $repo_dir); return Command::SUCCESS; } - // ─── Flow: Existing Project ────────────────────────────────── + // ─── Flow: Setup Project (unified new + existing) ───────────── - protected function flowExistingProject( + protected function flowSetupProject( string $repo_dir, InputInterface $input, OutputInterface $output, $helper, SymfonyStyle $io ): int { - $totalSteps = 4; $step = 0; - - // Step: Repository URL - $this->writeStep($output, ++$step, $totalSteps, 'Repository'); - $output->writeln(" Tell us the GitHub URL for your existing repository."); - $output->writeln(''); - - $existingRemote = Git::RemoteUrl($repo_dir); - $defaultRemote = $existingRemote ?: ''; - - $question = new Question( - ' GitHub URL: ' . ($defaultRemote ? "[{$defaultRemote}] " : ''), - $defaultRemote - ); - $gitRemote = $helper->ask($input, $output, $question); - - if ($gitRemote) { - // Set or update the remote - if (!$existingRemote) { - Shell::run("git -C " . escapeshellarg($repo_dir) . " remote add origin " . escapeshellarg($gitRemote) . " 2>/dev/null"); - } elseif ($existingRemote !== $gitRemote) { - Shell::run("git -C " . escapeshellarg($repo_dir) . " remote set-url origin " . escapeshellarg($gitRemote) . " 2>/dev/null"); - } + $hasDockerCompose = file_exists(rtrim($repo_dir, '/') . '/docker-compose.yml'); + $totalSteps = $hasDockerCompose ? 1 : 2; + + // Step: Docker container (only if no docker-compose.yml) + if (!$hasDockerCompose) { + $this->writeStep($output, ++$step, $totalSteps, 'Docker Container'); + $output->writeln(" Protocol manages the Docker container for your project."); + $output->writeln(" No docker-compose.yml was found. We'll set one up for you."); + $output->writeln(''); + $output->writeln(" We suggest going with the latest in-house container."); + $output->writeln(" You can also choose an older version or enter your own."); $output->writeln(''); - $output->writeln(" ✓ Remote: {$gitRemote}"); + + $selectedInitializer = $this->selectProjectType($input, $output, $helper); + $selectedInitializer->initialize($repo_dir, $input, $output, $helper); } else { $output->writeln(''); - $output->writeln(" ! No URL provided — skipping remote setup"); + $output->writeln(" ✓ docker-compose.yml detected"); + $initializers = $this->getAvailableInitializers(); + $selectedInitializer = reset($initializers); } - // Use default initializer — existing projects manage their own Docker setup - $initializers = $this->getAvailableInitializers(); - $selectedInitializer = reset($initializers); + // Step: Create protocol.json + $this->writeStep($output, ++$step, $totalSteps, 'Protocol Configuration'); $selectedKey = $this->getInitializerKey($selectedInitializer); $selectedInitializer->createProtocolJson($repo_dir, $selectedKey, $output, self::SCHEMA_VERSION); - // Step: Deployment strategy - $this->writeStep($output, ++$step, $totalSteps, 'Deployment Strategy'); - $output->writeln(" This controls how code gets to your servers. Release-based"); - $output->writeln(" creates tagged versions you can roll back. Branch-based"); - $output->writeln(" just follows the tip of a branch."); - $output->writeln(''); - $this->configureDeploymentStrategy($repo_dir, $input, $output, $helper); - - // Step: Secrets - $this->writeStep($output, ++$step, $totalSteps, 'Secrets Management'); - $this->configureSecrets($repo_dir, $input, $output, $helper); - - // Step: Config repo - $this->writeStep($output, ++$step, $totalSteps, 'Configuration Repository'); - $this->configureConfigRepo($repo_dir, $input, $output, $helper, $selectedInitializer); - // Done $this->writeCompletion($output, $repo_dir); @@ -397,7 +373,7 @@ protected function flowProtocolProject( return $this->flowFixMigrate($repo_dir, $input, $output, $helper, $io); case 'settings': - return $this->flowExistingProject($repo_dir, $input, $output, $helper, $io); + return $this->flowSetupProject($repo_dir, $input, $output, $helper, $io); case 'strategy': $output->writeln('');