feat(Production): Complete production deployment infrastructure

- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace App\Framework\Vite\ValueObjects;
/**
* Value Object for Vite configuration
*
* Holds configuration for Vite integration including paths,
* dev server settings, and entrypoint configuration.
*/
final readonly class ViteConfig
{
/**
* @param string $buildDirectory Directory where Vite outputs files (relative to public/)
* @param string $manifestFileName Name of the manifest file
* @param string $devServerUrl URL of the Vite dev server
* @param array<string> $entrypoints Default entrypoints to load
* @param string|null $nonce CSP nonce for inline scripts
* @param bool $hotReload Whether to enable hot module replacement in dev
*/
public function __construct(
public string $buildDirectory = 'assets',
public string $manifestFileName = '.vite/manifest.json',
public string $devServerUrl = 'https://localhost:3000',
public array $entrypoints = ['main'],
public ?string $nonce = null,
public bool $hotReload = true
) {
}
/**
* Get the full manifest path relative to public directory
*/
public function getManifestPath(string $publicPath): string
{
return rtrim($publicPath, '/') . '/' . ltrim($this->manifestFileName, '/');
}
/**
* Get the dev server entry URL
*/
public function getDevServerEntry(string $entry): string
{
return rtrim($this->devServerUrl, '/') . '/' . ltrim($entry, '/');
}
/**
* Get the Vite client URL for HMR
*/
public function getViteClientUrl(): string
{
return rtrim($this->devServerUrl, '/') . '/@vite/client';
}
/**
* Create config with custom entrypoints
*
* @param array<string> $entrypoints
*/
public function withEntrypoints(array $entrypoints): self
{
return new self(
buildDirectory: $this->buildDirectory,
manifestFileName: $this->manifestFileName,
devServerUrl: $this->devServerUrl,
entrypoints: $entrypoints,
nonce: $this->nonce,
hotReload: $this->hotReload
);
}
/**
* Create config with CSP nonce
*/
public function withNonce(string $nonce): self
{
return new self(
buildDirectory: $this->buildDirectory,
manifestFileName: $this->manifestFileName,
devServerUrl: $this->devServerUrl,
entrypoints: $this->entrypoints,
nonce: $nonce,
hotReload: $this->hotReload
);
}
}

View File

@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace App\Framework\Vite\ValueObjects;
/**
* Value Object representing a single Vite manifest entry
*
* Represents an asset entry from Vite's manifest.json file,
* including its compiled file path, source, dependencies, and metadata.
*/
final readonly class ViteEntry
{
/**
* @param string $file Compiled output file path (e.g., "assets/main-abc123.js")
* @param string $src Source file path (e.g., "resources/js/main.js")
* @param bool $isEntry Whether this is an entry point file
* @param array<string> $css Associated CSS files
* @param array<string> $imports Import dependencies (other entry names)
* @param string|null $integrity Subresource integrity hash
*/
public function __construct(
public string $file,
public string $src,
public bool $isEntry,
public array $css = [],
public array $imports = [],
public ?string $integrity = null
) {
}
/**
* Create ViteEntry from manifest entry array
*/
public static function fromManifestEntry(string $name, array $entry): self
{
return new self(
file: $entry['file'] ?? '',
src: $entry['src'] ?? $name,
isEntry: $entry['isEntry'] ?? false,
css: $entry['css'] ?? [],
imports: $entry['imports'] ?? [],
integrity: $entry['integrity'] ?? null
);
}
/**
* Get the full URL path for this entry
*/
public function getUrl(string $baseUrl = ''): string
{
return rtrim($baseUrl, '/') . '/' . ltrim($this->file, '/');
}
/**
* Check if this entry has CSS dependencies
*/
public function hasCss(): bool
{
return ! empty($this->css);
}
/**
* Check if this entry has import dependencies
*/
public function hasImports(): bool
{
return ! empty($this->imports);
}
}

View File

@@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
namespace App\Framework\Vite\ValueObjects;
use App\Framework\Vite\Exceptions\ViteManifestException;
/**
* Value Object representing Vite's manifest.json
*
* Parses and provides access to Vite's build manifest,
* which maps source files to their compiled output files.
*/
final readonly class ViteManifest
{
/**
* @param array<string, ViteEntry> $entries Map of entry names to ViteEntry objects
*/
public function __construct(
public array $entries
) {
}
/**
* Create ViteManifest from manifest file path
*/
public static function fromFile(string $path): self
{
if (! file_exists($path)) {
throw ViteManifestException::fileNotFound($path);
}
$content = file_get_contents($path);
if ($content === false) {
throw ViteManifestException::cannotRead($path);
}
$data = json_decode($content, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw ViteManifestException::invalidJson($path, json_last_error_msg());
}
return self::fromArray($data);
}
/**
* Create ViteManifest from array data
*
* @param array<string, array> $data
*/
public static function fromArray(array $data): self
{
$entries = [];
foreach ($data as $name => $entry) {
if (! is_array($entry)) {
continue;
}
$entries[$name] = ViteEntry::fromManifestEntry($name, $entry);
}
return new self($entries);
}
/**
* Get entry by name
*/
public function getEntry(string $name): ?ViteEntry
{
return $this->entries[$name] ?? null;
}
/**
* Check if entry exists
*/
public function hasEntry(string $name): bool
{
return isset($this->entries[$name]);
}
/**
* Get all entry point entries (isEntry === true)
*
* @return array<string, ViteEntry>
*/
public function getEntryPoints(): array
{
return array_filter(
$this->entries,
fn (ViteEntry $entry) => $entry->isEntry
);
}
/**
* Get entry with all its dependencies resolved
*
* @return array<ViteEntry>
*/
public function getEntryWithDependencies(string $name): array
{
$entry = $this->getEntry($name);
if ($entry === null) {
return [];
}
$resolved = [$entry];
// Recursively resolve imports
foreach ($entry->imports as $importName) {
$importEntries = $this->getEntryWithDependencies($importName);
foreach ($importEntries as $importEntry) {
// Avoid duplicates
if (! in_array($importEntry, $resolved, true)) {
$resolved[] = $importEntry;
}
}
}
return $resolved;
}
/**
* Get total number of entries
*/
public function count(): int
{
return count($this->entries);
}
}