lastChecked = now()->format('H:i'); $this->dispatch('notify', message: 'Entwicklungsversion — kein Release-Kanal konfiguriert.'); } /** Resolve the current commit from .git without needing the git binary. */ private function build(): array { $root = base_path('.git'); $head = @file_get_contents($root.'/HEAD'); if ($head === false) { return ['sha' => null, 'branch' => config('clusev.channel')]; } $head = trim($head); if (! str_starts_with($head, 'ref: ')) { return ['sha' => substr($head, 0, 7), 'branch' => config('clusev.channel')]; } $ref = substr($head, 5); $branch = preg_replace('#^refs/heads/#', '', $ref); $sha = @file_get_contents($root.'/'.$ref); if ($sha === false) { // packed-refs fallback foreach (preg_split('/\R/', (string) @file_get_contents($root.'/packed-refs')) as $line) { if (str_ends_with(trim($line), ' '.$ref)) { $sha = explode(' ', trim($line))[0]; break; } } } return ['sha' => $sha ? substr(trim($sha), 0, 7) : null, 'branch' => $branch]; } /** * Parse CHANGELOG.md into dated groups for the timeline. * * @return array}> */ private function changelog(): array { $path = base_path('CHANGELOG.md'); if (! is_file($path)) { return []; } $groups = []; $date = null; foreach (preg_split('/\R/', (string) file_get_contents($path)) as $line) { if (preg_match('/^###\s+(.+)$/', $line, $m)) { $date = trim($m[1]); $groups[$date] = []; continue; } if ($date !== null && preg_match('/^-\s*(\w+)\s*·\s*(.+)$/u', trim($line), $m)) { $groups[$date][] = ['type' => strtolower($m[1]), 'text' => trim($m[2])]; } } $out = []; foreach ($groups as $d => $items) { if ($items !== []) { $out[] = ['date' => $d, 'items' => $items]; } } return $out; } public function render() { return view('livewire.versions.index', [ 'version' => config('clusev.version'), 'channel' => config('clusev.channel'), 'repository' => config('clusev.repository'), 'license' => config('clusev.license'), 'build' => $this->build(), 'groups' => $this->changelog(), ]); } }