'Validate YAML only, do not write HTML', '--serve' => 'Print the URL at which the docs are served', ]; public function run(array $params) { $projectRoot = ROOTPATH; $openapiFile = $projectRoot . 'openapi/openapi.yaml'; $outputFile = $projectRoot . 'public/api-docs.html'; // ── Validate YAML exists ────────────────────────────────────────── if (!file_exists($openapiFile)) { CLI::error('[ERROR] openapi/openapi.yaml not found at: ' . $openapiFile); CLI::write('Create it first, then run this command again.', 'yellow'); return EXIT_ERROR; } $yamlContent = file_get_contents($openapiFile); if (empty($yamlContent)) { CLI::error('[ERROR] openapi/openapi.yaml is empty.'); return EXIT_ERROR; } // Basic structural validation (line count, presence of openapi/info/paths) $lines = explode("\n", $yamlContent); $hasOpenapi = preg_match('/^openapi:/m', $yamlContent); $hasInfo = preg_match('/^info:/m', $yamlContent); $hasPaths = preg_match('/^paths:/m', $yamlContent); CLI::write(sprintf(' Spec file: %s', $openapiFile), 'green'); CLI::write(sprintf(' Size: %d bytes', strlen($yamlContent)), 'green'); CLI::write(sprintf(' Lines: %d', count($lines)), 'green'); $errors = []; if (!$hasOpenapi) $errors[] = 'Missing "openapi:" version declaration'; if (!$hasInfo) $errors[] = 'Missing "info:" section'; if (!$hasPaths) $errors[] = 'Missing "paths:" section'; $totalPaths = 0; if (preg_match_all('/^\s{2}\/[a-z]/m', $yamlContent, $matches)) { $totalPaths = count($matches[0]); } CLI::write(sprintf(' Endpoints: %d', $totalPaths), 'green'); if (!empty($errors)) { CLI::error('[VALIDATION] ' . count($errors) . ' issue(s) found:'); foreach ($errors as $err) { CLI::write(' - ' . $err, 'red'); } return EXIT_ERROR; } CLI::write('[VALIDATION] OpenAPI spec looks valid.', 'green'); // ── --watch mode: stop here ──────────────────────────────────────── if (isset($params['watch']) || array_key_exists('watch', $params)) { CLI::write('Watch mode — no files written.', 'yellow'); return EXIT_SUCCESS; } // ── Generate HTML ──────────────────────────────────────────────── $apiTitle = 'Todo App API Documentation'; // Escape YAML for embedding as a JS template literal. // Safe: escape backtick, backslash, and template substitution. $escapedYaml = str_replace( ['\\', '`', '${'], ['\\\\', '\\`', '\\${'], $yamlContent ); $html = << {$apiTitle}

{$apiTitle}

Todo App Backend — OpenAPI 3.0
Generated: GENERATED_DATE
Loading API documentation...
HTML; $html = str_replace( ['YAML_CONTENT', 'GENERATED_DATE'], [$escapedYaml, date('Y-m-d H:i:s')], $html ); file_put_contents($outputFile, $html); CLI::write(sprintf('[DONE] Docs generated: %s', $outputFile), 'green'); CLI::write(sprintf(' Size: %d bytes', filesize($outputFile)), 'green'); if (isset($params['serve']) || array_key_exists('serve', $params)) { $baseUrl = CLI::getOption('base-url') ?? 'http://localhost:8080'; CLI::write(sprintf(' Open in browser: %s/api-docs.html', $baseUrl), 'cyan'); } return EXIT_SUCCESS; } }