cbfa23c4c4
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Scripts governance (push) Successful in 5s
Generic: Repo Health / Release configuration (push) Successful in 5s
Generic: Repo Health / Repository health (push) Successful in 12s
Platform: moko-platform CI / Gate 1: Code Quality (push) Successful in 45s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 4s
Universal: PR Check / Validate PR (pull_request) Successful in 5s
Universal: PR Check / Build RC Package (pull_request) Successful in 2s
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Failing after 44s
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Failing after 48s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Failing after 48s
Platform: moko-platform CI / Gate 4: Governance (push) Successful in 48s
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Failing after 50s
Platform: moko-platform CI / Gate 5: Template Integrity (push) Failing after 12s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Successful in 1m13s
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Failing after 5s
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Failing after 42s
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Failing after 45s
Platform: moko-platform CI / Gate 4: Governance (pull_request) Successful in 44s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Failing after 47s
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Failing after 49s
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Platform: moko-platform CI / CI Summary (pull_request) Has been cancelled
Real bugs found and fixed: - bulk_joomla_template: $org undefined in heredoc (missing parameter) - RepositorySynchronizer: $root undefined (should be $repoRoot), duplicate array key - RepositoryHealthChecker: wrong class name (UnifiedValidation → UnifiedValidator) - scan_drift: missing $adapter property declaration - auto_detect_platform: wrong method name (detectProjectType → detect) - EnterpriseReadinessValidator: void return used as value - check_client_theme: extra parameter to printSummary() - ApiClient: unused constructor parameter now stored - GitPlatformAdapter: added listBranches/getCloneUrl/cloneRepo to interface - MokoGiteaAdapter/GitHubAdapter: implemented new interface methods 3 legacy CLIApp scripts excluded (need migration to CliFramework): repo_cleanup.php, push_files.php, joomla_release.php Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
839 lines
25 KiB
PHP
839 lines
25 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.Plugins
|
|
* INGROUP: MokoStandards
|
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
|
* PATH: /lib/Enterprise/Plugins/ApiPlugin.php
|
|
* BRIEF: Enterprise plugin for API/Microservices projects
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace MokoEnterprise\Plugins;
|
|
|
|
use MokoEnterprise\AbstractProjectPlugin;
|
|
|
|
/**
|
|
* API/Microservices Project Plugin
|
|
*
|
|
* Provides validation, metrics, and management capabilities for
|
|
* API and microservices projects (REST, GraphQL, gRPC).
|
|
*/
|
|
class ApiPlugin extends AbstractProjectPlugin
|
|
{
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function getProjectType(): string
|
|
{
|
|
return 'api';
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function getPluginName(): string
|
|
{
|
|
return 'API/Microservices Enterprise Plugin';
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function validateProject(array $config, string $projectPath): array
|
|
{
|
|
$errors = [];
|
|
$warnings = [];
|
|
|
|
$apiType = $this->detectAPIType($projectPath);
|
|
|
|
// Check for API documentation
|
|
if (!$this->hasAPIDocumentation($projectPath, $apiType)) {
|
|
$errors[] = 'No API documentation found (OpenAPI, GraphQL schema, etc.)';
|
|
}
|
|
|
|
// Check for proper error handling
|
|
if (!$this->hasErrorHandling($projectPath)) {
|
|
$warnings[] = 'Consider implementing standardized error handling';
|
|
}
|
|
|
|
// Check for authentication
|
|
if (!$this->hasAuthentication($projectPath)) {
|
|
$warnings[] = 'No authentication mechanism detected';
|
|
}
|
|
|
|
// Check for rate limiting
|
|
if (!$this->hasRateLimiting($projectPath)) {
|
|
$warnings[] = 'Consider implementing rate limiting';
|
|
}
|
|
|
|
// Check for logging
|
|
if (!$this->hasLogging($projectPath)) {
|
|
$warnings[] = 'No logging configuration found';
|
|
}
|
|
|
|
// Check for input validation
|
|
if (!$this->hasInputValidation($projectPath)) {
|
|
$warnings[] = 'Ensure proper input validation is implemented';
|
|
}
|
|
|
|
// Check for CORS configuration
|
|
if (!$this->hasCORSConfig($projectPath)) {
|
|
$warnings[] = 'No CORS configuration found';
|
|
}
|
|
|
|
// Check for tests
|
|
if (!$this->hasTests($projectPath)) {
|
|
$warnings[] = 'No API tests found';
|
|
}
|
|
|
|
$this->log(
|
|
'API project validation completed',
|
|
'info',
|
|
['errors' => count($errors), 'warnings' => count($warnings), 'type' => $apiType]
|
|
);
|
|
|
|
return [
|
|
'valid' => empty($errors),
|
|
'errors' => $errors,
|
|
'warnings' => $warnings,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function collectMetrics(string $projectPath, array $config): array
|
|
{
|
|
$apiType = $this->detectAPIType($projectPath);
|
|
$language = $this->detectLanguage($projectPath);
|
|
|
|
$metrics = [
|
|
'api_type' => $apiType,
|
|
'language' => $language,
|
|
'has_documentation' => $this->hasAPIDocumentation($projectPath, $apiType),
|
|
'has_authentication' => $this->hasAuthentication($projectPath),
|
|
'has_authorization' => $this->hasAuthorization($projectPath),
|
|
'has_rate_limiting' => $this->hasRateLimiting($projectPath),
|
|
'has_logging' => $this->hasLogging($projectPath),
|
|
'has_monitoring' => $this->hasMonitoring($projectPath),
|
|
'has_caching' => $this->hasCaching($projectPath),
|
|
'has_tests' => $this->hasTests($projectPath),
|
|
'has_docker' => $this->fileExists($projectPath, 'Dockerfile'),
|
|
'has_ci' => $this->hasCICD($projectPath),
|
|
'has_kubernetes' => $this->hasKubernetes($projectPath),
|
|
];
|
|
|
|
// Count endpoints
|
|
$metrics['endpoints_count'] = $this->countEndpoints($projectPath, $apiType, $language);
|
|
|
|
// Count routes/controllers
|
|
$metrics['routes_count'] = $this->countRoutes($projectPath, $language);
|
|
|
|
// Count middleware
|
|
$metrics['middleware_count'] = $this->countMiddleware($projectPath, $language);
|
|
|
|
// Count lines of code
|
|
$metrics['total_lines'] = $this->countTotalLines($projectPath, $language);
|
|
|
|
// Detect framework
|
|
$metrics['framework'] = $this->detectFramework($projectPath, $language);
|
|
|
|
// Record metrics
|
|
$this->recordMetric('api', 'endpoints', $metrics['endpoints_count']);
|
|
$this->recordMetric('api', 'total_lines', $metrics['total_lines']);
|
|
|
|
$this->log('Collected API metrics', 'info', $metrics);
|
|
|
|
return $metrics;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function healthCheck(string $projectPath, array $config): array
|
|
{
|
|
$issues = [];
|
|
$score = 100;
|
|
|
|
$apiType = $this->detectAPIType($projectPath);
|
|
|
|
// Check for API documentation
|
|
if (!$this->hasAPIDocumentation($projectPath, $apiType)) {
|
|
$issues[] = [
|
|
'severity' => 'warning',
|
|
'message' => 'Missing API documentation',
|
|
];
|
|
$score -= 15;
|
|
}
|
|
|
|
// Check for authentication
|
|
if (!$this->hasAuthentication($projectPath)) {
|
|
$issues[] = [
|
|
'severity' => 'critical',
|
|
'message' => 'No authentication mechanism detected',
|
|
];
|
|
$score -= 20;
|
|
}
|
|
|
|
// Check for authorization
|
|
if (!$this->hasAuthorization($projectPath)) {
|
|
$issues[] = [
|
|
'severity' => 'warning',
|
|
'message' => 'No authorization/access control detected',
|
|
];
|
|
$score -= 10;
|
|
}
|
|
|
|
// Check for input validation
|
|
if (!$this->hasInputValidation($projectPath)) {
|
|
$issues[] = [
|
|
'severity' => 'critical',
|
|
'message' => 'Input validation may be missing',
|
|
];
|
|
$score -= 20;
|
|
}
|
|
|
|
// Check for rate limiting
|
|
if (!$this->hasRateLimiting($projectPath)) {
|
|
$issues[] = [
|
|
'severity' => 'warning',
|
|
'message' => 'No rate limiting configured',
|
|
];
|
|
$score -= 10;
|
|
}
|
|
|
|
// Check for logging
|
|
if (!$this->hasLogging($projectPath)) {
|
|
$issues[] = [
|
|
'severity' => 'warning',
|
|
'message' => 'No logging configuration found',
|
|
];
|
|
$score -= 10;
|
|
}
|
|
|
|
// Check for tests
|
|
if (!$this->hasTests($projectPath)) {
|
|
$issues[] = [
|
|
'severity' => 'warning',
|
|
'message' => 'No API tests found',
|
|
];
|
|
$score -= 15;
|
|
}
|
|
|
|
// Check for README
|
|
if (!$this->fileExists($projectPath, 'README.md')) {
|
|
$issues[] = [
|
|
'severity' => 'warning',
|
|
'message' => 'Missing README.md',
|
|
];
|
|
$score -= 5;
|
|
}
|
|
|
|
// Check for environment configuration
|
|
if (!$this->fileExists($projectPath, '.env.example')) {
|
|
$issues[] = [
|
|
'severity' => 'info',
|
|
'message' => 'Missing .env.example for configuration',
|
|
];
|
|
$score -= 5;
|
|
}
|
|
|
|
$score = max(0, $score);
|
|
|
|
$this->log('API health check completed', 'info', [
|
|
'score' => $score,
|
|
'issues_count' => count($issues),
|
|
'api_type' => $apiType,
|
|
]);
|
|
|
|
return [
|
|
'healthy' => $score >= 70,
|
|
'score' => $score,
|
|
'issues' => $issues,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function getRequiredFiles(): array
|
|
{
|
|
return [
|
|
'API documentation (openapi.yaml, swagger.json, schema.graphql)',
|
|
'Authentication configuration',
|
|
'Error handling middleware',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function getRecommendedFiles(): array
|
|
{
|
|
return [
|
|
'README.md',
|
|
'.env.example',
|
|
'openapi.yaml or swagger.json (REST)',
|
|
'schema.graphql (GraphQL)',
|
|
'Dockerfile',
|
|
'docker-compose.yml',
|
|
'kubernetes/*.yaml',
|
|
'tests/ or test/',
|
|
'.mokogitea/workflows/* or .gitea/workflows/* or .gitlab-ci.yml',
|
|
'middleware/ or middlewares/',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function getConfigSchema(): array
|
|
{
|
|
return [
|
|
'type' => 'object',
|
|
'properties' => [
|
|
'api_type' => [
|
|
'type' => 'string',
|
|
'enum' => ['rest', 'graphql', 'grpc', 'soap', 'websocket'],
|
|
'description' => 'API type',
|
|
],
|
|
'authentication' => [
|
|
'type' => 'string',
|
|
'enum' => ['jwt', 'oauth2', 'api-key', 'basic', 'none'],
|
|
'description' => 'Authentication method',
|
|
],
|
|
'framework' => [
|
|
'type' => 'string',
|
|
'description' => 'Framework used (Express, FastAPI, Spring Boot, etc.)',
|
|
],
|
|
'enable_rate_limiting' => [
|
|
'type' => 'boolean',
|
|
'description' => 'Enable rate limiting',
|
|
],
|
|
'enable_caching' => [
|
|
'type' => 'boolean',
|
|
'description' => 'Enable response caching',
|
|
],
|
|
'port' => [
|
|
'type' => 'integer',
|
|
'description' => 'API server port',
|
|
],
|
|
],
|
|
'required' => ['api_type'],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function getBestPractices(): array
|
|
{
|
|
return [
|
|
'Document API with OpenAPI/Swagger or GraphQL schema',
|
|
'Implement proper authentication (JWT, OAuth2)',
|
|
'Use authorization for access control',
|
|
'Validate all input data',
|
|
'Implement rate limiting to prevent abuse',
|
|
'Use standardized error responses',
|
|
'Implement comprehensive logging',
|
|
'Add monitoring and metrics collection',
|
|
'Use HTTPS/TLS for all endpoints',
|
|
'Implement CORS properly',
|
|
'Version your API endpoints',
|
|
'Use pagination for list endpoints',
|
|
'Implement caching where appropriate',
|
|
'Write comprehensive API tests',
|
|
'Use Docker for consistent deployments',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Detect API type
|
|
*/
|
|
private function detectAPIType(string $projectPath): string
|
|
{
|
|
// GraphQL
|
|
if (
|
|
$this->fileExists($projectPath, 'schema.graphql') ||
|
|
$this->fileExists($projectPath, '*.graphql')
|
|
) {
|
|
return 'graphql';
|
|
}
|
|
|
|
// gRPC
|
|
if ($this->fileExists($projectPath, '*.proto')) {
|
|
return 'grpc';
|
|
}
|
|
|
|
// REST (OpenAPI/Swagger)
|
|
if (
|
|
$this->fileExists($projectPath, 'openapi.yaml') ||
|
|
$this->fileExists($projectPath, 'openapi.json') ||
|
|
$this->fileExists($projectPath, 'swagger.yaml') ||
|
|
$this->fileExists($projectPath, 'swagger.json')
|
|
) {
|
|
return 'rest';
|
|
}
|
|
|
|
// Check code for REST patterns
|
|
$files = $this->findFiles($projectPath, '**/*.{js,ts,py,java,go,php}');
|
|
foreach (array_slice($files, 0, 10) as $file) {
|
|
if (is_file($file)) {
|
|
$content = @file_get_contents($file);
|
|
if ($content) {
|
|
if (
|
|
preg_match('/@(Get|Post|Put|Delete|Patch)\(/', $content) ||
|
|
preg_match('/(get|post|put|delete|patch)\s*\([\'"]/', $content)
|
|
) {
|
|
return 'rest';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 'rest';
|
|
}
|
|
|
|
/**
|
|
* Detect language
|
|
*/
|
|
private function detectLanguage(string $projectPath): string
|
|
{
|
|
$counts = [
|
|
'js' => $this->countFiles($projectPath, '**/*.js'),
|
|
'ts' => $this->countFiles($projectPath, '**/*.ts'),
|
|
'py' => $this->countFiles($projectPath, '**/*.py'),
|
|
'java' => $this->countFiles($projectPath, '**/*.java'),
|
|
'go' => $this->countFiles($projectPath, '**/*.go'),
|
|
'php' => $this->countFiles($projectPath, '**/*.php'),
|
|
];
|
|
|
|
arsort($counts);
|
|
$topLang = array_key_first($counts);
|
|
|
|
$langMap = [
|
|
'js' => 'JavaScript',
|
|
'ts' => 'TypeScript',
|
|
'py' => 'Python',
|
|
'java' => 'Java',
|
|
'go' => 'Go',
|
|
'php' => 'PHP',
|
|
];
|
|
|
|
return $langMap[$topLang] ?? 'Unknown';
|
|
}
|
|
|
|
/**
|
|
* Check for API documentation
|
|
*/
|
|
private function hasAPIDocumentation(string $projectPath, string $apiType): bool
|
|
{
|
|
if ($apiType === 'graphql') {
|
|
return $this->fileExists($projectPath, 'schema.graphql') ||
|
|
$this->countFiles($projectPath, '**/*.graphql') > 0;
|
|
}
|
|
|
|
if ($apiType === 'grpc') {
|
|
return $this->countFiles($projectPath, '**/*.proto') > 0;
|
|
}
|
|
|
|
// REST
|
|
return $this->fileExists($projectPath, 'openapi.yaml') ||
|
|
$this->fileExists($projectPath, 'openapi.json') ||
|
|
$this->fileExists($projectPath, 'swagger.yaml') ||
|
|
$this->fileExists($projectPath, 'swagger.json');
|
|
}
|
|
|
|
/**
|
|
* Check for error handling
|
|
*/
|
|
private function hasErrorHandling(string $projectPath): bool
|
|
{
|
|
$files = $this->findFiles($projectPath, '**/*.{js,ts,py}');
|
|
|
|
foreach (array_slice($files, 0, 10) as $file) {
|
|
if (is_file($file)) {
|
|
$content = @file_get_contents($file);
|
|
if (
|
|
$content && (
|
|
strpos($content, 'errorHandler') !== false ||
|
|
strpos($content, 'error_handler') !== false ||
|
|
preg_match('/class\s+\w*Error/', $content)
|
|
)
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check for authentication
|
|
*/
|
|
private function hasAuthentication(string $projectPath): bool
|
|
{
|
|
$files = $this->findFiles($projectPath, '**/*.{js,ts,py,java,go,php}');
|
|
|
|
foreach (array_slice($files, 0, 15) as $file) {
|
|
if (is_file($file)) {
|
|
$content = @file_get_contents($file);
|
|
if (
|
|
$content && (
|
|
stripos($content, 'jwt') !== false ||
|
|
stripos($content, 'oauth') !== false ||
|
|
stripos($content, 'passport') !== false ||
|
|
stripos($content, 'authenticate') !== false ||
|
|
stripos($content, 'api_key') !== false ||
|
|
stripos($content, 'bearer') !== false
|
|
)
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check for authorization
|
|
*/
|
|
private function hasAuthorization(string $projectPath): bool
|
|
{
|
|
$files = $this->findFiles($projectPath, '**/*.{js,ts,py,java,go,php}');
|
|
|
|
foreach (array_slice($files, 0, 10) as $file) {
|
|
if (is_file($file)) {
|
|
$content = @file_get_contents($file);
|
|
if (
|
|
$content && (
|
|
stripos($content, 'authorize') !== false ||
|
|
stripos($content, 'permission') !== false ||
|
|
stripos($content, 'role') !== false ||
|
|
stripos($content, 'acl') !== false
|
|
)
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check for rate limiting
|
|
*/
|
|
private function hasRateLimiting(string $projectPath): bool
|
|
{
|
|
$files = $this->findFiles($projectPath, '**/*.{js,ts,py,java,go,php}');
|
|
|
|
foreach (array_slice($files, 0, 10) as $file) {
|
|
if (is_file($file)) {
|
|
$content = @file_get_contents($file);
|
|
if (
|
|
$content && (
|
|
stripos($content, 'rate_limit') !== false ||
|
|
stripos($content, 'rateLimit') !== false ||
|
|
stripos($content, 'throttle') !== false
|
|
)
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check for logging
|
|
*/
|
|
private function hasLogging(string $projectPath): bool
|
|
{
|
|
$files = $this->findFiles($projectPath, '**/*.{js,ts,py,java,go,php}');
|
|
|
|
foreach (array_slice($files, 0, 10) as $file) {
|
|
if (is_file($file)) {
|
|
$content = @file_get_contents($file);
|
|
if (
|
|
$content && (
|
|
stripos($content, 'logger') !== false ||
|
|
stripos($content, 'winston') !== false ||
|
|
stripos($content, 'logging') !== false ||
|
|
stripos($content, 'log.') !== false
|
|
)
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check for monitoring
|
|
*/
|
|
private function hasMonitoring(string $projectPath): bool
|
|
{
|
|
$files = $this->findFiles($projectPath, '**/*.{js,ts,py,java,go,php}');
|
|
|
|
foreach (array_slice($files, 0, 10) as $file) {
|
|
if (is_file($file)) {
|
|
$content = @file_get_contents($file);
|
|
if (
|
|
$content && (
|
|
stripos($content, 'prometheus') !== false ||
|
|
stripos($content, 'metrics') !== false ||
|
|
stripos($content, 'monitoring') !== false ||
|
|
stripos($content, 'newrelic') !== false
|
|
)
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check for caching
|
|
*/
|
|
private function hasCaching(string $projectPath): bool
|
|
{
|
|
$files = $this->findFiles($projectPath, '**/*.{js,ts,py,java,go,php}');
|
|
|
|
foreach (array_slice($files, 0, 10) as $file) {
|
|
if (is_file($file)) {
|
|
$content = @file_get_contents($file);
|
|
if (
|
|
$content && (
|
|
stripos($content, 'redis') !== false ||
|
|
stripos($content, 'cache') !== false ||
|
|
stripos($content, 'memcached') !== false
|
|
)
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check for input validation
|
|
*/
|
|
private function hasInputValidation(string $projectPath): bool
|
|
{
|
|
$files = $this->findFiles($projectPath, '**/*.{js,ts,py,java,go,php}');
|
|
|
|
foreach (array_slice($files, 0, 10) as $file) {
|
|
if (is_file($file)) {
|
|
$content = @file_get_contents($file);
|
|
if (
|
|
$content && (
|
|
stripos($content, 'validate') !== false ||
|
|
stripos($content, 'validator') !== false ||
|
|
stripos($content, 'joi') !== false ||
|
|
stripos($content, 'yup') !== false
|
|
)
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check for CORS configuration
|
|
*/
|
|
private function hasCORSConfig(string $projectPath): bool
|
|
{
|
|
$files = $this->findFiles($projectPath, '**/*.{js,ts,py,java,go,php}');
|
|
|
|
foreach (array_slice($files, 0, 10) as $file) {
|
|
if (is_file($file)) {
|
|
$content = @file_get_contents($file);
|
|
if ($content && stripos($content, 'cors') !== false) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check for tests
|
|
*/
|
|
private function hasTests(string $projectPath): bool
|
|
{
|
|
return $this->fileExists($projectPath, 'tests') ||
|
|
$this->fileExists($projectPath, 'test') ||
|
|
$this->fileExists($projectPath, '__tests__') ||
|
|
$this->countFiles($projectPath, '**/*.test.*') > 0 ||
|
|
$this->countFiles($projectPath, '**/*.spec.*') > 0;
|
|
}
|
|
|
|
/**
|
|
* Check for CI/CD
|
|
*/
|
|
private function hasCICD(string $projectPath): bool
|
|
{
|
|
return $this->fileExists($projectPath, '.mokogitea/workflows') ||
|
|
$this->fileExists($projectPath, '.mokogitea/workflows') ||
|
|
$this->fileExists($projectPath, '.gitlab-ci.yml') ||
|
|
$this->fileExists($projectPath, 'Jenkinsfile') ||
|
|
$this->fileExists($projectPath, '.circleci');
|
|
}
|
|
|
|
/**
|
|
* Check for Kubernetes
|
|
*/
|
|
private function hasKubernetes(string $projectPath): bool
|
|
{
|
|
return $this->fileExists($projectPath, 'k8s') ||
|
|
$this->fileExists($projectPath, 'kubernetes') ||
|
|
$this->countFiles($projectPath, '**/*.yaml') > 0;
|
|
}
|
|
|
|
/**
|
|
* Count endpoints
|
|
*/
|
|
private function countEndpoints(string $projectPath, string $apiType, string $language): int
|
|
{
|
|
$count = 0;
|
|
$pattern = $language === 'Python' ? '**/*.py' : '**/*.{js,ts}';
|
|
$files = $this->findFiles($projectPath, $pattern);
|
|
|
|
foreach ($files as $file) {
|
|
if (is_file($file)) {
|
|
$content = @file_get_contents($file);
|
|
if ($content) {
|
|
$count += preg_match_all('/@(app\.)?(get|post|put|delete|patch)\s*\(/', $content);
|
|
$count += preg_match_all('/\.(get|post|put|delete|patch)\s*\([\'"]/', $content);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $count;
|
|
}
|
|
|
|
/**
|
|
* Count routes
|
|
*/
|
|
private function countRoutes(string $projectPath, string $language): int
|
|
{
|
|
$routeFiles = array_merge(
|
|
$this->findFiles($projectPath, '**/routes/**/*.{js,ts,py}'),
|
|
$this->findFiles($projectPath, '**/route*.{js,ts,py}')
|
|
);
|
|
|
|
return count($routeFiles);
|
|
}
|
|
|
|
/**
|
|
* Count middleware
|
|
*/
|
|
private function countMiddleware(string $projectPath, string $language): int
|
|
{
|
|
$middlewareFiles = array_merge(
|
|
$this->findFiles($projectPath, '**/middleware/**/*.{js,ts,py}'),
|
|
$this->findFiles($projectPath, '**/middlewares/**/*.{js,ts,py}')
|
|
);
|
|
|
|
return count($middlewareFiles);
|
|
}
|
|
|
|
/**
|
|
* Count total lines
|
|
*/
|
|
private function countTotalLines(string $projectPath, string $language): int
|
|
{
|
|
$extMap = [
|
|
'JavaScript' => ['js'],
|
|
'TypeScript' => ['ts'],
|
|
'Python' => ['py'],
|
|
'Java' => ['java'],
|
|
'Go' => ['go'],
|
|
'PHP' => ['php'],
|
|
];
|
|
|
|
$extensions = $extMap[$language] ?? ['js', 'ts', 'py'];
|
|
$totalLines = 0;
|
|
|
|
foreach ($extensions as $ext) {
|
|
$files = $this->findFiles($projectPath, "**/*.{$ext}");
|
|
foreach ($files as $file) {
|
|
if (is_file($file)) {
|
|
$totalLines += count(file($file));
|
|
}
|
|
}
|
|
}
|
|
|
|
return $totalLines;
|
|
}
|
|
|
|
/**
|
|
* Detect framework
|
|
*/
|
|
private function detectFramework(string $projectPath, string $language): string
|
|
{
|
|
if ($language === 'JavaScript' || $language === 'TypeScript') {
|
|
$packageData = $this->parseJsonFile($projectPath, 'package.json');
|
|
if ($packageData) {
|
|
$deps = array_merge(
|
|
$packageData['dependencies'] ?? [],
|
|
$packageData['devDependencies'] ?? []
|
|
);
|
|
|
|
if (isset($deps['express'])) {
|
|
return 'Express';
|
|
}
|
|
if (isset($deps['fastify'])) {
|
|
return 'Fastify';
|
|
}
|
|
if (isset($deps['@nestjs/core'])) {
|
|
return 'NestJS';
|
|
}
|
|
if (isset($deps['koa'])) {
|
|
return 'Koa';
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($language === 'Python') {
|
|
$requirements = $this->readFile($projectPath, 'requirements.txt');
|
|
if ($requirements) {
|
|
if (stripos($requirements, 'fastapi') !== false) {
|
|
return 'FastAPI';
|
|
}
|
|
if (stripos($requirements, 'flask') !== false) {
|
|
return 'Flask';
|
|
}
|
|
if (stripos($requirements, 'django') !== false) {
|
|
return 'Django';
|
|
}
|
|
}
|
|
}
|
|
|
|
return 'Unknown';
|
|
}
|
|
}
|