- 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.
363 lines
9.0 KiB
PHP
363 lines
9.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Process\Services;
|
|
|
|
use App\Framework\Core\ValueObjects\Duration;
|
|
use App\Framework\Filesystem\ValueObjects\FilePath;
|
|
use App\Framework\Process\Process;
|
|
use App\Framework\Process\ValueObjects\Command;
|
|
use App\Framework\Process\ValueObjects\Npm\NpmAuditResult;
|
|
use App\Framework\Process\ValueObjects\Npm\NpmPackage;
|
|
|
|
/**
|
|
* NPM Service.
|
|
*
|
|
* Verwaltet NPM-Operationen wie Installations, Updates, Security Audits und Package-Informationen.
|
|
*/
|
|
final readonly class NpmService
|
|
{
|
|
public function __construct(
|
|
private Process $process,
|
|
private ?FilePath $projectRoot = null
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Gibt die NPM-Version zurück.
|
|
*/
|
|
public function getVersion(): string
|
|
{
|
|
$result = $this->process->run(
|
|
Command::fromArray(['npm', '--version'])
|
|
);
|
|
|
|
if (! $result->isSuccess()) {
|
|
return 'unknown';
|
|
}
|
|
|
|
return trim($result->stdout);
|
|
}
|
|
|
|
/**
|
|
* Installiert Dependencies.
|
|
*/
|
|
public function install(bool $production = false): bool
|
|
{
|
|
$args = ['npm', 'install'];
|
|
|
|
if ($production) {
|
|
$args[] = '--production';
|
|
}
|
|
|
|
$result = $this->process->run(
|
|
command: Command::fromArray($args),
|
|
workingDirectory: $this->projectRoot,
|
|
timeout: Duration::fromMinutes(5)
|
|
);
|
|
|
|
return $result->isSuccess();
|
|
}
|
|
|
|
/**
|
|
* Aktualisiert Dependencies.
|
|
*/
|
|
public function update(bool $production = false): bool
|
|
{
|
|
$args = ['npm', 'update'];
|
|
|
|
if ($production) {
|
|
$args[] = '--production';
|
|
}
|
|
|
|
$result = $this->process->run(
|
|
command: Command::fromArray($args),
|
|
workingDirectory: $this->projectRoot,
|
|
timeout: Duration::fromMinutes(10)
|
|
);
|
|
|
|
return $result->isSuccess();
|
|
}
|
|
|
|
/**
|
|
* Führt Security Audit durch.
|
|
*/
|
|
public function audit(): NpmAuditResult
|
|
{
|
|
$result = $this->process->run(
|
|
command: Command::fromArray(['npm', 'audit', '--json']),
|
|
workingDirectory: $this->projectRoot,
|
|
timeout: Duration::fromSeconds(60)
|
|
);
|
|
|
|
if (! $result->isSuccess()) {
|
|
// Auch bei Vulnerabilities ist Exit-Code != 0
|
|
// Versuche trotzdem JSON zu parsen
|
|
}
|
|
|
|
$data = json_decode($result->stdout, true);
|
|
|
|
if (! is_array($data)) {
|
|
return NpmAuditResult::safe();
|
|
}
|
|
|
|
return NpmAuditResult::fromAuditJson($data);
|
|
}
|
|
|
|
/**
|
|
* Versucht automatisches Fixing von Vulnerabilities.
|
|
*/
|
|
public function auditFix(bool $force = false): bool
|
|
{
|
|
$args = ['npm', 'audit', 'fix'];
|
|
|
|
if ($force) {
|
|
$args[] = '--force';
|
|
}
|
|
|
|
$result = $this->process->run(
|
|
command: Command::fromArray($args),
|
|
workingDirectory: $this->projectRoot,
|
|
timeout: Duration::fromMinutes(5)
|
|
);
|
|
|
|
return $result->isSuccess();
|
|
}
|
|
|
|
/**
|
|
* Listet veraltete Packages auf.
|
|
*
|
|
* @return NpmPackage[]
|
|
*/
|
|
public function getOutdatedPackages(): array
|
|
{
|
|
$result = $this->process->run(
|
|
command: Command::fromArray(['npm', 'outdated', '--json']),
|
|
workingDirectory: $this->projectRoot,
|
|
timeout: Duration::fromSeconds(60)
|
|
);
|
|
|
|
// npm outdated gibt Exit-Code != 0 wenn es outdated packages gibt
|
|
$data = json_decode($result->stdout, true);
|
|
|
|
if (! is_array($data)) {
|
|
return [];
|
|
}
|
|
|
|
$packages = [];
|
|
|
|
foreach ($data as $name => $info) {
|
|
$packages[] = NpmPackage::fromNpmOutput(
|
|
name: $name,
|
|
currentVersion: $info['current'] ?? 'unknown',
|
|
wantedVersion: $info['wanted'] ?? null,
|
|
latestVersion: $info['latest'] ?? null,
|
|
type: $info['type'] ?? null
|
|
);
|
|
}
|
|
|
|
return $packages;
|
|
}
|
|
|
|
/**
|
|
* Listet installierte Packages auf.
|
|
*
|
|
* @return NpmPackage[]
|
|
*/
|
|
public function getInstalledPackages(): array
|
|
{
|
|
$result = $this->process->run(
|
|
command: Command::fromArray(['npm', 'list', '--json', '--depth=0']),
|
|
workingDirectory: $this->projectRoot,
|
|
timeout: Duration::fromSeconds(30)
|
|
);
|
|
|
|
if (! $result->isSuccess()) {
|
|
return [];
|
|
}
|
|
|
|
$data = json_decode($result->stdout, true);
|
|
|
|
if (! is_array($data)) {
|
|
return [];
|
|
}
|
|
|
|
$packages = [];
|
|
|
|
// Dependencies
|
|
foreach ($data['dependencies'] ?? [] as $name => $info) {
|
|
$packages[] = new NpmPackage(
|
|
name: $name,
|
|
currentVersion: $info['version'] ?? 'unknown',
|
|
type: 'dependencies'
|
|
);
|
|
}
|
|
|
|
// DevDependencies (wenn --depth=0 nicht nur dependencies zurückgibt)
|
|
foreach ($data['devDependencies'] ?? [] as $name => $info) {
|
|
$packages[] = new NpmPackage(
|
|
name: $name,
|
|
currentVersion: $info['version'] ?? 'unknown',
|
|
type: 'devDependencies'
|
|
);
|
|
}
|
|
|
|
return $packages;
|
|
}
|
|
|
|
/**
|
|
* Führt NPM Script aus.
|
|
*/
|
|
public function runScript(string $script): bool
|
|
{
|
|
$result = $this->process->run(
|
|
command: Command::fromArray(['npm', 'run', $script]),
|
|
workingDirectory: $this->projectRoot,
|
|
timeout: Duration::fromMinutes(10)
|
|
);
|
|
|
|
return $result->isSuccess();
|
|
}
|
|
|
|
/**
|
|
* Cleaned node_modules und package-lock.json.
|
|
*/
|
|
public function clean(): bool
|
|
{
|
|
if ($this->projectRoot === null) {
|
|
return false;
|
|
}
|
|
|
|
$nodeModules = $this->projectRoot->toString() . '/node_modules';
|
|
$packageLock = $this->projectRoot->toString() . '/package-lock.json';
|
|
|
|
// Remove node_modules
|
|
if (is_dir($nodeModules)) {
|
|
$result = $this->process->run(
|
|
command: Command::fromArray(['rm', '-rf', $nodeModules]),
|
|
timeout: Duration::fromMinutes(2)
|
|
);
|
|
|
|
if (! $result->isSuccess()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Remove package-lock.json
|
|
if (file_exists($packageLock)) {
|
|
$result = $this->process->run(
|
|
command: Command::fromArray(['rm', $packageLock]),
|
|
timeout: Duration::fromSeconds(5)
|
|
);
|
|
|
|
if (! $result->isSuccess()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Prüft package.json Validität.
|
|
*/
|
|
public function validate(): bool
|
|
{
|
|
if ($this->projectRoot === null) {
|
|
return false;
|
|
}
|
|
|
|
$packageJson = $this->projectRoot->toString() . '/package.json';
|
|
|
|
if (! file_exists($packageJson)) {
|
|
return false;
|
|
}
|
|
|
|
$content = file_get_contents($packageJson);
|
|
$data = json_decode($content, true);
|
|
|
|
// Grundlegende Validierung
|
|
return is_array($data)
|
|
&& isset($data['name'])
|
|
&& isset($data['version']);
|
|
}
|
|
|
|
/**
|
|
* Initialisiert neues NPM-Projekt.
|
|
*/
|
|
public function init(bool $yes = false): bool
|
|
{
|
|
$args = ['npm', 'init'];
|
|
|
|
if ($yes) {
|
|
$args[] = '--yes';
|
|
}
|
|
|
|
$result = $this->process->run(
|
|
command: Command::fromArray($args),
|
|
workingDirectory: $this->projectRoot,
|
|
timeout: Duration::fromSeconds(30)
|
|
);
|
|
|
|
return $result->isSuccess();
|
|
}
|
|
|
|
/**
|
|
* Installiert spezifisches Package.
|
|
*/
|
|
public function installPackage(string $package, bool $dev = false, bool $exact = false): bool
|
|
{
|
|
$args = ['npm', 'install', $package];
|
|
|
|
if ($dev) {
|
|
$args[] = '--save-dev';
|
|
}
|
|
|
|
if ($exact) {
|
|
$args[] = '--save-exact';
|
|
}
|
|
|
|
$result = $this->process->run(
|
|
command: Command::fromArray($args),
|
|
workingDirectory: $this->projectRoot,
|
|
timeout: Duration::fromMinutes(3)
|
|
);
|
|
|
|
return $result->isSuccess();
|
|
}
|
|
|
|
/**
|
|
* Deinstalliert spezifisches Package.
|
|
*/
|
|
public function uninstallPackage(string $package): bool
|
|
{
|
|
$result = $this->process->run(
|
|
command: Command::fromArray(['npm', 'uninstall', $package]),
|
|
workingDirectory: $this->projectRoot,
|
|
timeout: Duration::fromMinutes(2)
|
|
);
|
|
|
|
return $result->isSuccess();
|
|
}
|
|
|
|
/**
|
|
* Zeigt Package-Informationen.
|
|
*/
|
|
public function showPackageInfo(string $package): ?array
|
|
{
|
|
$result = $this->process->run(
|
|
command: Command::fromArray(['npm', 'show', $package, '--json']),
|
|
timeout: Duration::fromSeconds(30)
|
|
);
|
|
|
|
if (! $result->isSuccess()) {
|
|
return null;
|
|
}
|
|
|
|
$data = json_decode($result->stdout, true);
|
|
|
|
return is_array($data) ? $data : null;
|
|
}
|
|
}
|