From c191b0b47efb25fe28effe00dcebc623fffa0177 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Thu, 5 Feb 2026 12:16:42 -0700 Subject: [PATCH] impoved yaml linter to be more use built-in grav for more detail Signed-off-by: Andy Miller --- system/src/Grav/Common/Helpers/YamlLinter.php | 66 +++++++++++++++---- .../Grav/Console/Cli/YamlLinterCommand.php | 30 +++++++-- 2 files changed, 78 insertions(+), 18 deletions(-) diff --git a/system/src/Grav/Common/Helpers/YamlLinter.php b/system/src/Grav/Common/Helpers/YamlLinter.php index 7136ae06c..4b0ef8768 100644 --- a/system/src/Grav/Common/Helpers/YamlLinter.php +++ b/system/src/Grav/Common/Helpers/YamlLinter.php @@ -15,6 +15,7 @@ use Grav\Common\Utils; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use RegexIterator; +use RocketTheme\Toolbox\Compat\Yaml\Yaml as CompatYaml; use RocketTheme\Toolbox\File\MarkdownFile; use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator; use Symfony\Component\Yaml\Yaml; @@ -30,18 +31,19 @@ class YamlLinter * @param callable|null $callback Optional callback for progress: function(string $file, bool $success, ?string $error) * @return array */ - public static function lint(?string $folder = null, ?callable $callback = null) + public static function lint(?string $folder = null, ?callable $callback = null, bool $strict = false) { if (null !== $folder) { $folder = $folder ?: GRAV_ROOT; - return static::recurseFolder($folder, '(md|yaml)', $callback); + return static::recurseFolder($folder, '(md|yaml)', $callback, $strict); } return array_merge( - static::lintConfig($callback), - static::lintPages($callback), - static::lintBlueprints($callback) + static::lintConfig($callback, $strict), + static::lintPages($callback, $strict), + static::lintBlueprints($callback, $strict), + static::lintEnvironments($callback, $strict) ); } @@ -49,25 +51,58 @@ class YamlLinter * @param callable|null $callback Optional callback for progress: function(string $file, bool $success, ?string $error) * @return array */ - public static function lintPages(?callable $callback = null) + public static function lintPages(?callable $callback = null, bool $strict = false) { - return static::recurseFolder('page://', '(md|yaml)', $callback); + return static::recurseFolder('page://', '(md|yaml)', $callback, $strict); } /** * @param callable|null $callback Optional callback for progress: function(string $file, bool $success, ?string $error) + * @param bool $strict Use the stricter Compat YAML parser (matches runtime behavior) * @return array */ - public static function lintConfig(?callable $callback = null) + public static function lintConfig(?callable $callback = null, bool $strict = false) { - return static::recurseFolder('config://', '(md|yaml)', $callback); + return static::recurseFolder('config://', '(md|yaml)', $callback, $strict); } /** * @param callable|null $callback Optional callback for progress: function(string $file, bool $success, ?string $error) + * @param bool $strict Use the stricter Compat YAML parser (matches runtime behavior) * @return array */ - public static function lintBlueprints(?callable $callback = null) + public static function lintEnvironments(?callable $callback = null, bool $strict = false) + { + $lint_errors = []; + $user_path = GRAV_ROOT . '/' . GRAV_USER_PATH; + + // Scan Grav 1.6 style: user//config/ + foreach (glob($user_path . '/*/config', GLOB_ONLYDIR) as $envConfigDir) { + $envName = basename(dirname($envConfigDir)); + // Skip known non-environment directories + if (in_array($envName, ['config', 'plugins', 'themes', 'pages', 'accounts', 'data', 'assets'])) { + continue; + } + $lint_errors = array_merge($lint_errors, static::recurseFolder($envConfigDir, '(md|yaml)', $callback, $strict)); + } + + // Scan Grav 1.7+ style: user/env//config/ + $envPath = $user_path . '/env'; + if (is_dir($envPath)) { + foreach (glob($envPath . '/*/config', GLOB_ONLYDIR) as $envConfigDir) { + $lint_errors = array_merge($lint_errors, static::recurseFolder($envConfigDir, '(md|yaml)', $callback, $strict)); + } + } + + return $lint_errors; + } + + /** + * @param callable|null $callback Optional callback for progress: function(string $file, bool $success, ?string $error) + * @param bool $strict Use the stricter Compat YAML parser (matches runtime behavior) + * @return array + */ + public static function lintBlueprints(?callable $callback = null, bool $strict = false) { /** @var UniformResourceLocator $locator */ $locator = Grav::instance()['locator']; @@ -76,7 +111,7 @@ class YamlLinter $theme_path = 'themes://' . $current_theme . '/blueprints'; $locator->addPath('blueprints', '', [$theme_path]); - return static::recurseFolder('blueprints://', '(md|yaml)', $callback); + return static::recurseFolder('blueprints://', '(md|yaml)', $callback, $strict); } /** @@ -85,7 +120,7 @@ class YamlLinter * @param callable|null $callback Optional callback for progress: function(string $file, bool $success, ?string $error) * @return array */ - public static function recurseFolder($path, $extensions = '(md|yaml)', ?callable $callback = null) + public static function recurseFolder($path, $extensions = '(md|yaml)', ?callable $callback = null, bool $strict = false) { $lint_errors = []; @@ -104,7 +139,12 @@ class YamlLinter foreach ($iterator as $filepath => $file) { $relativePath = str_replace(GRAV_ROOT, '', $filepath); try { - Yaml::parse(static::extractYaml($filepath)); + $yaml = static::extractYaml($filepath); + if ($strict) { + CompatYaml::parse($yaml); + } else { + Yaml::parse($yaml); + } if ($callback) { $callback($relativePath, true, null); } diff --git a/system/src/Grav/Console/Cli/YamlLinterCommand.php b/system/src/Grav/Console/Cli/YamlLinterCommand.php index 94be94305..9722776b0 100644 --- a/system/src/Grav/Console/Cli/YamlLinterCommand.php +++ b/system/src/Grav/Console/Cli/YamlLinterCommand.php @@ -39,6 +39,12 @@ class YamlLinterCommand extends GravCommand InputOption::VALUE_OPTIONAL, 'Go through specific folder' ) + ->addOption( + 'strict', + 's', + InputOption::VALUE_NONE, + 'Use the stricter Compat YAML parser that matches runtime behavior' + ) ->setDescription('Checks various files for YAML errors') ->setHelp('Checks various files for YAML errors'); } @@ -54,6 +60,7 @@ class YamlLinterCommand extends GravCommand $io->title('Yaml Linter'); $verbose = $io->isVerbose(); + $strict = (bool) $input->getOption('strict'); $checked = 0; $callback = $verbose ? function (string $file, bool $success, ?string $error) use ($io, &$checked) { $checked++; @@ -64,10 +71,14 @@ class YamlLinterCommand extends GravCommand } } : null; + if ($strict) { + $io->note('Using strict Compat YAML parser (matches runtime behavior)'); + } + $error = 0; if ($input->getOption('all')) { $io->section('All'); - $errors = YamlLinter::lint('', $callback); + $errors = YamlLinter::lint('', $callback, $strict); $this->displayResult($errors, $io, 'No YAML Linting issues found', $verbose, $checked); if (!empty($errors)) { @@ -75,7 +86,7 @@ class YamlLinterCommand extends GravCommand } } elseif ($folder = $input->getOption('folder')) { $io->section($folder); - $errors = YamlLinter::lint($folder, $callback); + $errors = YamlLinter::lint($folder, $callback, $strict); $this->displayResult($errors, $io, 'No YAML Linting issues found', $verbose, $checked); if (!empty($errors)) { @@ -84,7 +95,7 @@ class YamlLinterCommand extends GravCommand } else { $io->section('User Configuration'); $checked = 0; - $errors = YamlLinter::lintConfig($callback); + $errors = YamlLinter::lintConfig($callback, $strict); $this->displayResult($errors, $io, 'No YAML Linting issues with configuration', $verbose, $checked); if (!empty($errors)) { @@ -93,7 +104,7 @@ class YamlLinterCommand extends GravCommand $io->section('Pages Frontmatter'); $checked = 0; - $errors = YamlLinter::lintPages($callback); + $errors = YamlLinter::lintPages($callback, $strict); $this->displayResult($errors, $io, 'No YAML Linting issues with pages', $verbose, $checked); if (!empty($errors)) { @@ -102,12 +113,21 @@ class YamlLinterCommand extends GravCommand $io->section('Page Blueprints'); $checked = 0; - $errors = YamlLinter::lintBlueprints($callback); + $errors = YamlLinter::lintBlueprints($callback, $strict); $this->displayResult($errors, $io, 'No YAML Linting issues with blueprints', $verbose, $checked); if (!empty($errors)) { $error = 1; } + + $io->section('Environment Configuration'); + $checked = 0; + $errors = YamlLinter::lintEnvironments($callback, $strict); + + $this->displayResult($errors, $io, 'No YAML Linting issues with environment configs', $verbose, $checked); + if (!empty($errors)) { + $error = 1; + } } return $error;