Enable Discovery debug logging for production troubleshooting

- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
2025-08-11 20:13:26 +02:00
parent 59fd3dd3b1
commit 55a330b223
3683 changed files with 2956207 additions and 16948 deletions

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace App\Framework\Markdown;
@@ -7,40 +8,179 @@ final readonly class MarkdownConverter
{
public function toHtml(string $markdown): string
{
// Einfacher Markdown-zu-HTML Converter
$html = $markdown;
$lines = explode("\n", $markdown);
$html = [];
$inCodeBlock = false;
$inList = false;
$listType = null;
$currentParagraph = [];
// Headers
$html = preg_replace('/^# (.+)$/m', '<h1>$1</h1>', $html);
$html = preg_replace('/^## (.+)$/m', '<h2>$1</h2>', $html);
$html = preg_replace('/^### (.+)$/m', '<h3>$1</h3>', $html);
foreach ($lines as $line) {
$trimmed = trim($line);
// Bold und Italic
$html = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $html);
$html = preg_replace('/\*(.+?)\*/', '<em>$1</em>', $html);
// Code blocks
if (str_starts_with($trimmed, '```')) {
if ($inCodeBlock) {
$html[] = '</code></pre>';
$inCodeBlock = false;
} else {
$this->flushParagraph($currentParagraph, $html);
$this->closeList($inList, $html);
// Code Blöcke
$html = preg_replace('/```(\w+)?\n(.*?)\n```/s', '<pre><code class="language-$1">$2</code></pre>', $html);
$html = preg_replace('/`(.+?)`/', '<code>$1</code>', $html);
$language = substr($trimmed, 3);
$langClass = $language ? " class=\"language-{$language}\"" : '';
$html[] = "<pre><code{$langClass}>";
$inCodeBlock = true;
}
continue;
}
if ($inCodeBlock) {
$html[] = htmlspecialchars($line);
continue;
}
// Empty line - close current paragraph/list
if ($trimmed === '') {
$this->flushParagraph($currentParagraph, $html);
$this->closeList($inList, $html);
continue;
}
// Headers
if (preg_match('/^(#{1,6})\s+(.+)$/', $trimmed, $matches)) {
$this->flushParagraph($currentParagraph, $html);
$this->closeList($inList, $html);
$level = strlen($matches[1]);
$text = $this->parseInlineElements($matches[2]);
$html[] = "<h{$level}>{$text}</h{$level}>";
continue;
}
// Lists
if (preg_match('/^[\-\*\+]\s+(.+)$/', $trimmed, $matches)) {
$this->flushParagraph($currentParagraph, $html);
$this->handleList($inList, $listType, 'ul', $html);
$text = $this->parseInlineElements($matches[1]);
$html[] = "<li>{$text}</li>";
continue;
}
if (preg_match('/^\d+\.\s+(.+)$/', $trimmed, $matches)) {
$this->flushParagraph($currentParagraph, $html);
$this->handleList($inList, $listType, 'ol', $html);
$text = $this->parseInlineElements($matches[1]);
$html[] = "<li>{$text}</li>";
continue;
}
// Blockquotes
if (str_starts_with($trimmed, '> ')) {
$this->flushParagraph($currentParagraph, $html);
$this->closeList($inList, $html);
$text = $this->parseInlineElements(substr($trimmed, 2));
$html[] = "<blockquote>{$text}</blockquote>";
continue;
}
// Horizontal rules
if (preg_match('/^[\-\*_]{3,}$/', $trimmed)) {
$this->flushParagraph($currentParagraph, $html);
$this->closeList($inList, $html);
$html[] = '<hr>';
continue;
}
// Regular paragraph text
$this->closeList($inList, $html);
$currentParagraph[] = $this->parseInlineElements($trimmed);
}
// Flush remaining content
$this->flushParagraph($currentParagraph, $html);
$this->closeList($inList, $html);
return implode("\n", $html);
}
private function parseInlineElements(string $text): string
{
// Code spans (before other formatting)
$text = preg_replace('/`([^`]+)`/', '<code>$1</code>', $text);
// Bold and italic
$text = preg_replace('/\*\*\*(.+?)\*\*\*/', '<strong><em>$1</em></strong>', $text);
$text = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $text);
$text = preg_replace('/\*(.+?)\*/', '<em>$1</em>', $text);
// Alternative bold/italic syntax
$text = preg_replace('/___(.+?)___/', '<strong><em>$1</em></strong>', $text);
$text = preg_replace('/__(.+?)__/', '<strong>$1</strong>', $text);
$text = preg_replace('/_(.+?)_/', '<em>$1</em>', $text);
// Links
$html = preg_replace('/\[(.+?)\]\((.+?)\)/', '<a href="$2">$1</a>', $html);
$text = preg_replace('/\[([^\]]+)\]\(([^)]+)\)/', '<a href="$2">$1</a>', $text);
// Listen
$html = preg_replace('/^- (.+)$/m', '<li>$1</li>', $html);
$html = preg_replace('/(<li>.*<\/li>)/s', '<ul>$1</ul>', $html);
// Images
$text = preg_replace('/!\[([^\]]*)\]\(([^)]+)\)/', '<img src="$2" alt="$1">', $text);
// Blockquotes
$html = preg_replace('/^> (.+)$/m', '<blockquote>$1</blockquote>', $html);
return $text;
}
// Paragraphen
$html = preg_replace('/\n\n/', '</p><p>', $html);
$html = '<p>' . $html . '</p>';
private function flushParagraph(array &$currentParagraph, array &$html): void
{
if (! empty($currentParagraph)) {
$text = implode(' ', $currentParagraph);
$html[] = "<p>{$text}</p>";
$currentParagraph = [];
}
}
// Zeilenumbrüche
$html = preg_replace('/\n/', '<br>', $html);
private function handleList(bool &$inList, ?string &$listType, string $newType, array &$html): void
{
if (! $inList) {
$html[] = "<{$newType}>";
$inList = true;
$listType = $newType;
} elseif ($listType !== $newType) {
$html[] = "</{$listType}>";
$html[] = "<{$newType}>";
$listType = $newType;
}
}
return $html;
private function closeList(bool &$inList, array &$html): void
{
if ($inList) {
$html[] = "</{$this->getListType($html)}>";
$inList = false;
}
}
private function getListType(array $html): string
{
for ($i = count($html) - 1; $i >= 0; $i--) {
if (str_starts_with($html[$i], '<ul>')) {
return 'ul';
}
if (str_starts_with($html[$i], '<ol>')) {
return 'ol';
}
}
return 'ul';
}
public function stripMarkdown(string $markdown): string

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace App\Framework\Markdown;
/**
* Complete Markdown renderer with styling and themes
*/
final readonly class MarkdownRenderer
{
public function __construct(
private MarkdownConverter $converter,
) {
}
public function render(string $markdown, string $theme = 'default', array $options = []): string
{
$html = $this->converter->toHtml($markdown);
return $this->wrapWithLayout($html, $theme, $options);
}
public function renderFile(string $filePath, string $theme = 'default', array $options = []): string
{
if (! file_exists($filePath)) {
throw new \InvalidArgumentException("Markdown file not found: {$filePath}");
}
$markdown = file_get_contents($filePath);
return $this->render($markdown, $theme, $options);
}
public function renderToHtmlOnly(string $markdown): string
{
return $this->converter->toHtml($markdown);
}
private function wrapWithLayout(string $content, string $theme, array $options): string
{
$markdownTheme = new MarkdownTheme($theme);
$styles = $markdownTheme->getStyles();
$title = $options['title'] ?? 'Documentation';
$viewport = $options['viewport'] ?? 'width=device-width, initial-scale=1.0';
$extraStyles = $options['extraStyles'] ?? '';
$extraScripts = $options['extraScripts'] ?? '';
$syntaxHighlighting = $options['syntaxHighlighting'] ?? true;
$syntaxHighlightingScript = $syntaxHighlighting ? $this->getSyntaxHighlightingScript() : '';
return <<<HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="{$viewport}">
<title>{$title}</title>
<style>{$styles}{$extraStyles}</style>
{$syntaxHighlightingScript}
</head>
<body>
<div class="markdown-content">
{$content}
</div>
{$extraScripts}
</body>
</html>
HTML;
}
private function getSyntaxHighlightingScript(): string
{
return <<<HTML
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-php.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-bash.min.js"></script>
HTML;
}
}

View File

@@ -0,0 +1,337 @@
<?php
declare(strict_types=1);
namespace App\Framework\Markdown;
/**
* Theme system for Markdown rendering
*/
final readonly class MarkdownTheme
{
public function __construct(
private string $name = 'default',
private array $colors = [],
private array $fonts = [],
private array $spacing = [],
) {
}
public function getStyles(): string
{
return match ($this->name) {
'github' => $this->getGitHubTheme(),
'docs' => $this->getDocsTheme(),
'api' => $this->getApiTheme(),
default => $this->getDefaultTheme(),
};
}
private function getDefaultTheme(): string
{
return <<<CSS
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #333;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background: #fff;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 24px;
margin-bottom: 16px;
font-weight: 600;
line-height: 1.25;
color: #1a1a1a;
}
h1 { font-size: 2em; border-bottom: 1px solid #eaecef; padding-bottom: 10px; }
h2 { font-size: 1.5em; border-bottom: 1px solid #eaecef; padding-bottom: 8px; }
h3 { font-size: 1.25em; }
h4 { font-size: 1em; }
h5 { font-size: 0.875em; }
h6 { font-size: 0.85em; color: #6a737d; }
p {
margin-bottom: 16px;
margin-top: 0;
}
ul, ol {
margin-bottom: 16px;
padding-left: 30px;
}
li {
margin-bottom: 4px;
}
code {
background: #f6f8fa;
border-radius: 3px;
font-size: 85%;
margin: 0;
padding: 0.2em 0.4em;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
}
pre {
background: #f6f8fa;
border-radius: 6px;
overflow: auto;
padding: 16px;
margin-bottom: 16px;
font-size: 85%;
line-height: 1.45;
}
pre code {
background: transparent;
border: 0;
display: inline;
line-height: inherit;
margin: 0;
overflow: visible;
padding: 0;
word-wrap: normal;
}
blockquote {
border-left: 4px solid #dfe2e5;
margin: 0 0 16px 0;
padding: 0 16px;
color: #6a737d;
}
table {
border-spacing: 0;
border-collapse: collapse;
width: 100%;
margin-bottom: 16px;
}
th, td {
border: 1px solid #dfe2e5;
padding: 6px 13px;
}
th {
background: #f6f8fa;
font-weight: 600;
}
a {
color: #0366d6;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
hr {
border: none;
border-top: 1px solid #e1e4e8;
margin: 24px 0;
}
img {
max-width: 100%;
height: auto;
}
CSS;
}
private function getGitHubTheme(): string
{
return $this->getDefaultTheme();
}
private function getDocsTheme(): string
{
return <<<CSS
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
line-height: 1.7;
color: #2d3748;
max-width: 900px;
margin: 0 auto;
padding: 40px 20px;
background: #fafafa;
}
.markdown-content {
background: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1, h2, h3, h4, h5, h6 {
color: #1a202c;
font-weight: 700;
margin-top: 32px;
margin-bottom: 16px;
}
h1 {
font-size: 2.5em;
border-bottom: 3px solid #4299e1;
padding-bottom: 16px;
margin-top: 0;
}
h2 {
font-size: 1.875em;
border-bottom: 2px solid #e2e8f0;
padding-bottom: 12px;
}
h3 { font-size: 1.5em; color: #2d3748; }
code {
background: #edf2f7;
color: #e53e3e;
border-radius: 4px;
padding: 2px 6px;
font-family: 'Fira Code', monospace;
font-size: 0.9em;
}
pre {
background: #2d3748;
color: #e2e8f0;
border-radius: 8px;
padding: 24px;
overflow-x: auto;
margin: 24px 0;
}
pre code {
background: transparent;
color: inherit;
padding: 0;
}
blockquote {
border-left: 4px solid #4299e1;
background: #ebf8ff;
margin: 24px 0;
padding: 16px 24px;
border-radius: 0 8px 8px 0;
}
ul, ol {
margin: 16px 0;
padding-left: 24px;
}
li {
margin-bottom: 8px;
}
a {
color: #4299e1;
font-weight: 500;
}
a:hover {
color: #2b6cb0;
}
CSS;
}
private function getApiTheme(): string
{
return <<<CSS
body {
font-family: 'Source Sans Pro', sans-serif;
line-height: 1.6;
color: #2c3e50;
max-width: 1000px;
margin: 0 auto;
padding: 20px;
background: #ecf0f1;
}
.markdown-content {
background: white;
padding: 30px;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
h1, h2, h3 {
color: #34495e;
font-weight: 600;
}
h1 {
font-size: 2.2em;
color: #2980b9;
border-bottom: 3px solid #3498db;
padding-bottom: 12px;
}
h2 {
font-size: 1.6em;
color: #27ae60;
margin-top: 32px;
}
h3 {
font-size: 1.3em;
color: #8e44ad;
}
code {
background: #f8f9fa;
color: #e74c3c;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 2px 6px;
font-family: 'Monaco', 'Menlo', monospace;
}
pre {
background: #2c3e50;
color: #ecf0f1;
border-radius: 8px;
padding: 20px;
border-left: 4px solid #3498db;
}
pre code {
background: transparent;
color: inherit;
border: none;
padding: 0;
}
.method {
display: inline-block;
padding: 4px 12px;
border-radius: 20px;
font-weight: bold;
color: white;
margin-right: 10px;
font-size: 0.8em;
text-transform: uppercase;
}
.method.get { background: #27ae60; }
.method.post { background: #f39c12; }
.method.put { background: #e67e22; }
.method.delete { background: #e74c3c; }
blockquote {
border-left: 4px solid #3498db;
background: #ebf3fd;
margin: 20px 0;
padding: 15px 20px;
border-radius: 0 6px 6px 0;
}
CSS;
}
}