diff --git a/src/Cli/IO.php b/src/Cli/IO.php index b156937..a533366 100644 --- a/src/Cli/IO.php +++ b/src/Cli/IO.php @@ -62,6 +62,17 @@ public static function writeln(string|array $messages): void self::writeToLogsFile($messages); } + /** + * Write on stdout as is: text that looks like Symfony Console tags (``, ``) is not interpreted. + * For text that doesn't come from the CLI, e.g. the output of a command run on Lambda. + * @param string|string[] $messages + */ + public static function writeRaw(string|array $messages): void + { + self::safeWrite($messages, OutputInterface::OUTPUT_RAW); + self::writeToLogsFile($messages); + } + /** * Log at the verbose level. * @param string|string[] $messages @@ -202,8 +213,9 @@ private static function doLogVerbose(string|array $messages): void /** * @param string|string[] $messages + * @param OutputInterface::OUTPUT_NORMAL|OutputInterface::OUTPUT_RAW $options */ - private static function safeWrite(string|array $messages): void + private static function safeWrite(string|array $messages, int $options = OutputInterface::OUTPUT_NORMAL): void { if (OutputInterface::VERBOSITY_QUIET === self::$output->getVerbosity()) return; @@ -228,7 +240,7 @@ private static function safeWrite(string|array $messages): void stream_set_blocking(STDOUT, true); stream_set_blocking(STDERR, true); - self::$output->writeln($messages); + self::$output->writeln($messages, $options); // Render the spinner again self::$spinner?->render(); diff --git a/src/Commands/Command.php b/src/Commands/Command.php index 8affa2c..b983792 100644 --- a/src/Commands/Command.php +++ b/src/Commands/Command.php @@ -57,7 +57,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($invocation['status'] === 'success') { IO::spinClear(); - IO::writeln($invocation['output']); + // As is: the output of the command is not formatted for Symfony Console + IO::writeRaw($invocation['output']); return 0; } @@ -86,7 +87,7 @@ private function writeErrorDetails(string $output): void || ! isset($errorDetails['errorType'], $errorDetails['errorMessage']) || ! is_string($errorDetails['errorType']) || ! is_string($errorDetails['errorMessage'])) { - IO::writeln(Styles::red($output)); + IO::writeRaw(Styles::red($output)); return; } @@ -100,14 +101,15 @@ private function writeErrorDetails(string $output): void '', Styles::bold(Styles::red('ERROR')) . ' ' . Styles::gray($errorType), '', - $errorDetails['errorMessage'], ]); + // The output of the command + IO::writeRaw($errorDetails['errorMessage']); if (isset($errorDetails['stackTrace']) && is_array($errorDetails['stackTrace'])) { $stackTrace = array_values(array_filter($errorDetails['stackTrace'], 'is_string')); IO::verbose($stackTrace); } } catch (JsonException) { - IO::writeln(Styles::red($output)); + IO::writeRaw(Styles::red($output)); } } } diff --git a/tests/Commands/CommandTest.php b/tests/Commands/CommandTest.php index 18ee731..78ae9a1 100644 --- a/tests/Commands/CommandTest.php +++ b/tests/Commands/CommandTest.php @@ -42,15 +42,34 @@ public function test_no_colors_when_the_output_is_not_a_terminal(): void $this->assertFalse($this->requestBodies['/api/v1/commands/start']['ansi']); } + public function test_the_output_is_printed_as_is(): void + { + // Text that looks like Symfony Console tags is not interpreted: it's the output of the application + $output = 'Hello world, a link'; + + [$status, $display] = $this->runCommand(decorated: true, command: ['status' => 'success', 'output' => $output]); + $this->assertSame(0, $status, $display); + $this->assertStringContainsString($output, $display); + + [$status, $display] = $this->runCommand(decorated: false, command: [ + 'status' => 'failed', + 'output' => json_encode(['errorType' => 'Bref\ConsoleRuntime\CommandFailed', 'errorMessage' => $output]), + ]); + $this->assertSame(1, $status, $display); + $this->assertStringContainsString($output, $display); + } + /** + * @param array{status: string, output: string} $command What Bref Cloud returns for the command * @return array{int, string} The exit code and the output */ - private function runCommand(bool $decorated): array + private function runCommand(bool $decorated, array $command = ['status' => 'success', 'output' => "\e[32mLaravel\e[39m 13"]): array { + $this->requestBodies = []; $command = new Command($this->brefCloud([ '/api/v1/environments/find' => $this->environment(), '/api/v1/commands/start' => ['id' => 5], - '/api/v1/commands/5' => ['status' => 'success', 'output' => "\e[32mLaravel\e[39m 13"], + '/api/v1/commands/5' => $command, ])); $input = new ArrayInput(['args' => 'about', '--config' => $this->configFile]); $input->setInteractive(false);