feat: Add init command#182
Merged
Merged
Conversation
Contributor
Author
Contributor
Author
|
@roxblnfk review pls |
There was a problem hiding this comment.
Pull request overview
Adds an init command to the Symfony Console bridge so projects can bootstrap Testo configuration, test directories, and a Composer script.
Changes:
- Added a new
Initconsole command. - Added a
testo.phpconfiguration stub. - Registered the
initcommand in the CLI entrypoint.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
bridge/symfony-console/src/Command/Init.php |
Implements the new project initialization flow. |
bridge/symfony-console/resources/stubs/testo.php |
Provides the generated configuration template. |
bridge/symfony-console/bin/testo |
Registers the new init command with the console application. |
Comments suppressed due to low confidence (2)
bridge/symfony-console/src/Command/Init.php:136
- When
testo.phpdoes not already exist, this block is skipped entirely, so the init command never writes the new configuration file on a fresh project. The stub generation should also run for the missing-file case; otherwise the final message points to a configuration file that was not created.
if (\is_file(self::DESTINATION)) {
if (!$input->isInteractive()) {
$io->warning('testo.php already exists. Skipping (non-interactive mode).');
return Command::SUCCESS;
}
if ($io->confirm('testo.php already exists. Overwrite?', false)) {
bridge/symfony-console/src/Command/Init.php:140
- The raw path values are interpolated into PHP single-quoted strings in the stub. A user-supplied path containing an apostrophe (or other PHP string-sensitive characters) will generate an invalid or incorrect
testo.php; the generated values should be escaped/serialized before being written into PHP code.
$stubContent = \str_replace(
['__SRC_PATH__', '__TESTS_UNIT_PATH__'],
[(string) $srcPath, (string) $testsUnitPath],
\file_get_contents(self::STUB),
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
roxblnfk
reviewed
May 15, 2026
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 3 changed files in this pull request and generated 6 comments.
Comments suppressed due to low confidence (2)
bridge/symfony-console/src/Command/Init.php:153
file_get_contents(self::STUB)isn’t checked for failure. If the stub path is missing/unreadable, this will produce an invalid/emptytesto.php. Consider validating the stub read and failing the command with a clear error message instead of proceeding.
['__SRC_PATH__', '__SUITES__'],
[(string) $srcPath, $suitesCode],
\file_get_contents(self::STUB),
);
\file_put_contents(self::DESTINATION, $stubContent);
bridge/symfony-console/src/Command/Init.php:155
file_put_contents(self::DESTINATION, ...)isn’t checked. If writingtesto.phpfails (permissions, existing file locked), the command will still report success. Consider checking forfalseand returning Command::FAILURE with an actionable error message.
\file_get_contents(self::STUB),
);
\file_put_contents(self::DESTINATION, $stubContent);
$io->success('Created testo.php');
}
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Comment on lines
+89
to
+93
| if ($path->isDir()) { | ||
| return; | ||
| } | ||
|
|
||
| \mkdir((string) $path, 0755, true); |
Comment on lines
+57
to
+67
| $basePath = Path::create((string) $input->getOption('path')); | ||
| self::ensureDirectory($basePath, $io); | ||
|
|
||
| $srcPath = self::discoverSourceDirectory($input, $io); | ||
|
|
||
| $testsPath = $basePath->join('tests'); | ||
| self::ensureDirectory($testsPath, $io); | ||
|
|
||
| $suites = self::discoverSuites($testsPath, $io); | ||
| $composerKeys = self::updateComposerScripts($suites, $basePath, $io); | ||
|
|
| } | ||
|
|
||
| if (!$input->isInteractive()) { | ||
| throw new \RuntimeException('src/ directory not found. Skipping (non-interactive mode).'); |
Comment on lines
+162
to
+181
| /** @var array{scripts?: array<string, string>}&array<string, mixed> $composer */ | ||
| $composer = \json_decode( | ||
| \file_get_contents((string) $composerJsonPath), | ||
| associative: true, | ||
| flags: \JSON_THROW_ON_ERROR, | ||
| ); | ||
|
|
||
| if (!isset($composer['scripts'][self::SCRIPT_ALL_KEY])) { | ||
| $composer['scripts'][self::SCRIPT_ALL_KEY] = self::SCRIPT_ALL_COMMAND; | ||
| $io->success(\sprintf('Added "%s" script to composer.json', self::SCRIPT_ALL_KEY)); | ||
| } | ||
|
|
||
| foreach ($suites as $suite) { | ||
| $key = \sprintf(self::SCRIPT_SUITE_KEY_TEMPLATE, \strtolower($suite)); | ||
| $keys[] = $key; | ||
|
|
||
| if (!isset($composer['scripts'][$key])) { | ||
| $composer['scripts'][$key] = \sprintf(self::SCRIPT_SUITE_COMMAND_TEMPLATE, $suite); | ||
| $io->success(\sprintf('Added "%s" script to composer.json', $key)); | ||
| } |
Comment on lines
+184
to
+188
| \file_put_contents( | ||
| (string) $composerJsonPath, | ||
| \json_encode($composer, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE) . "\n", | ||
| ); | ||
|
|
| \file_get_contents(self::STUB), | ||
| ); | ||
|
|
||
| \file_put_contents((string) $configPath, $stub); |
Comment on lines
+155
to
+161
| $keys = [self::SCRIPT_ALL_KEY]; | ||
|
|
||
| $composerJsonPath = $basePath->join('composer.json'); | ||
| if (!$composerJsonPath->isFile()) { | ||
| return $keys; | ||
| } | ||
|
|
Comment on lines
+208
to
+250
| public function customPathPlacesTestsAndConfigUnderSubdirectory(): void | ||
| { | ||
| $this->sandbox->makeDir('src'); | ||
| $this->sandbox->writeFile( | ||
| 'app/composer.json', | ||
| \json_encode(['name' => 'acme/sub-app'], \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES) . "\n", | ||
| ); | ||
| $rootComposer = \json_encode(['name' => 'acme/root'], \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES) . "\n"; | ||
| $this->sandbox->writeFile('composer.json', $rootComposer); | ||
|
|
||
| $tester = $this->run(['--path' => 'app']); | ||
|
|
||
| Assert::same( | ||
| $tester->getStatusCode(), | ||
| Command::SUCCESS, | ||
| 'init must succeed when --path points at a subdirectory; output: ' . $tester->getDisplay(), | ||
| ); | ||
|
|
||
| Assert::true(\is_dir($this->sandbox->path('app/tests/Unit')), 'tests/Unit/ must be created under --path'); | ||
| Assert::true(\is_file($this->sandbox->path('app/testo.php')), 'testo.php must be created under --path'); | ||
|
|
||
| Assert::false( | ||
| \is_file($this->sandbox->path('testo.php')), | ||
| 'no testo.php must be created at the project root when --path is set', | ||
| ); | ||
| Assert::false( | ||
| \is_dir($this->sandbox->path('tests')), | ||
| 'no tests/ must be created at the project root when --path is set', | ||
| ); | ||
|
|
||
| $subScripts = $this->readComposerScripts('app/composer.json'); | ||
| Assert::same( | ||
| $subScripts['test:unit'] ?? null, | ||
| 'vendor/bin/testo --suite=Unit', | ||
| 'composer scripts must be written to the composer.json colocated with --path', | ||
| ); | ||
|
|
||
| Assert::same( | ||
| \file_get_contents($this->sandbox->path('composer.json')), | ||
| $rootComposer, | ||
| 'the project-root composer.json must be left untouched when --path is set', | ||
| ); | ||
| } |
| { | ||
| $target = $this->path . \DIRECTORY_SEPARATOR . \ltrim($relative, '/\\'); | ||
| $dir = \dirname($target); | ||
| \is_dir($dir) or \mkdir($dir, 0o755, true); |
|
|
||
| public function destroy(): void | ||
| { | ||
| \chdir($this->originalCwd); |
roxblnfk
approved these changes
May 20, 2026
This was referenced May 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was changed
See issue 174
Checklist