From 72760e1e98255cdb8a28b51b50c9a451847e3dc2 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Tue, 6 Oct 2026 23:36:54 +0200 Subject: [PATCH 1/2] Add `bref deploy --param` for serverless.yml applications The params are forwarded as-is to osls, on both `serverless deploy` and the `serverless info` that follows it, so that `${param:...}` variables resolve in both. bref.php applications reject the option. Claude-Session: https://claude.ai/code/session_01Y4Gb7wKthAh4iXemSDps7M --- src/Commands/Deploy.php | 10 +++-- src/Components/ServerlessFramework.php | 47 +++++++++++++------- tests/Commands/DeployTest.php | 30 +++++++++++++ tests/Components/ServerlessFrameworkTest.php | 23 ++++++++++ 4 files changed, 90 insertions(+), 20 deletions(-) create mode 100644 tests/Commands/DeployTest.php diff --git a/src/Commands/Deploy.php b/src/Commands/Deploy.php index ab654d7..4bac4be 100644 --- a/src/Commands/Deploy.php +++ b/src/Commands/Deploy.php @@ -40,7 +40,8 @@ protected function configure(): void ->setName('deploy') ->setDescription('Deploy the application') ->addOption('force', null, InputOption::VALUE_NONE, 'Force the deployment') - ->addOption('osls4', null, InputOption::VALUE_NONE, 'Use osls@4 instead of osls@3'); + ->addOption('osls4', null, InputOption::VALUE_NONE, 'Use osls@4 instead of osls@3') + ->addOption('param', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Pass a parameter to serverless.yml, e.g. --param="key=value" (can be repeated)'); parent::configure(); } @@ -54,6 +55,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int 'config' => $config, ] = $this->parseStandardOptions($input); + $isServerlessFrameworkDeploy = $config['type'] === 'serverless-framework'; + if (! $isServerlessFrameworkDeploy && $input->getOption('param')) { + throw new Exception('The --param option is only supported for serverless.yml applications'); + } + $brefCloud = new BrefCloudClient; IO::writeln([ @@ -105,8 +111,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int IO::writeln("" . Styles::gray($deployment['url']) . ''); - $isServerlessFrameworkDeploy = $config['type'] === 'serverless-framework'; - if ($isServerlessFrameworkDeploy) { if ($credentials === null) { IO::spinError(); diff --git a/src/Components/ServerlessFramework.php b/src/Components/ServerlessFramework.php index c597c28..fb7bf75 100644 --- a/src/Components/ServerlessFramework.php +++ b/src/Components/ServerlessFramework.php @@ -28,18 +28,11 @@ class ServerlessFramework */ public function deploy(int $deploymentId, string $environment, array $awsCredentials, BrefCloudClient $brefCloud, InputInterface $input): void { - $options = []; + $commonOptions = $this->commonOptions($input); + $options = $commonOptions; if ($input->hasOption('force')) { $options[] = '--force'; } - $configOption = $input->getOption('config'); - if ($input->hasOption('config') && is_string($configOption) && $configOption !== '') { - $configFile = $configOption; - $options[] = '--config'; - $options[] = $configFile; - } else { - $configFile = null; - } $oslsPackage = ($input->hasOption('osls4') && $input->getOption('osls4')) ? 'osls@4' : 'osls@3'; $newLogs = ''; @@ -102,7 +95,7 @@ public function deploy(int $deploymentId, string $environment, array $awsCredent $hasChanges = ! str_contains($newLogs, 'No changes to deploy. Deployment skipped.'); if ($hasChanges) { - $outputs = $this->retrieveOutputs($oslsPackage, $environment, $awsCredentials, $configFile); + $outputs = $this->retrieveOutputs($oslsPackage, $environment, $awsCredentials, $commonOptions); $region = $outputs['region']; $stackName = $outputs['stack']; @@ -123,6 +116,31 @@ public function deploy(int $deploymentId, string $environment, array $awsCredent } } + /** + * The options that both `serverless deploy` and the `serverless info` that follows it need, to resolve the + * same configuration (e.g. `${param:...}` variables). + * + * @return list + */ + public function commonOptions(InputInterface $input): array + { + $options = []; + $configFile = $input->hasOption('config') ? $input->getOption('config') : null; + if (is_string($configFile) && $configFile !== '') { + $options[] = '--config'; + $options[] = $configFile; + } + $params = $input->hasOption('param') ? $input->getOption('param') : []; + foreach (is_array($params) ? $params : [] as $param) { + if (is_string($param)) { + $options[] = '--param'; + $options[] = $param; + } + } + + return $options; + } + /** * The stack that the deployment created, if it did. * @@ -150,17 +168,12 @@ public function findCreatedStack(string $output): ?array /** * @param array{ accessKeyId: string, secretAccessKey: string, sessionToken: string } $awsCredentials + * @param list $options * @return array * @throws Exception */ - private function retrieveOutputs(string $oslsPackage, string $environment, array $awsCredentials, ?string $configFile): array + private function retrieveOutputs(string $oslsPackage, string $environment, array $awsCredentials, array $options): array { - $options = []; - if ($configFile) { - $options[] = '--config'; - $options[] = $configFile; - } - $process = $this->serverlessExec($oslsPackage, 'info', $environment, $awsCredentials, $options); $process->join(); $infoOutput = buffer($process->getStdout()); diff --git a/tests/Commands/DeployTest.php b/tests/Commands/DeployTest.php new file mode 100644 index 0000000..c0131cc --- /dev/null +++ b/tests/Commands/DeployTest.php @@ -0,0 +1,30 @@ + "shop", "team" => "acme", "type" => "laravel"]);'); + $input = new ArrayInput(['--config' => $configFile, '--param' => ['domain=example.com']]); + $input->setInteractive(false); + $output = new BufferedOutput; + IO::init($input, $output); + + try { + $this->expectException(Exception::class); + $this->expectExceptionMessage('The --param option is only supported for serverless.yml applications'); + (new Deploy)->run($input, $output); + } finally { + unlink($configFile); + } + } +} diff --git a/tests/Components/ServerlessFrameworkTest.php b/tests/Components/ServerlessFrameworkTest.php index a146fdc..4b83430 100644 --- a/tests/Components/ServerlessFrameworkTest.php +++ b/tests/Components/ServerlessFrameworkTest.php @@ -2,12 +2,35 @@ namespace Bref\Cli\Test\Components; +use Bref\Cli\Commands\Deploy; use Bref\Cli\Components\ServerlessFramework; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Input\ArrayInput; class ServerlessFrameworkTest extends TestCase { + public function test_forwards_the_config_file_and_the_params_to_serverless(): void + { + $input = new ArrayInput([ + '--config' => 'serverless.prod.yml', + '--param' => ['layer=arn:aws:lambda:us-east-1:123:layer:php-85:1', 'domain=example.com'], + ], (new Deploy)->getDefinition()); + + $this->assertSame([ + '--config', 'serverless.prod.yml', + '--param', 'layer=arn:aws:lambda:us-east-1:123:layer:php-85:1', + '--param', 'domain=example.com', + ], (new ServerlessFramework)->commonOptions($input)); + } + + public function test_no_options_by_default(): void + { + $input = new ArrayInput([], (new Deploy)->getDefinition()); + + $this->assertSame([], (new ServerlessFramework)->commonOptions($input)); + } + /** * @param array{string, string}|null $expected */ From d3609ea36838a8bf9a2720958e80229233569273 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Tue, 6 Oct 2026 23:38:27 +0200 Subject: [PATCH 2/2] Fix PHPStan with older Symfony Console versions Claude-Session: https://claude.ai/code/session_01Y4Gb7wKthAh4iXemSDps7M --- src/Components/ServerlessFramework.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Components/ServerlessFramework.php b/src/Components/ServerlessFramework.php index fb7bf75..672ea59 100644 --- a/src/Components/ServerlessFramework.php +++ b/src/Components/ServerlessFramework.php @@ -130,12 +130,11 @@ public function commonOptions(InputInterface $input): array $options[] = '--config'; $options[] = $configFile; } + /** @var list $params */ $params = $input->hasOption('param') ? $input->getOption('param') : []; - foreach (is_array($params) ? $params : [] as $param) { - if (is_string($param)) { - $options[] = '--param'; - $options[] = $param; - } + foreach ($params as $param) { + $options[] = '--param'; + $options[] = $param; } return $options;