Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions src/Commands/ConfigNew.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,8 @@ protected function configure(): void
HELP)
;
$this
// configure an argument
->addArgument('environment', InputArgument::OPTIONAL, 'Name for the new environment (e.g., production, staging)')
->addOption('dir', 'd', InputOption::VALUE_OPTIONAL, 'Directory Path', Git::getGitLocalFolder())
// ...
;
}

Expand Down Expand Up @@ -108,8 +107,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}

// get the correct environment
$question = new Question('New Environment Name: ');
$newName = $helper->ask($input, $output, $question);
$newName = $input->getArgument('environment');
if (!$newName) {
$question = new Question('New Environment Name: ');
$newName = $helper->ask($input, $output, $question);
}

$slug = Str::slugify( $newName );
if ($newName != $slug) {
Expand Down
72 changes: 58 additions & 14 deletions src/Commands/ConfigSwitch.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,32 +106,76 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
}

$newName = $input->getArgument('environment', false);
if (!$newName) {
// get the correct environment
Git::fetch( $configrepo );
$branches = Git::branches( $configrepo );
$branchStr = implode(', ',$branches);
$currentBranch = Git::branch( $configrepo );
// Always fetch to ensure we have remote branches
Git::fetch( $configrepo );

$currentBranch = Git::branch( $configrepo );
$branches = Git::branches( $configrepo );

$newName = $input->getArgument('environment');
if (!$newName) {
$branchStr = implode(', ', $branches);
$output->writeln("<info>You have the following environments: $branchStr</info>");
$question = new Question("You are on env ($currentBranch), switch to what environment?: ");
$newName = $helper->ask($input, $output, $question);
}

if (!in_array($newName, $branches)) {
$output->writeln("<info>That's not a valid branch, quitting...</info>");
return Command::SUCCESS;
if (!$newName) {
$output->writeln("<error>No environment specified.</error>");
return Command::FAILURE;
}

// Check if branch exists locally or as a remote
$escapedRepo = escapeshellarg($configrepo);
$localExists = trim(Shell::run("git -C {$escapedRepo} branch --list " . escapeshellarg($newName) . " 2>/dev/null") ?: '');
$remoteExists = trim(Shell::run("git -C {$escapedRepo} branch -r --list " . escapeshellarg("*/{$newName}") . " 2>/dev/null") ?: '');

if (!$localExists && !$remoteExists) {
$question = new ConfirmationQuestion(
"Environment \"{$newName}\" does not exist. Create it? [Y/n] ",
true
);
if (!$helper->ask($input, $output, $question)) {
$output->writeln('Available: ' . implode(', ', $branches));
return Command::FAILURE;
}

// Create the new branch from current, then let the rest of the flow handle it
$command = $this->getApplication()->find('config:new');
$returnCode = $command->run(new ArrayInput([
'environment' => $newName,
'--dir' => $repo_dir,
]), $output);
return $returnCode;
}

if ($newName === $currentBranch) {
$output->writeln("<info>Already on {$newName}.</info>");
return Command::SUCCESS;
}

// Unlink current config
$command = $this->getApplication()->find('config:unlink');
$returnCode = $command->run((new ArrayInput(['--dir' => $repo_dir])), $output);
$command->run((new ArrayInput(['--dir' => $repo_dir])), $output);

// Switch branch — use checkout which auto-tracks remote branches
$result = Shell::run("git -C {$escapedRepo} checkout " . escapeshellarg($newName) . " 2>&1", $returnVar);

Git::switchBranch( $newName, $configrepo );
$output->writeln("<info>Switched! Your new environment is $newName.</info>");
// Verify the switch actually happened
$actualBranch = trim(Shell::run("git -C {$escapedRepo} rev-parse --abbrev-ref HEAD 2>/dev/null") ?: '');

if ($returnVar !== 0 || $actualBranch !== $newName) {
$output->writeln("<error>Failed to switch to {$newName}:</error>");
$output->writeln("<fg=gray>{$result}</>");
return Command::FAILURE;
}

$output->writeln("<info>Config repo switched to: {$newName}</info>");

// Re-link (decrypts .env.enc if encryption key is present)
$command = $this->getApplication()->find('config:link');
$returnCode = $command->run((new ArrayInput(['--dir' => $repo_dir])), $output);
$command->run((new ArrayInput(['--dir' => $repo_dir])), $output);

return Command::SUCCESS;
}

Expand Down
3 changes: 1 addition & 2 deletions src/Commands/SecretsSetup.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ protected function configure(): void
The storage location is determined by the environment set via
config:env. Production environments store the key globally in
protocol's .node/key. All other environments (local, dev, staging)
store the key per-project in the current working directory's
.protocol/key.
store the key per-project in the current working directory's .node/key.

The key is also read automatically from the PROTOCOL_ENCRYPTION_KEY
environment variable, so CI/CD workflows can pass it from GitHub secrets.
Expand Down
25 changes: 25 additions & 0 deletions src/Helpers/GitHubApp.php
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,30 @@ public static function writeGitCredentials(string $token): void
Shell::run("git config --global credential.helper 'store --file=" . escapeshellarg($credentialFile) . "'");
}

/**
* Write the GitHub App token to composer's global auth.json
* so composer can access GitHub API without rate-limit prompts.
*/
public static function writeComposerAuth(string $token): void
{
$composerHome = getenv('COMPOSER_HOME') ?: (getenv('HOME') ?: getenv('USERPROFILE')) . '/.config/composer';
$authFile = $composerHome . '/auth.json';

$auth = [];
if (is_file($authFile)) {
$auth = json_decode(file_get_contents($authFile), true) ?: [];
}

$auth['github-oauth'] = $auth['github-oauth'] ?? [];
$auth['github-oauth']['github.com'] = $token;

if (!is_dir($composerHome)) {
mkdir($composerHome, 0700, true);
}
file_put_contents($authFile, json_encode($auth, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n", LOCK_EX);
chmod($authFile, 0600);
}

/**
* Refresh the git credentials with a new installation token.
* Call this periodically (tokens expire after 1 hour).
Expand All @@ -251,6 +275,7 @@ public static function refreshGitCredentials(?string $owner = null): bool
return false;
}
self::writeGitCredentials($token);
self::writeComposerAuth($token);
return true;
}

Expand Down
45 changes: 43 additions & 2 deletions src/Plugins/awssecrets/AwsSecretsHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,17 @@ public static function defaultSecretName($repoDir = false): string
*/
public static function getClient($repoDir = false): SecretsManagerClient
{
return new SecretsManagerClient([
$config = [
'region' => self::region($repoDir),
'version' => 'latest',
]);
];

$profile = self::config('profile', null, $repoDir);
if ($profile) {
$config['profile'] = $profile;
}

return new SecretsManagerClient($config);
}

// ─── Logging ────────────────────────────────────────────────
Expand Down Expand Up @@ -147,6 +154,40 @@ public static function jsonToEnv(string $json): string
*
* @return bool True on success
*/
public static function pushSecretAs(string $envContents, string $secretName, $repoDir = false): bool
{
$client = self::getClient($repoDir);
$secretString = self::envToJson($envContents);

self::log("Pushing secret: {$secretName}");

try {
$client->putSecretValue([
'SecretId' => $secretName,
'SecretString' => $secretString,
]);
self::log("Secret updated: {$secretName}");
return true;
} catch (AwsException $e) {
if ($e->getAwsErrorCode() === 'ResourceNotFoundException') {
try {
$client->createSecret([
'Name' => $secretName,
'SecretString' => $secretString,
'Description' => 'Protocol managed environment secrets',
]);
self::log("Secret created: {$secretName}");
return true;
} catch (AwsException $createEx) {
self::log("ERROR creating secret: " . $createEx->getMessage());
return false;
}
}
self::log("ERROR pushing secret: " . $e->getMessage());
return false;
}
}

public static function pushSecret(string $envContents, $repoDir = false): bool
{
$client = self::getClient($repoDir);
Expand Down
Loading
Loading