Skip to content

Commit 52d5225

Browse files
[command] Add global dev-tools no-logo control for nested runs (#278)
* [command] Add global dev-tools no-logo control for nested runs * [tests] Fix no-logo option handling and assertions * Update wiki submodule pointer for PR #278 * Refactor logo display in DevTools class Signed-off-by: Felipe Sayão Lobato Abreu <github@mentordosnerds.com> * fix: add coverage metadata for logo banner behavior * fix: hide logo for JSON output modes * test: add coverage metadata for tests command --------- Signed-off-by: Felipe Sayão Lobato Abreu <github@mentordosnerds.com> Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 8c0b865 commit 52d5225

8 files changed

Lines changed: 277 additions & 13 deletions

File tree

.github/wiki

Submodule wiki updated from f1a8bc3 to 4782e85

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- Show the DevTools ASCII logo by default on all top-level command executions, while adding a `--no-logo` global option and automatically suppressing the banner for `--json` / `--pretty-json` invocations (including automatic forwarding of `--no-logo` to internal DevTools subprocesses) to avoid banner repetition in orchestrated command queues (#277)
13+
1014
### Added
1115

1216
- Add a configurable DevTools generated artifact workspace through `--workspace-dir` and `FAST_FORWARD_WORKSPACE_DIR`, keeping explicit output/cache command options authoritative (#274)

src/Console/DevTools.php

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ final class DevTools extends Application
5353
| | | |/ _ \ \ / / | |/ _ \ / _ \| / __|
5454
| |_| | __/\ V / | | (_) | (_) | \__ \
5555
|____/ \___| \_/ |_|\___/ \___/|_|___/
56+
========================================
57+
5658
LOGO;
5759

5860
/**
@@ -87,17 +89,6 @@ public function __construct(
8789
$this->setCommandLoader($commandLoader);
8890
}
8991

90-
/**
91-
* Gets the help message for the DevTools application, including the ASCII logo.
92-
*
93-
* @return string
94-
*/
95-
#[Override]
96-
public function getHelp(): string
97-
{
98-
return self::LOGO . "\n\n" . parent::getHelp();
99-
}
100-
10192
/**
10293
* Returns the application-level input definition with DevTools runtime options.
10394
*
@@ -128,6 +119,12 @@ protected function getDefaultInputDefinition(): InputDefinition
128119
description: 'Store generated DevTools artifacts in the given directory.',
129120
));
130121

122+
$definition->addOption(new InputOption(
123+
name: 'no-logo',
124+
mode: InputOption::VALUE_NONE,
125+
description: 'Hide the startup ASCII logo.',
126+
));
127+
131128
return $definition;
132129
}
133130

@@ -142,6 +139,14 @@ protected function getDefaultInputDefinition(): InputDefinition
142139
#[Override]
143140
public function doRun(InputInterface $input, OutputInterface $output): int
144141
{
142+
$noLogo = (bool) $input->getParameterOption('--no-logo', null, true)
143+
|| (bool) $input->hasParameterOption('--json', true)
144+
|| (bool) $input->hasParameterOption('--pretty-json', true);
145+
146+
if (! $noLogo) {
147+
$output->writeln(self::LOGO);
148+
}
149+
145150
try {
146151
$this->workingDirectorySwitcher->switchTo($this->getWorkingDirectoryOption($input));
147152
$this->configureWorkspaceDirectory($input);
@@ -151,7 +156,7 @@ public function doRun(InputInterface $input, OutputInterface $output): int
151156
return Command::FAILURE;
152157
}
153158

154-
if (! $this->isSelfUpdateCommand($input)) {
159+
if (! $noLogo && ! $this->isSelfUpdateCommand($input)) {
155160
$this->runAutoUpdateWhenRequested($input, $output);
156161
$this->versionCheckNotifier->notify($output);
157162
}

src/Process/ProcessBuilder.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
namespace FastForward\DevTools\Process;
2121

22+
use FastForward\DevTools\Path\DevToolsPathResolver;
2223
use Symfony\Component\Process\Process;
2324

2425
/**
@@ -31,6 +32,8 @@
3132
*/
3233
final readonly class ProcessBuilder implements ProcessBuilderInterface
3334
{
35+
private const string NO_LOGO_ARGUMENT = '--no-logo';
36+
3437
/**
3538
* Creates a new immutable process builder instance.
3639
*
@@ -94,10 +97,53 @@ public function getArguments(): array
9497
*/
9598
public function build(string|array $command): Process
9699
{
100+
if (\is_array($command)) {
101+
$command = array_values($command);
102+
}
103+
97104
if (\is_string($command)) {
98105
$command = explode(' ', $command);
99106
}
100107

108+
if ($this->shouldAddLogoSuppressionArgument($command)) {
109+
$command = $this->prependLogoSuppressionArgument($command);
110+
}
111+
101112
return new Process(command: [...$command, ...$this->arguments], timeout: 0);
102113
}
114+
115+
/**
116+
* @param list<string> $command
117+
*/
118+
private function shouldAddLogoSuppressionArgument(array $command): bool
119+
{
120+
if (\in_array(self::NO_LOGO_ARGUMENT, $this->arguments, true)) {
121+
return false;
122+
}
123+
124+
if ([] === $command) {
125+
return false;
126+
}
127+
128+
$binary = str_replace('\\', '/', $command[0]);
129+
$packageBinaryPath = str_replace('\\', '/', DevToolsPathResolver::getBinaryPath());
130+
131+
return $binary === $packageBinaryPath;
132+
}
133+
134+
/**
135+
* @param list<string> $command
136+
*
137+
* @return list<string>
138+
*/
139+
private function prependLogoSuppressionArgument(array $command): array
140+
{
141+
if ([] === $command) {
142+
return $command;
143+
}
144+
145+
$binary = array_shift($command);
146+
147+
return [$binary, self::NO_LOGO_ARGUMENT, ...$command];
148+
}
103149
}

tests/Console/Command/DependenciesCommandTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use FastForward\DevTools\Console\Command\DependenciesCommand;
2323
use FastForward\DevTools\Console\Command\Traits\LogsCommandResults;
2424
use FastForward\DevTools\Config\ComposerDependencyAnalyserConfig;
25+
use FastForward\DevTools\Path\DevToolsPathResolver;
2526
use FastForward\DevTools\Process\ProcessBuilder;
2627
use FastForward\DevTools\Process\ProcessBuilderInterface;
2728
use FastForward\DevTools\Process\ProcessQueueInterface;
@@ -42,6 +43,7 @@
4243
use Symfony\Component\Process\Process;
4344

4445
#[CoversClass(DependenciesCommand::class)]
46+
#[UsesClass(DevToolsPathResolver::class)]
4547
#[UsesClass(ProcessBuilder::class)]
4648
#[UsesTrait(LogsCommandResults::class)]
4749
final class DependenciesCommandTest extends TestCase

tests/Console/Command/TestsCommandTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use FastForward\DevTools\Process\ProcessBuilder;
2929
use FastForward\DevTools\Process\ProcessQueueInterface;
3030
use FastForward\DevTools\Path\ManagedWorkspace;
31+
use FastForward\DevTools\Path\DevToolsPathResolver;
3132
use PHPUnit\Framework\Attributes\CoversClass;
3233
use PHPUnit\Framework\Attributes\Test;
3334
use PHPUnit\Framework\Attributes\UsesClass;
@@ -48,6 +49,7 @@
4849

4950
#[CoversClass(TestsCommand::class)]
5051
#[UsesClass(CoverageSummary::class)]
52+
#[UsesClass(DevToolsPathResolver::class)]
5153
#[UsesClass(ProcessBuilder::class)]
5254
#[UsesClass(ManagedWorkspace::class)]
5355
#[UsesTrait(LogsCommandResults::class)]

tests/Console/DevToolsTest.php

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,11 @@
6262
use Symfony\Component\Console\Command\HelpCommand;
6363
use Symfony\Component\Console\Command\ListCommand;
6464
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
65+
use Symfony\Component\Console\Input\ArrayInput;
66+
use Symfony\Component\Console\Input\InputOption;
6567
use Symfony\Component\Console\Input\InputInterface;
6668
use Symfony\Component\Console\Output\OutputInterface;
69+
use Symfony\Component\Console\Output\BufferedOutput;
6770

6871
use function Safe\putenv;
6972

@@ -215,6 +218,148 @@ public function constructorWillRegisterGlobalRuntimeOptions(): void
215218
self::assertTrue($definition->hasOption('workspace-dir'));
216219
self::assertSame('w', $definition->getOption('workspace-dir')->getShortcut());
217220
self::assertTrue($definition->hasOption('auto-update'));
221+
self::assertTrue($definition->hasOption('no-logo'));
222+
self::assertFalse($definition->getOption('no-logo')->acceptValue());
223+
}
224+
225+
/**
226+
* @return void
227+
*/
228+
#[Test]
229+
public function doRunWillRenderLogoUnlessNoLogoOptionIsProvided(): void
230+
{
231+
$input = new ArrayInput([
232+
'command' => 'list',
233+
]);
234+
235+
$output = new BufferedOutput();
236+
237+
$this->environment->get('FAST_FORWARD_AUTO_UPDATE', '')
238+
->willReturn('');
239+
$this->workingDirectorySwitcher->switchTo(null)
240+
->shouldBeCalledOnce();
241+
$this->versionCheckNotifier->notify($output)
242+
->shouldBeCalledOnce();
243+
244+
$result = $this->invokeDoRun($input, $output);
245+
246+
self::assertSame(Command::SUCCESS, $result);
247+
self::assertStringContainsString('_____', $output->fetch());
248+
}
249+
250+
/**
251+
* @return void
252+
*/
253+
#[Test]
254+
public function doRunWillNotRenderLogoWhenNoLogoOptionIsSet(): void
255+
{
256+
$input = new ArrayInput([
257+
'--no-logo' => true,
258+
'command' => 'list',
259+
]);
260+
261+
$output = new BufferedOutput();
262+
263+
$this->environment->get('FAST_FORWARD_AUTO_UPDATE', '')
264+
->willReturn('');
265+
$this->workingDirectorySwitcher->switchTo(null)
266+
->shouldBeCalledOnce();
267+
$this->versionCheckNotifier->notify($output)
268+
->shouldNotBeCalled();
269+
270+
$this->invokeDoRun($input, $output);
271+
272+
self::assertStringNotContainsString('_____', $output->fetch());
273+
}
274+
275+
/**
276+
* @return void
277+
*/
278+
#[Test]
279+
public function doRunWillNotRenderLogoWhenJsonOptionIsProvided(): void
280+
{
281+
$command = new class extends Command {
282+
public function __construct()
283+
{
284+
parent::__construct('standards');
285+
}
286+
287+
protected function configure(): void
288+
{
289+
$this->addOption(name: 'json', mode: InputOption::VALUE_NONE, description: 'Emit structured JSON output.');
290+
$this->setCode(static fn(InputInterface $input, OutputInterface $output): int => Command::SUCCESS);
291+
}
292+
};
293+
294+
$this->commandLoader->has('standards')
295+
->willReturn(true)
296+
->shouldBeCalledOnce();
297+
$this->commandLoader->get('standards')
298+
->willReturn($command)
299+
->shouldBeCalledOnce();
300+
$input = new ArrayInput([
301+
'command' => 'standards',
302+
'--json' => true,
303+
]);
304+
305+
$output = new BufferedOutput();
306+
307+
$this->environment->get('FAST_FORWARD_AUTO_UPDATE', '')
308+
->willReturn('');
309+
$this->workingDirectorySwitcher->switchTo(null)
310+
->shouldBeCalledOnce();
311+
$this->versionCheckNotifier->notify($output)
312+
->shouldNotBeCalled();
313+
314+
$result = $this->invokeDoRun($input, $output);
315+
316+
self::assertSame(Command::SUCCESS, $result);
317+
self::assertStringNotContainsString('_____', $output->fetch());
318+
}
319+
320+
/**
321+
* @return void
322+
*/
323+
#[Test]
324+
public function doRunWillNotRenderLogoWhenPrettyJsonOptionIsProvided(): void
325+
{
326+
$command = new class extends Command {
327+
public function __construct()
328+
{
329+
parent::__construct('standards');
330+
}
331+
332+
protected function configure(): void
333+
{
334+
$this->addOption(name: 'pretty-json', mode: InputOption::VALUE_NONE, description: 'Emit pretty JSON output.');
335+
$this->setCode(static fn(InputInterface $input, OutputInterface $output): int => Command::SUCCESS);
336+
}
337+
};
338+
339+
$this->commandLoader->has('standards')
340+
->willReturn(true)
341+
->shouldBeCalledOnce();
342+
$this->commandLoader->get('standards')
343+
->willReturn($command)
344+
->shouldBeCalledOnce();
345+
$input = new ArrayInput([
346+
'command' => 'standards',
347+
'--pretty-json' => true,
348+
]);
349+
350+
$output = new BufferedOutput();
351+
352+
$this->environment->get('FAST_FORWARD_AUTO_UPDATE', '')
353+
->willReturn('');
354+
$this->workingDirectorySwitcher->switchTo(null)
355+
->shouldBeCalledOnce();
356+
$this->versionCheckNotifier->notify($output)
357+
->shouldNotBeCalled();
358+
359+
$result = $this->invokeDoRun($input, $output);
360+
361+
self::assertSame(Command::SUCCESS, $result);
362+
self::assertStringNotContainsString('_____', $output->fetch());
218363
}
219364

220365
/**
@@ -404,4 +549,17 @@ private function invokeConfigureWorkspaceDirectory(InputInterface $input): void
404549
$reflectionMethod = new ReflectionMethod($this->devTools, 'configureWorkspaceDirectory');
405550
$reflectionMethod->invoke($this->devTools, $input);
406551
}
552+
553+
/**
554+
* @param InputInterface $input
555+
* @param OutputInterface $output
556+
*
557+
* @return int
558+
*/
559+
private function invokeDoRun(InputInterface $input, OutputInterface $output): int
560+
{
561+
$reflectionMethod = new ReflectionMethod($this->devTools, 'doRun');
562+
563+
return (int) $reflectionMethod->invoke($this->devTools, $input, $output);
564+
}
407565
}

0 commit comments

Comments
 (0)