diff --git a/README.md b/README.md index 84e90c7..3624bdf 100644 --- a/README.md +++ b/README.md @@ -352,6 +352,14 @@ vendor/bin/structarmed analyze --report=json vendor/bin/structarmed analyse --disable-parallel ``` +## CLI version commands + +```bash +# Print the installed StructArmed version +vendor/bin/structarmed --version +vendor/bin/structarmed -V +``` + ## Tips ### Rule key constants diff --git a/src/Cache/AnalysisCacheMetadataFactory.php b/src/Cache/AnalysisCacheMetadataFactory.php index fc5cf85..3545cde 100644 --- a/src/Cache/AnalysisCacheMetadataFactory.php +++ b/src/Cache/AnalysisCacheMetadataFactory.php @@ -4,6 +4,7 @@ namespace Boundwize\StructArmed\Cache; +use Boundwize\StructArmed\Version; use Composer\InstalledVersions; use function array_map; @@ -68,10 +69,10 @@ private function filesHash(array $files): string public function composerGeneratedVersionHash(): string { - $version = InstalledVersions::isInstalled('boundwize/structarmed') + $version = InstalledVersions::isInstalled(Version::PACKAGE_NAME) ? [ - 'prettyVersion' => InstalledVersions::getPrettyVersion('boundwize/structarmed'), - 'reference' => InstalledVersions::getReference('boundwize/structarmed'), + 'prettyVersion' => InstalledVersions::getPrettyVersion(Version::PACKAGE_NAME), + 'reference' => InstalledVersions::getReference(Version::PACKAGE_NAME), ] : InstalledVersions::getRootPackage(); diff --git a/src/Cli/StructArmedApplication.php b/src/Cli/StructArmedApplication.php index 3bb928c..600d550 100644 --- a/src/Cli/StructArmedApplication.php +++ b/src/Cli/StructArmedApplication.php @@ -5,6 +5,7 @@ namespace Boundwize\StructArmed\Cli; use Boundwize\StructArmed\Analyser\Parallel\ClassNodeWorker; +use Boundwize\StructArmed\Version; use function array_slice; use function getcwd; @@ -25,6 +26,12 @@ public function run(array $argv, ?string $basePath = null): int return ClassNodeWorker::run($argv[2] ?? '', $argv[3] ?? ''); } + if (in_array($command, ['--version', '-V'], true)) { + echo sprintf("StructArmed %s\n", Version::current()); + + return 0; + } + if (in_array($command, [null, '--help', '-h'], true)) { echo Usage::render(); diff --git a/src/Cli/Usage.php b/src/Cli/Usage.php index 6aa5c84..fa4c3043 100644 --- a/src/Cli/Usage.php +++ b/src/Cli/Usage.php @@ -10,6 +10,7 @@ public static function render(): string { return <<<'TXT' Usage: + structarmed --version structarmed init [--preset=ddd|mvc|psr1|psr4|all] structarmed analyse|analyze [path ...] [--config=path/to/structarmed.php] [--report=console|json] [--no-progress] [--clear-cache] [--disable-parallel] diff --git a/src/Report/Reports/ConsoleReport.php b/src/Report/Reports/ConsoleReport.php index eb152b7..f529c95 100644 --- a/src/Report/Reports/ConsoleReport.php +++ b/src/Report/Reports/ConsoleReport.php @@ -7,10 +7,12 @@ use Boundwize\StructArmed\Cli\ColorSupport; use Boundwize\StructArmed\Report\ReportInterface; use Boundwize\StructArmed\Rule\RuleViolationCollection; +use Boundwize\StructArmed\Version; use function implode; use function sprintf; use function str_repeat; +use function strlen; use const PHP_EOL; @@ -18,12 +20,14 @@ final class ConsoleReport implements ReportInterface { public function render(RuleViolationCollection $ruleViolationCollection, float $elapsedSeconds): string { - $useColor = ColorSupport::detect(); - $lines = []; + $useColor = ColorSupport::detect(); + $lines = []; + $heading = sprintf('StructArmed %s — Architecture Enforcement', Version::current()); + $lineWidth = strlen($heading); $lines[] = ''; - $lines[] = 'StructArmed — Architecture Enforcement'; - $lines[] = str_repeat('=', 42); + $lines[] = $heading; + $lines[] = str_repeat('=', $lineWidth); if ($ruleViolationCollection->isEmpty()) { $lines[] = ''; @@ -35,7 +39,7 @@ public function render(RuleViolationCollection $ruleViolationCollection, float $ $lines[] = ''; $lines[] = sprintf('Found %d violation(s):', $ruleViolationCollection->count()); - $lines[] = str_repeat('─', 42); + $lines[] = str_repeat('─', $lineWidth); foreach ($ruleViolationCollection as $violation) { $lines[] = ''; @@ -49,7 +53,7 @@ public function render(RuleViolationCollection $ruleViolationCollection, float $ } $lines[] = ''; - $lines[] = str_repeat('─', 42); + $lines[] = str_repeat('─', $lineWidth); $lines[] = sprintf( '%d violation(s) found • %.2fs', $ruleViolationCollection->count(), diff --git a/src/Version.php b/src/Version.php new file mode 100644 index 0000000..3ac105e --- /dev/null +++ b/src/Version.php @@ -0,0 +1,28 @@ +runApplication(['structarmed']); $this->assertSame(0, $exitCode); + $this->assertStringContainsString('structarmed --version', $output); $this->assertStringContainsString('structarmed init', $output); $this->assertStringContainsString('structarmed analyse|analyze', $output); } + public function testApplicationPrintsVersion(): void + { + [$exitCode, $output] = $this->runApplication(['structarmed', '--version']); + + $this->assertSame(0, $exitCode); + $this->assertSame( + sprintf("StructArmed %s\n", Version::current()), + $output + ); + } + public function testApplicationRejectsUnknownCommand(): void { [$exitCode, $output] = $this->runApplication(['structarmed', 'unknown']); diff --git a/tests/Report/ReportTest.php b/tests/Report/ReportTest.php index 7e3e360..9a8b2aa 100644 --- a/tests/Report/ReportTest.php +++ b/tests/Report/ReportTest.php @@ -8,6 +8,7 @@ use Boundwize\StructArmed\Report\Reports\JsonReport; use Boundwize\StructArmed\Rule\RuleViolation; use Boundwize\StructArmed\Rule\RuleViolationCollection; +use Boundwize\StructArmed\Version; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; @@ -17,13 +18,14 @@ #[CoversClass(ConsoleReport::class)] #[CoversClass(JsonReport::class)] +#[CoversClass(Version::class)] final class ReportTest extends TestCase { public function testConsoleReportRendersPassingResult(): void { $report = (new ConsoleReport())->render(new RuleViolationCollection(), 0.12); - $this->assertStringContainsString('StructArmed', $report); + $this->assertStringContainsString('StructArmed ' . Version::current(), $report); $this->assertStringContainsString('No violations found', $report); } diff --git a/tests/VersionTest.php b/tests/VersionTest.php new file mode 100644 index 0000000..fe8b068 --- /dev/null +++ b/tests/VersionTest.php @@ -0,0 +1,139 @@ +, + * dev: bool + * } + * @phpstan-type InstalledPackageVersion array{ + * pretty_version?: string, + * version?: string, + * reference?: string|null, + * type?: string, + * install_path?: string, + * aliases?: array, + * dev_requirement: bool, + * replaced?: array, + * provided?: array + * } + * @phpstan-type InstalledVersionsData array{ + * root: InstalledRootPackage, + * versions: array + * } + */ +#[CoversClass(Version::class)] +final class VersionTest extends TestCase +{ + public function testCurrentUsesInstalledPackagePrettyVersion(): void + { + $this->withInstalledVersions([ + 'root' => $this->rootPackage('some/project', 'dev-root'), + 'versions' => [ + 'boundwize/structarmed' => [ + 'pretty_version' => '1.2.3', + 'version' => '1.2.3.0', + 'reference' => 'abc123', + 'type' => 'library', + 'install_path' => __DIR__ . '/..', + 'aliases' => [], + 'dev_requirement' => false, + ], + ], + ], static function (): void { + self::assertSame('1.2.3', Version::current()); + }); + } + + public function testCurrentUsesUnknownWhenInstalledPackageHasNoPrettyVersion(): void + { + $this->withInstalledVersions([ + 'root' => $this->rootPackage('some/project', 'dev-root'), + 'versions' => [ + 'boundwize/structarmed' => [ + 'version' => '1.2.3.0', + 'reference' => 'abc123', + 'type' => 'library', + 'install_path' => __DIR__ . '/..', + 'aliases' => [], + 'dev_requirement' => false, + ], + ], + ], static function (): void { + self::assertSame('unknown', Version::current()); + }); + } + + public function testCurrentUsesRootPackageVersionWhenStructArmedIsNotInstalled(): void + { + $this->withInstalledVersions([ + 'root' => $this->rootPackage('some/project', 'dev-root'), + 'versions' => [], + ], static function (): void { + self::assertSame('dev-root', Version::current()); + }); + } + + /** + * @param array $installedVersions + * @phpstan-param InstalledVersionsData $installedVersions + * @param callable(): void $callback + */ + private function withInstalledVersions(array $installedVersions, callable $callback): void + { + $canGetVendors = new ReflectionProperty(InstalledVersions::class, 'canGetVendors'); + $installed = new ReflectionProperty(InstalledVersions::class, 'installed'); + $installedByVendor = new ReflectionProperty(InstalledVersions::class, 'installedByVendor'); + $isLocalDir = new ReflectionProperty(InstalledVersions::class, 'installedIsLocalDir'); + + $origCanGetVendors = $canGetVendors->getValue(); + $origInstalled = $installed->getValue(); + $origInstalledByVendor = $installedByVendor->getValue(); + $origIsLocalDir = $isLocalDir->getValue(); + + try { + $canGetVendors->setValue(null, false); + InstalledVersions::reload($installedVersions); + + $callback(); + } finally { + $installed->setValue(null, $origInstalled); + $installedByVendor->setValue(null, $origInstalledByVendor); + $isLocalDir->setValue(null, $origIsLocalDir); + $canGetVendors->setValue(null, $origCanGetVendors); + } + } + + /** + * @phpstan-return InstalledRootPackage + * @return array + */ + private function rootPackage(string $name, string $version): array + { + return [ + 'name' => $name, + 'pretty_version' => $version, + 'version' => $version, + 'reference' => null, + 'type' => 'project', + 'install_path' => __DIR__, + 'aliases' => [], + 'dev' => true, + ]; + } +}