PSR-12, build script, fixes

This commit is contained in:
2022-12-24 00:55:13 +02:00
parent 348fc6f785
commit cf87d02ecb
15 changed files with 648 additions and 593 deletions

66
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
name: Build
on:
push:
branches: [ master ]
pull_request:
release:
types: [ created ]
jobs:
build-phar:
runs-on: ubuntu-latest
name: Build PHAR
strategy:
fail-fast: false
matrix:
php: [ '8.1' ]
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
ini-values: phar.readonly=0
tools: composer
coverage: none
- name: Install Composer dependencies
uses: ramsey/composer-install@v2
- name: Build PHAR
run: |
cp application application.phar
composer build -- --build-version
# Smoke test
- name: Ensure the PHAR works
run: builds/branch-usage-checker --version
- uses: actions/upload-artifact@v3
name: Upload the PHAR artifact
with:
name: branch-usage-checker
path: builds/branch-usage-checker
publish-phar:
runs-on: ubuntu-latest
name: Publish the PHAR
needs:
- 'build-phar'
if: github.event_name == 'release'
steps:
- uses: actions/download-artifact@v3
with:
name: branch-usage-checker
path: builds/
- name: Upload box.phar
uses: softprops/action-gh-release@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
files: builds/branch-usage-checker

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@
/.vscode /.vscode
/.vagrant /.vagrant
.phpunit.result.cache .phpunit.result.cache
/builds/branch-usage-checker

View File

@@ -6,7 +6,8 @@ use App\Dto\PackagistApiPackagePayload;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use LaravelZero\Framework\Commands\Command; use LaravelZero\Framework\Commands\Command;
class CheckCommand extends Command { class CheckCommand extends Command
{
protected $signature = 'check protected $signature = 'check
{vendor : Package vendor (required)} {vendor : Package vendor (required)}
{package : Package name (required)} {package : Package name (required)}
@@ -17,119 +18,128 @@ class CheckCommand extends Command {
private string $vendor = ''; private string $vendor = '';
private string $package = ''; private string $package = '';
private string $filter = ''; private string $filter = '';
private int $total_branches = 0; private int $totalBranches = 0;
public function handle() : int { public function handle(): int
$this->vendor = (string) $this->argument( 'vendor' ); {
$this->package = (string) $this->argument( 'package' ); $this->vendor = (string)$this->argument('vendor');
$months = (int) $this->argument( 'months' ); $this->package = (string)$this->argument('package');
$months = (int)$this->argument('months');
$this->info( 'Checking: ' . sprintf( '%s/%s', $this->vendor, $this->package ) ); $this->info('Checking: ' . sprintf('%s/%s', $this->vendor, $this->package));
$this->info( 'Months: ' . $months ); $this->info('Months: ' . $months);
$payload = Http::get( sprintf( $payload = Http::get(
'https://packagist.org/packages/%s/%s.json', sprintf(
$this->vendor, 'https://packagist.org/packages/%s/%s.json',
$this->package $this->vendor,
) ); $this->package
)
);
$this->filter = now()->subMonths( $months )->day( 1 )->toDateString(); $this->filter = now()->subMonths($months)->day(1)->toDateString();
try { try {
$pkg = new PackagistApiPackagePayload( $payload->json() ); $pkg = new PackagistApiPackagePayload($payload->json());
$this->info( 'Found the package. Type: ' . $pkg->type ); $this->info('Found the package. Type: ' . $pkg->type);
$versions = collect( $pkg->versions ?? [] ) $versions = collect($pkg->versions ?? [])
->keys() ->keys()
// Filter actual versions out. // Filter actual versions out.
->filter( fn( $version ) => \str_starts_with( $version, 'dev-' ) ) ->filter(fn($version) => \str_starts_with($version, 'dev-'))
->sort(); ->sort();
$this->total_branches = $versions->count(); $this->totalBranches = $versions->count();
$this->info( sprintf( $this->info(
'Package has %d branches. Starting to download statistics.', sprintf(
$this->total_branches 'Package has %d branches. Starting to download statistics.',
) ); $this->totalBranches
)
);
$statistics = collect( $versions ) $statistics = collect($versions)
->mapWithKeys( fn( $branch ) => $this->get_statistics( $branch ) ) ->mapWithKeys(fn($branch) => $this->getStatistics($branch))
->toArray(); ->toArray();
$this->info( 'Downloaded statistics...' ); $this->info('Downloaded statistics...');
$this->output_table( $statistics ); $this->outputTable($statistics);
$this->output_suggestions( $statistics ); $this->outputSuggestions($statistics);
} } catch (\Exception $e) {
catch ( \Exception $e ) { $this->error($e->getMessage(), $e);
$this->error( $e->getMessage(), $e );
} }
return 0; return 0;
} }
private function get_statistics( $branch ) : array { private function getStatistics($branch): array
$payload = Http::get( sprintf( {
'https://packagist.org/packages/%s/%s/stats/%s.json?average=monthly&from=%s', $payload = Http::get(
$this->vendor, sprintf(
$this->package, 'https://packagist.org/packages/%s/%s/stats/%s.json?average=monthly&from=%s',
$branch, $this->vendor,
$this->filter $this->package,
) ); $branch,
$this->filter
)
);
$data = collect( $payload->json() ); $data = collect($payload->json());
$labels = collect( $data->get( 'labels', [] ) )->toArray(); $labels = collect($data->get('labels', []))->toArray();
$values = collect( $data->get( 'values', [] ) )->flatten()->toArray(); $values = collect($data->get('values', []))->flatten()->toArray();
$labels[] = 'Total'; $labels[] = 'Total';
$values[] = array_sum( $values ); $values[] = array_sum($values);
return [ $branch => \array_combine( $labels, $values ) ]; return [$branch => \array_combine($labels, $values)];
} }
private function output_table( array $statistics ) : void { private function outputTable(array $statistics): void
if ( empty( $statistics ) ) { {
$this->info( 'No statistics found... Stopping.' ); if (empty($statistics)) {
exit( 0 ); $this->info('No statistics found... Stopping.');
exit(0);
} }
$tableHeaders = [ '' => 'Branch' ]; $tableHeaders = ['' => 'Branch'];
$tableBranches = []; $tableBranches = [];
foreach ( $statistics as $branch => $stats ) { foreach ($statistics as $branch => $stats) {
foreach ( $stats as $m => $v ) { foreach ($stats as $m => $v) {
$tableHeaders[ $m ] = (string) $m; $tableHeaders[$m] = (string)$m;
$tableBranches[ $branch ][ $branch ] = $branch; $tableBranches[$branch][$branch] = $branch;
$tableBranches[ $branch ][ $m ] = (string) $v; $tableBranches[$branch][$m] = (string)$v;
} }
} }
$this->line(''); $this->line('');
$this->table( $tableHeaders, $tableBranches ); $this->table($tableHeaders, $tableBranches);
} }
private function output_suggestions( array $statistics = [] ) : void { private function outputSuggestions(array $statistics = []): void
{
$deletable = []; $deletable = [];
if ( empty( $statistics ) ) { if (empty($statistics)) {
$this->info( 'No statistics to give suggestions for. Quitting...' ); $this->info('No statistics to give suggestions for. Quitting...');
exit( 0 ); exit(0);
} }
foreach ( $statistics as $k => $values ) { foreach ($statistics as $k => $values) {
if ( ! empty( $values['Total'] ) ) { if (!empty($values['Total'])) {
continue; continue;
} }
$deletable[ $k ] = $values['Total']; $deletable[$k] = $values['Total'];
} }
if ( empty( $deletable ) ) { if (empty($deletable)) {
$this->info( 'No suggestions available. Good job!' ); $this->info('No suggestions available. Good job!');
exit( 0 ); exit(0);
} }
$keys = array_keys( $deletable ); $keys = array_keys($deletable);
$branches = collect( $keys )->mapWithKeys( function ( $branch ) { $branches = collect($keys)->mapWithKeys(function ($branch) {
return [ return [
$branch => [ $branch => [
$branch, $branch,
@@ -141,15 +151,17 @@ class CheckCommand extends Command {
), ),
], ],
]; ];
} ); });
$this->line(''); $this->line('');
$this->info( sprintf( $this->info(
'Found %d branches (out of %d total) with no downloads since %s', sprintf(
$branches->count(), 'Found %d branches (out of %d total) with no downloads since %s',
$this->total_branches, $branches->count(),
$this->filter $this->totalBranches,
) ); $this->filter
$this->table( [ 'Branch', 'URL' ], $branches ); )
);
$this->table(['Branch', 'URL'], $branches);
} }
} }

View File

@@ -4,6 +4,7 @@ namespace App\Commands;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Console\Scheduling\Schedule;
use LaravelZero\Framework\Commands\Command; use LaravelZero\Framework\Commands\Command;
use function Termwind\{render}; use function Termwind\{render};
class InspireCommand extends Command class InspireCommand extends Command
@@ -29,20 +30,23 @@ class InspireCommand extends Command
*/ */
public function handle() public function handle()
{ {
render(<<<'HTML' render(
<<<'HTML'
<div class="py-1 ml-2"> <div class="py-1 ml-2">
<div class="px-1 bg-blue-300 text-black">Laravel Zero</div> <div class="px-1 bg-blue-300 text-black">Laravel Zero</div>
<em class="ml-1"> <em class="ml-1">
Simplicity is the ultimate sophistication. Simplicity is the ultimate sophistication.
</em> </em>
</div> </div>
HTML); HTML
);
} }
/** /**
* Define the command's schedule. * Define the command's schedule.
* *
* @param \Illuminate\Console\Scheduling\Schedule $schedule * @param \Illuminate\Console\Scheduling\Schedule $schedule
*
* @return void * @return void
*/ */
public function schedule(Schedule $schedule) public function schedule(Schedule $schedule)

View File

@@ -4,7 +4,8 @@ namespace App\Dto;
use Spatie\DataTransferObject\DataTransferObject; use Spatie\DataTransferObject\DataTransferObject;
class GitHubApiBranch extends DataTransferObject { class GitHubApiBranch extends DataTransferObject
{
public string $name; public string $name;
public bool $protected; public bool $protected;
} }

View File

@@ -4,7 +4,8 @@ namespace App\Dto;
use Spatie\DataTransferObject\Attributes\MapFrom; use Spatie\DataTransferObject\Attributes\MapFrom;
class PackagistApiPackagePayload extends \Spatie\DataTransferObject\DataTransferObject { class PackagistApiPackagePayload extends \Spatie\DataTransferObject\DataTransferObject
{
#[MapFrom('package.name')] #[MapFrom('package.name')]
public string $name = ''; public string $name = '';
#[MapFrom('package.description')] #[MapFrom('package.description')]

View File

@@ -4,7 +4,8 @@ namespace App\Dto;
use Spatie\DataTransferObject\Attributes\MapFrom; use Spatie\DataTransferObject\Attributes\MapFrom;
class PackagistApiStatsPayload extends \Spatie\DataTransferObject\DataTransferObject { class PackagistApiStatsPayload extends \Spatie\DataTransferObject\DataTransferObject
{
public array $labels; public array $labels;
#[MapFrom('values.[0]')] #[MapFrom('values.[0]')]
public string $version; public string $version;

View File

@@ -4,9 +4,11 @@ namespace App\Fetchers;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
class GitHubRestApi { class GitHubRestApi
public static function getBranches( string $vendor, string $package ) : array { {
$pages = self::downloader( $vendor, $package ); public static function getBranches(string $vendor, string $package): array
{
$pages = self::downloader($vendor, $package);
$pages = \collect($pages) $pages = \collect($pages)
->flatten(1) ->flatten(1)
->toArray(); ->toArray();
@@ -14,7 +16,8 @@ class GitHubRestApi {
return $pages; return $pages;
} }
public static function downloader( $vendor, $package ) : array { public static function downloader($vendor, $package): array
{
$responses = []; $responses = [];
$continue = true; $continue = true;
@@ -25,15 +28,15 @@ class GitHubRestApi {
$package $package
); );
while ( $continue ) { while ($continue) {
$response = Http::get( $gh_api . '&page=' . $page ); $response = Http::get($gh_api . '&page=' . $page);
if ( empty( $response ) ) { if (empty($response)) {
$continue = false; $continue = false;
} }
$responses[ $page ] = $response; $responses[$page] = $response;
$page ++; $page++;
} }
return $responses; return $responses;

47
application Normal file
View File

@@ -0,0 +1,47 @@
#!/usr/bin/env php
<?php
define('LARAVEL_START', microtime(true));
/**
* Register The Auto Loader
*
* Composer provides a convenient, automatically generated class loader
* for our application. We just need to utilize it! We'll require it
* into the script here so that we do not have to worry about the
* loading of any our classes "manually". Feels great to relax.
*
*/
$autoloader = require file_exists(__DIR__ . '/vendor/autoload.php')
? __DIR__ . '/vendor/autoload.php'
: __DIR__ . '/../../autoload.php';
$app = require_once __DIR__ . '/bootstrap/app.php';
/**
* Run The Artisan Application
*
* When we run the console application, the current CLI command will be
* executed in this console and the response sent back to a terminal
* or another output device for the developers. Here goes nothing!
*
*/
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput(),
new Symfony\Component\Console\Output\ConsoleOutput()
);
/**
* Shutdown The Application
*
* Once Artisan has finished running, we will fire off the shutdown events
* so that any final work may be done by the application before we shut
* down the process. This is the last thing to happen to the request.
*
*/
$kernel->terminate($input, $status);
exit($status);

View File

@@ -1,30 +1,23 @@
<?php <?php
/* /**
|-------------------------------------------------------------------------- * Create The Application
| Create The Application *
|-------------------------------------------------------------------------- * The first thing we will do is create a new Laravel application instance
| * which serves as the "glue" for all the components of Laravel, and is
| The first thing we will do is create a new Laravel application instance * the IoC container for the system binding all of the various parts.
| which serves as the "glue" for all the components of Laravel, and is */
| the IoC container for the system binding all of the various parts.
|
*/
$app = new LaravelZero\Framework\Application( $app = new LaravelZero\Framework\Application(
dirname(__DIR__) dirname( __DIR__ )
); );
/* /**
|-------------------------------------------------------------------------- * Bind Important Interfaces
| Bind Important Interfaces *
|-------------------------------------------------------------------------- * Next, we need to bind some important interfaces into the container so
| * we will be able to resolve them when needed. The kernels serve the
| Next, we need to bind some important interfaces into the container so * incoming requests to this application from both the web and CLI.
| we will be able to resolve them when needed. The kernels serve the */
| incoming requests to this application from both the web and CLI.
|
*/
$app->singleton( $app->singleton(
Illuminate\Contracts\Console\Kernel::class, Illuminate\Contracts\Console\Kernel::class,
@@ -36,15 +29,12 @@ $app->singleton(
Illuminate\Foundation\Exceptions\Handler::class Illuminate\Foundation\Exceptions\Handler::class
); );
/* /**
|-------------------------------------------------------------------------- * Return The Application
| Return The Application *
|-------------------------------------------------------------------------- * This script returns the application instance. The instance is given to
| * the calling script so we can separate the building of the instances
| This script returns the application instance. The instance is given to * from the actual running of the application and sending responses.
| the calling script so we can separate the building of the instances */
| from the actual running of the application and sending responses.
|
*/
return $app; return $app;

View File

@@ -1,53 +0,0 @@
#!/usr/bin/env php
<?php
define('LARAVEL_START', microtime(true));
/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader
| for our application. We just need to utilize it! We'll require it
| into the script here so that we do not have to worry about the
| loading of any our classes "manually". Feels great to relax.
|
*/
$autoloader = require file_exists(__DIR__.'/vendor/autoload.php') ? __DIR__.'/vendor/autoload.php' : __DIR__.'/../../autoload.php';
$app = require_once __DIR__.'/bootstrap/app.php';
/*
|--------------------------------------------------------------------------
| Run The Artisan Application
|--------------------------------------------------------------------------
|
| When we run the console application, the current CLI command will be
| executed in this console and the response sent back to a terminal
| or another output device for the developers. Here goes nothing!
|
*/
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
new Symfony\Component\Console\Output\ConsoleOutput
);
/*
|--------------------------------------------------------------------------
| Shutdown The Application
|--------------------------------------------------------------------------
|
| Once Artisan has finished running, we will fire off the shutdown events
| so that any final work may be done by the application before we shut
| down the process. This is the last thing to happen to the request.
|
*/
$kernel->terminate($input, $status);
exit($status);

View File

@@ -70,7 +70,10 @@
"post-autoload-dump": [ "post-autoload-dump": [
"composer normalize" "composer normalize"
], ],
"build": "php branch-usage-checker app:build branch-usage-checker", "build": [
"x": "@php branch-usage-checker" "cp application application.phar",
"@php application app:build branch-usage-checker"
],
"x": "@php builds/branch-usage-checker"
} }
} }

636
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,57 +2,43 @@
return [ return [
/* /**
|-------------------------------------------------------------------------- * Application Name
| Application Name *
|-------------------------------------------------------------------------- * This value is the name of your application. This value is used when the
| * framework needs to place the application's name in a notification or
| This value is the name of your application. This value is used when the * any other location as required by the application or its packages.
| framework needs to place the application's name in a notification or */
| any other location as required by the application or its packages. 'name' => 'Branch usage checker',
|
*/
'name' => 'Branch usage checker', /**
* Application Version
*
* This value determines the "version" your application is currently running
* in. You may want to follow the "Semantic Versioning" - Given a version
* number MAJOR.MINOR.PATCH when an update happens: https://semver.org.
*
*/
'version' => app('git.version'),
/* /**
|-------------------------------------------------------------------------- * Application Environment
| Application Version *
|-------------------------------------------------------------------------- * This value determines the "environment" your application is currently
| * running in. This may determine how you prefer to configure various
| This value determines the "version" your application is currently running * services the application utilizes. This can be overridden using
| in. You may want to follow the "Semantic Versioning" - Given a version * the global command line "--env" option when calling commands.
| number MAJOR.MINOR.PATCH when an update happens: https://semver.org. */
| 'env' => 'development',
*/
'version' => app('git.version'),
/*
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services the application utilizes. This can be overridden using
| the global command line "--env" option when calling commands.
|
*/
'env' => 'development',
/*
|--------------------------------------------------------------------------
| Autoloaded Service Providers
|--------------------------------------------------------------------------
|
| The service providers listed here will be automatically loaded on the
| request to your application. Feel free to add your own services to
| this array to grant expanded functionality to your applications.
|
*/
/**
* Autoloaded Service Providers
*
* The service providers listed here will be automatically loaded on the
* request to your application. Feel free to add your own services to
* this array to grant expanded functionality to your applications.
*
*/
'providers' => [ 'providers' => [
App\Providers\AppServiceProvider::class, App\Providers\AppServiceProvider::class,
], ],

View File

@@ -2,72 +2,57 @@
return [ return [
/* /**
|-------------------------------------------------------------------------- * Default Command
| Default Command *
|-------------------------------------------------------------------------- * Laravel Zero will always run the command specified below when no command name is
| * provided. Consider update the default command for single command applications.
| Laravel Zero will always run the command specified below when no command name is * You cannot pass arguments to the default command because they are ignored.
| provided. Consider update the default command for single command applications. */
| You cannot pass arguments to the default command because they are ignored.
|
*/
'default' => NunoMaduro\LaravelConsoleSummary\SummaryCommand::class, 'default' => NunoMaduro\LaravelConsoleSummary\SummaryCommand::class,
/* /**
|-------------------------------------------------------------------------- * Commands Paths
| Commands Paths *
|-------------------------------------------------------------------------- * This value determines the "paths" that should be loaded by the console's
| * kernel. Foreach "path" present on the array provided below the kernel
| This value determines the "paths" that should be loaded by the console's * will extract all "Illuminate\Console\Command" based class commands.
| kernel. Foreach "path" present on the array provided below the kernel */
| will extract all "Illuminate\Console\Command" based class commands. 'paths' => [app_path('Commands')],
|
*/
'paths' => [ app_path( 'Commands' ) ],
/* /**
|-------------------------------------------------------------------------- * Added Commands
| Added Commands *
|-------------------------------------------------------------------------- * You may want to include a single command class without having to load an
| * entire folder. Here you can specify which commands should be added to
| You may want to include a single command class without having to load an * your list of commands. The console's kernel will try to load them.
| entire folder. Here you can specify which commands should be added to */
| your list of commands. The console's kernel will try to load them. 'add' => [
|
*/
'add' => [
// .. // ..
], ],
/* /**
|-------------------------------------------------------------------------- * Hidden Commands
| Hidden Commands *
|-------------------------------------------------------------------------- * Your application commands will always be visible on the application list
| * of commands. But you can still make them "hidden" specifying an array
| Your application commands will always be visible on the application list * of commands below. All "hidden" commands can still be run/executed.
| of commands. But you can still make them "hidden" specifying an array */
| of commands below. All "hidden" commands can still be run/executed. 'hidden' => [
|
*/
'hidden' => [
NunoMaduro\LaravelConsoleSummary\SummaryCommand::class, NunoMaduro\LaravelConsoleSummary\SummaryCommand::class,
Symfony\Component\Console\Command\DumpCompletionCommand::class, Symfony\Component\Console\Command\DumpCompletionCommand::class,
Symfony\Component\Console\Command\HelpCommand::class, Symfony\Component\Console\Command\HelpCommand::class,
LaravelZero\Framework\Commands\StubPublishCommand::class, LaravelZero\Framework\Commands\StubPublishCommand::class,
], ],
/* /**
|-------------------------------------------------------------------------- * Removed Commands
| Removed Commands *
|-------------------------------------------------------------------------- * Do you have a service provider that loads a list of commands that
| * you don't need? No problem. Laravel Zero allows you to specify
| Do you have a service provider that loads a list of commands that * below a list of commands that you don't to see in your app.
| you don't need? No problem. Laravel Zero allows you to specify */
| below a list of commands that you don't to see in your app. 'remove' => [
|
*/
'remove' => [
\App\Commands\InspireCommand::class, \App\Commands\InspireCommand::class,
Illuminate\Console\Scheduling\ScheduleRunCommand::class, Illuminate\Console\Scheduling\ScheduleRunCommand::class,
Illuminate\Console\Scheduling\ScheduleListCommand::class, Illuminate\Console\Scheduling\ScheduleListCommand::class,