Improved validation
This commit is contained in:
parent
d98840c1dd
commit
89d8a897c4
|
@ -3,9 +3,12 @@
|
||||||
namespace App\Console\Commands\Bundle;
|
namespace App\Console\Commands\Bundle;
|
||||||
|
|
||||||
use App\Classes\Bundle;
|
use App\Classes\Bundle;
|
||||||
|
use App\Services\HtmlValidator;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Facades\Vite;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use JsonSchema\Validator;
|
use JsonSchema\Validator;
|
||||||
|
|
||||||
|
@ -22,14 +25,23 @@ class Validate extends Command
|
||||||
|
|
||||||
protected Filesystem $sourceDisk;
|
protected Filesystem $sourceDisk;
|
||||||
|
|
||||||
protected array $invalid = [];
|
protected array $invalidJson = [];
|
||||||
|
|
||||||
|
protected array $invalidHtml = [];
|
||||||
|
|
||||||
|
protected array $invalidCss = [];
|
||||||
|
|
||||||
|
protected $bundles;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->signature = 'bundle:validate
|
$this->signature = 'bundle:validate
|
||||||
{ --r|recursive : Also render sub-bundles }
|
{ --no-css : Do not validate CSS }
|
||||||
|
{ --no-html : Do not validate HTML }
|
||||||
|
{ --no-json : Do not validate JSON }
|
||||||
|
{ --r|recursive : Also validate sub-bundles }
|
||||||
{ --source-disk= : Use specified content disk - Defaults to <info>' . env('CONTENT_DISK') . '</info> }
|
{ --source-disk= : Use specified content disk - Defaults to <info>' . env('CONTENT_DISK') . '</info> }
|
||||||
{ path? : Path to a specific bundle to render - Default to <info>/</info> }
|
{ path? : Path to a specific bundle to validate - Default to <info>/</info> }
|
||||||
';
|
';
|
||||||
|
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
@ -41,8 +53,19 @@ public function __construct()
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$this->selectDisk()
|
$this->selectDisk()
|
||||||
->validate()
|
->validateCss()
|
||||||
|
->selectBundles()
|
||||||
|
->validateJson()
|
||||||
|
->validateHtml()
|
||||||
->showReport();
|
->showReport();
|
||||||
|
|
||||||
|
if (
|
||||||
|
!empty($this->invalidCss)
|
||||||
|
|| !empty($this->invalidJson)
|
||||||
|
|| !empty($this->invalidHtml)
|
||||||
|
) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,9 +77,9 @@ private function selectDisk(): self
|
||||||
|
|
||||||
$this->sourceDisk = Storage::disk($sourceDisk);
|
$this->sourceDisk = Storage::disk($sourceDisk);
|
||||||
|
|
||||||
$this->comment(
|
$this->line(
|
||||||
sprintf(
|
sprintf(
|
||||||
'Using `%s` as source disk',
|
'Using <info>%s</info> as source disk',
|
||||||
$sourceDisk
|
$sourceDisk
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -67,16 +90,16 @@ private function selectDisk(): self
|
||||||
/**
|
/**
|
||||||
* Collect a list of bundles to validate
|
* Collect a list of bundles to validate
|
||||||
*/
|
*/
|
||||||
private function getBundles()
|
private function selectBundles(): self
|
||||||
{
|
{
|
||||||
$path = $this->argument('path') ?? '/';
|
$path = $this->argument('path') ?? '/';
|
||||||
$comment = sprintf('Validating %s', $path);
|
$comment = sprintf('Validating <info>%s</info>', $path);
|
||||||
|
|
||||||
if ($this->option('recursive')) {
|
if ($this->option('recursive')) {
|
||||||
$comment .= ' and all sub-bundles';
|
$comment .= ' and <info>all sub-bundles</info>';
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->comment($comment);
|
$this->line($comment);
|
||||||
$this->output->write('Collecting bundles... ');
|
$this->output->write('Collecting bundles... ');
|
||||||
|
|
||||||
if ($this->option('recursive')) {
|
if ($this->option('recursive')) {
|
||||||
|
@ -85,16 +108,18 @@ private function getBundles()
|
||||||
$bundles = [new Bundle($path, $this->sourceDisk)];
|
$bundles = [new Bundle($path, $this->sourceDisk)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->bundles = $bundles;
|
||||||
|
|
||||||
$this->info('OK');
|
$this->info('OK');
|
||||||
|
|
||||||
return $bundles;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an associative array containing available bundles metadata files
|
* Return an associative array containing available bundles metadata files
|
||||||
* as keys and json-decoded validation schemas as values
|
* as keys and json-decoded validation schemas as values
|
||||||
*/
|
*/
|
||||||
private function getValidators()
|
private function getJsonValidators()
|
||||||
{
|
{
|
||||||
return array_map(function ($path) {
|
return array_map(function ($path) {
|
||||||
return json_decode(file_get_contents($path));
|
return json_decode(file_get_contents($path));
|
||||||
|
@ -104,15 +129,21 @@ private function getValidators()
|
||||||
/**
|
/**
|
||||||
* Perform json validation on selected bundles
|
* Perform json validation on selected bundles
|
||||||
*/
|
*/
|
||||||
private function validate(): self
|
private function validateJson(): self
|
||||||
{
|
{
|
||||||
$bundles = $this->getBundles();
|
if ($this->option('no-json')) {
|
||||||
$validators = $this->getValidators();
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->line('Validating <info>JSON</info>...');
|
||||||
|
|
||||||
|
$bundles = $this->bundles;
|
||||||
|
$validators = $this->getJsonValidators();
|
||||||
|
|
||||||
progress(
|
progress(
|
||||||
label: 'Validating... ',
|
label: 'Validating... ',
|
||||||
steps: $bundles,
|
steps: $bundles,
|
||||||
callback: fn (Bundle $bundle, $progress) => $this->handleBundle($bundle, $progress, $validators)
|
callback: fn (Bundle $bundle, $progress) => $this->handleBundleJsonValidation($bundle, $progress, $validators)
|
||||||
);
|
);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -121,7 +152,7 @@ private function validate(): self
|
||||||
/**
|
/**
|
||||||
* Handle specific bundle
|
* Handle specific bundle
|
||||||
*/
|
*/
|
||||||
private function handleBundle(Bundle $bundle, $progress, $validators)
|
private function handleBundleJsonValidation(Bundle $bundle, $progress, $validators)
|
||||||
{
|
{
|
||||||
$progress->label(sprintf('Validating %s ...', $bundle->getPath()));
|
$progress->label(sprintf('Validating %s ...', $bundle->getPath()));
|
||||||
|
|
||||||
|
@ -141,7 +172,7 @@ private function handleBundle(Bundle $bundle, $progress, $validators)
|
||||||
$validator->validate($data, $schema);
|
$validator->validate($data, $schema);
|
||||||
|
|
||||||
if (!$validator->isValid()) {
|
if (!$validator->isValid()) {
|
||||||
$this->invalid[$bundle->getPath()] = [
|
$this->invalidJson[$bundle->getPath()] = [
|
||||||
'metadata' => $metadataFilename,
|
'metadata' => $metadataFilename,
|
||||||
'errors' => $validator->getErrors(),
|
'errors' => $validator->getErrors(),
|
||||||
];
|
];
|
||||||
|
@ -149,20 +180,109 @@ private function handleBundle(Bundle $bundle, $progress, $validators)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function validateCss(): self
|
||||||
|
{
|
||||||
|
if ($this->option('no-css')) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->line('Validating <info>CSS</info>...');
|
||||||
|
|
||||||
|
$file = public_path(Vite::asset('resources/css/app.css'));
|
||||||
|
$cacheKey = sprintf('css_validation_%s', Str::slug($file));
|
||||||
|
|
||||||
|
$result = Cache::remember($cacheKey, now()->addMonth(), function () use ($file) {
|
||||||
|
$content = file_get_contents($file);
|
||||||
|
|
||||||
|
return HtmlValidator::validateCss($content);
|
||||||
|
});
|
||||||
|
|
||||||
|
$errors = collect($result['messages'])->where('type', '=', 'error');
|
||||||
|
|
||||||
|
if ($errors->count() > 0) {
|
||||||
|
$this->invalidCss = $result;
|
||||||
|
|
||||||
|
Cache::forever('css_is_valid', false);
|
||||||
|
} else {
|
||||||
|
Cache::forever('css_is_valid', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform json validation on selected bundles
|
||||||
|
*/
|
||||||
|
private function validateHtml(): self
|
||||||
|
{
|
||||||
|
if ($this->option('no-html')) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->line('Validating <info>HTML</info>...');
|
||||||
|
|
||||||
|
$bundles = $this->bundles;
|
||||||
|
|
||||||
|
progress(
|
||||||
|
label: 'Validating... ',
|
||||||
|
steps: $bundles,
|
||||||
|
callback: fn (Bundle $bundle, $progress) => $this->handleBundleHtmlValidation($bundle, $progress)
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function handleBundleHtmlValidation(Bundle $bundle, $progress)
|
||||||
|
{
|
||||||
|
$progress->label(sprintf('Validating %s ...', $bundle->getPath()));
|
||||||
|
|
||||||
|
$rendered = $bundle->render();
|
||||||
|
|
||||||
|
foreach ($rendered as $path => $content) {
|
||||||
|
$result = HtmlValidator::validateHtml($content);
|
||||||
|
|
||||||
|
$errors = collect($result['messages'])->where('type', '=', 'error');
|
||||||
|
|
||||||
|
$cacheKey = sprintf('html_is_valid_%s', Str::slug($path));
|
||||||
|
|
||||||
|
if ($errors->count() > 0) {
|
||||||
|
$this->invalidHtml[$path] = $result;
|
||||||
|
|
||||||
|
Cache::forever($cacheKey, false);
|
||||||
|
} else {
|
||||||
|
Cache::forever($cacheKey, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a report in case of need
|
* Show a report in case of need
|
||||||
*/
|
*/
|
||||||
private function showReport()
|
private function showReport()
|
||||||
{
|
{
|
||||||
if (empty($this->invalid)) {
|
$this->showCssReport();
|
||||||
$this->info('All files are valid');
|
$this->showJsonReport();
|
||||||
} else {
|
$this->showHtmlReport();
|
||||||
$count = count($this->invalid);
|
}
|
||||||
|
|
||||||
$this->error(sprintf('%d invalid %s reported', $count, Str::plural('file', $count)));
|
/**
|
||||||
|
* Show a report in case of need
|
||||||
|
*/
|
||||||
|
private function showJsonReport()
|
||||||
|
{
|
||||||
|
if ($this->option('no-json')) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($this->invalidJson)) {
|
||||||
|
$this->info('Checked JSON files are valid');
|
||||||
|
} else {
|
||||||
|
$count = count($this->invalidJson);
|
||||||
|
|
||||||
|
$this->error(sprintf('%d invalid JSON %s reported', $count, Str::plural('file', $count)));
|
||||||
$this->newLine(2);
|
$this->newLine(2);
|
||||||
|
|
||||||
foreach ($this->invalid as $bundlePath => $data) {
|
foreach ($this->invalidJson as $bundlePath => $data) {
|
||||||
$this->line($bundlePath);
|
$this->line($bundlePath);
|
||||||
$this->line(sprintf(' > In <comment>%s</comment>:', $data['metadata']));
|
$this->line(sprintf(' > In <comment>%s</comment>:', $data['metadata']));
|
||||||
|
|
||||||
|
@ -172,4 +292,73 @@ private function showReport()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function showCssReport()
|
||||||
|
{
|
||||||
|
if ($this->option('no-css')) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($this->invalidCss)) {
|
||||||
|
$this->info('Checked CSS files are valid');
|
||||||
|
} else {
|
||||||
|
$count = count($this->invalidCss);
|
||||||
|
|
||||||
|
$this->error(sprintf('%d CSS %s reported', $count, Str::plural('error', $count)));
|
||||||
|
$this->newLine();
|
||||||
|
|
||||||
|
$data = $this->invalidCss;
|
||||||
|
|
||||||
|
foreach ($data['messages'] as $message) {
|
||||||
|
$type = $message['type'];
|
||||||
|
$subType = $message['subType'] ?? $type;
|
||||||
|
|
||||||
|
$decoration = match ($subType) {
|
||||||
|
'error' => 'error',
|
||||||
|
'info' => 'comment',
|
||||||
|
'warning' => 'comment'
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->newLine();
|
||||||
|
$this->line(sprintf(' > <%1$s>%2$s</%1$s>: %3$s', $decoration, $subType, $message['message']));
|
||||||
|
$this->line(' > > ' . $message['extract']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function showHtmlReport()
|
||||||
|
{
|
||||||
|
if ($this->option('no-html')) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($this->invalidHtml)) {
|
||||||
|
$this->info('Checked HTML files are valid');
|
||||||
|
} else {
|
||||||
|
$count = count($this->invalidHtml);
|
||||||
|
|
||||||
|
$this->error(sprintf('%d invalid HTML %s reported', $count, Str::plural('file', $count)));
|
||||||
|
$this->newLine(2);
|
||||||
|
|
||||||
|
foreach ($this->invalidHtml as $path => $data) {
|
||||||
|
$this->newLine(2);
|
||||||
|
$this->comment($path);
|
||||||
|
|
||||||
|
foreach ($data['messages'] as $message) {
|
||||||
|
$type = $message['type'];
|
||||||
|
$subType = $message['subType'] ?? $type;
|
||||||
|
|
||||||
|
$decoration = match ($subType) {
|
||||||
|
'error' => 'error',
|
||||||
|
'info' => 'comment',
|
||||||
|
'warning' => 'comment'
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->newLine();
|
||||||
|
$this->line(sprintf(' > <%1$s>%2$s</%1$s>: %3$s', $decoration, $subType, $message['message']));
|
||||||
|
$this->line(' > > ' . $message['extract']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user