Files
moko-platform/lib/Enterprise/ProjectConfigValidator.php
Jonathan Miller 4cc3f5bee4
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 5s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 6s
Generic: Repo Health / Release configuration (push) Successful in 5s
Generic: Repo Health / Scripts governance (push) Successful in 5s
Generic: Repo Health / Release configuration (pull_request) Successful in 6s
Generic: Repo Health / Scripts governance (pull_request) Successful in 6s
Generic: Repo Health / Repository health (push) Successful in 14s
Generic: Repo Health / Repository health (pull_request) Successful in 12s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 44s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 49s
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Has been skipped
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been skipped
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Platform: moko-platform CI / CI Summary (pull_request) Has been cancelled
style: fix all PHPCS PSR-12 violations across 74 files (7539 → 0 errors)
- Convert tabs to spaces (3,413 violations)
- Fix line endings, trailing whitespace, brace placement
- Break lines exceeding 150-char absolute limit
- Replace heredoc tab closers with spaces
- Fix empty elseif, forbidden function calls
- Update phpcs.xml: exclude rules inappropriate for CLI scripts
  (SideEffects, MissingNamespace, MultipleClasses, HeaderOrder,
  empty catch blocks)

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-24 17:07:51 -05:00

356 lines
11 KiB
PHP

<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoStandards.Enterprise.ProjectTypes
* INGROUP: MokoStandards
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /lib/Enterprise/ProjectConfigValidator.php
* BRIEF: Enterprise library for validating project configurations
*/
declare(strict_types=1);
namespace MokoEnterprise;
/**
* Project Config Validator
*
* Enterprise library for validating project configurations against
* project type templates and standards.
*/
class ProjectConfigValidator
{
private AuditLogger $logger;
private MetricsCollector $metrics;
private ProjectTypeDetector $detector;
private array $validationResults = [];
private int $errorsCount = 0;
private int $warningsCount = 0;
private const VALIDATION_RULES = [
'nodejs' => [
'required_files' => ['package.json'],
'recommended_files' => ['README.md', '.gitignore', 'tsconfig.json'],
'required_fields' => ['name', 'version', 'description'],
],
'python' => [
'required_files' => ['setup.py|pyproject.toml'],
'recommended_files' => ['README.md', 'requirements.txt', '.gitignore'],
'required_fields' => ['name', 'version'],
],
'terraform' => [
'required_files' => ['*.tf'],
'recommended_files' => ['README.md', 'variables.tf', 'outputs.tf'],
'required_fields' => [],
],
'wordpress' => [
'required_files' => ['*.php'],
'recommended_files' => ['README.md', 'readme.txt'],
'required_fields' => ['Plugin Name|Theme Name', 'Version'],
],
'mobile' => [
'required_files' => ['package.json|pubspec.yaml'],
'recommended_files' => ['README.md', '.gitignore'],
'required_fields' => ['name', 'version'],
],
'api' => [
'required_files' => [],
'recommended_files' => ['README.md', 'openapi.yaml|swagger.yaml', 'Dockerfile'],
'required_fields' => [],
],
];
/**
* Constructor
*/
public function __construct(
?AuditLogger $logger = null,
?MetricsCollector $metrics = null,
?ProjectTypeDetector $detector = null
) {
$this->logger = $logger ?? new AuditLogger('project_config_validator');
$this->metrics = $metrics ?? new MetricsCollector();
$this->detector = $detector ?? new ProjectTypeDetector($this->logger, $this->metrics);
}
/**
* Validate project configuration
*
* @param string $repoPath Path to repository
* @param string|null $projectType Optional project type (auto-detect if null)
* @return array Validation results
*/
public function validate(string $repoPath, ?string $projectType = null): array
{
$this->logger->logInfo("Validating project configuration: {$repoPath}");
$this->resetResults();
// Detect project type if not provided
if ($projectType === null) {
$detection = $this->detector->detect($repoPath);
$projectType = $detection['type'];
$this->logger->logInfo("Auto-detected project type: {$projectType}");
}
// Get validation rules for project type
$rules = self::VALIDATION_RULES[$projectType] ?? [];
if (empty($rules)) {
$this->addWarning('No validation rules for project type: ' . $projectType);
return $this->getResults();
}
// Run validations
$this->validateRequiredFiles($repoPath, $rules['required_files'] ?? []);
$this->validateRecommendedFiles($repoPath, $rules['recommended_files'] ?? []);
$this->validateProjectFields($repoPath, $projectType, $rules['required_fields'] ?? []);
// Record metrics
$this->metrics->setGauge('validation_errors', $this->errorsCount);
$this->metrics->setGauge('validation_warnings', $this->warningsCount);
$this->logger->logInfo("Validation complete: {$this->errorsCount} errors, {$this->warningsCount} warnings");
return $this->getResults();
}
/**
* Check if validation passed (no errors)
*/
public function passed(): bool
{
return $this->errorsCount === 0;
}
/**
* Get validation results
*/
public function getResults(): array
{
return [
'passed' => $this->passed(),
'errors' => $this->errorsCount,
'warnings' => $this->warningsCount,
'results' => $this->validationResults,
];
}
private function resetResults(): void
{
$this->validationResults = [];
$this->errorsCount = 0;
$this->warningsCount = 0;
}
private function validateRequiredFiles(string $path, array $files): void
{
foreach ($files as $filePattern) {
$found = false;
// Handle OR patterns (file1|file2)
if (strpos($filePattern, '|') !== false) {
$patterns = explode('|', $filePattern);
foreach ($patterns as $pattern) {
if ($this->filePatternExists($path, trim($pattern))) {
$found = true;
break;
}
}
} else {
$found = $this->filePatternExists($path, $filePattern);
}
if (!$found) {
$this->addError("Required file missing: {$filePattern}");
} else {
$this->addSuccess("Required file found: {$filePattern}");
}
}
}
private function validateRecommendedFiles(string $path, array $files): void
{
foreach ($files as $filePattern) {
$found = false;
// Handle OR patterns
if (strpos($filePattern, '|') !== false) {
$patterns = explode('|', $filePattern);
foreach ($patterns as $pattern) {
if ($this->filePatternExists($path, trim($pattern))) {
$found = true;
break;
}
}
} else {
$found = $this->filePatternExists($path, $filePattern);
}
if (!$found) {
$this->addWarning("Recommended file missing: {$filePattern}");
} else {
$this->addSuccess("Recommended file found: {$filePattern}");
}
}
}
private function validateProjectFields(string $path, string $projectType, array $fields): void
{
if (empty($fields)) {
return;
}
// Validate based on project type
switch ($projectType) {
case 'nodejs':
$this->validateNodeJSFields($path, $fields);
break;
case 'python':
$this->validatePythonFields($path, $fields);
break;
case 'wordpress':
$this->validateWordPressFields($path, $fields);
break;
default:
$this->logger->logInfo("No field validation for project type: {$projectType}");
}
}
private function validateNodeJSFields(string $path, array $fields): void
{
$packageFile = "{$path}/package.json";
if (!file_exists($packageFile)) {
$this->addError("Cannot validate fields: package.json not found");
return;
}
$package = json_decode(file_get_contents($packageFile), true);
if (!$package) {
$this->addError("Cannot parse package.json");
return;
}
foreach ($fields as $field) {
if (!isset($package[$field])) {
$this->addError("Required field missing in package.json: {$field}");
} else {
$this->addSuccess("Required field found in package.json: {$field}");
}
}
}
private function validatePythonFields(string $path, array $fields): void
{
$setupFile = "{$path}/setup.py";
$pyprojectFile = "{$path}/pyproject.toml";
if (!file_exists($setupFile) && !file_exists($pyprojectFile)) {
$this->addError("Cannot validate fields: setup.py or pyproject.toml not found");
return;
}
// Basic validation - check if fields appear in file content
$content = '';
if (file_exists($setupFile)) {
$content = file_get_contents($setupFile);
} elseif (file_exists($pyprojectFile)) {
$content = file_get_contents($pyprojectFile);
}
foreach ($fields as $field) {
if (stripos($content, $field) === false) {
$this->addWarning("Field may be missing: {$field}");
} else {
$this->addSuccess("Field appears to be present: {$field}");
}
}
}
private function validateWordPressFields(string $path, array $fields): void
{
$phpFiles = glob("{$path}/*.php");
if (empty($phpFiles)) {
$this->addError("No PHP files found for WordPress validation");
return;
}
$content = '';
foreach ($phpFiles as $file) {
$content .= file_get_contents($file);
}
foreach ($fields as $field) {
// Handle OR patterns
if (strpos($field, '|') !== false) {
$patterns = explode('|', $field);
$found = false;
foreach ($patterns as $pattern) {
if (stripos($content, trim($pattern)) !== false) {
$found = true;
break;
}
}
if (!$found) {
$this->addError("Required header field missing: {$field}");
} else {
$this->addSuccess("Required header field found");
}
} else {
if (stripos($content, $field) === false) {
$this->addError("Required header field missing: {$field}");
} else {
$this->addSuccess("Required header field found: {$field}");
}
}
}
}
private function filePatternExists(string $path, string $pattern): bool
{
// Handle wildcard patterns
if (strpos($pattern, '*') !== false) {
$files = glob("{$path}/{$pattern}");
return !empty($files);
}
return file_exists("{$path}/{$pattern}");
}
private function addError(string $message): void
{
$this->validationResults[] = [
'level' => 'error',
'message' => $message,
];
$this->errorsCount++;
$this->logger->logError($message);
}
private function addWarning(string $message): void
{
$this->validationResults[] = [
'level' => 'warning',
'message' => $message,
];
$this->warningsCount++;
$this->logger->logWarning($message);
}
private function addSuccess(string $message): void
{
$this->validationResults[] = [
'level' => 'success',
'message' => $message,
];
}
}