dispatch('promote-public.yml', ['tag' => $betaTag]); } public function yank(string $tag): bool { return $this->dispatch('yank.yml', ['tag' => $tag]); } /** * The public repo's tag names, newest first (anonymous read; the repo is public). [] on failure. * * @return array */ public function publicTags(int $limit = 10): array { $slug = (string) config('clusev.public_slug'); if ($slug === '') { return []; } // Cached: render() (incl. the 5s page poll) calls this every render, and the anonymous GitHub // tag API is rate-limited (60/h per IP). 120s keeps it well under the limit; a yank reflects // once the workflow has actually deleted the tag (~minutes) anyway, so staleness is harmless. return Cache::remember("clusev:public-tags:{$slug}:{$limit}", 120, function () use ($slug, $limit) { try { $res = Http::timeout(5)->acceptJson()->withHeaders(['User-Agent' => 'Clusev-Panel']) ->get("https://api.github.com/repos/{$slug}/tags", ['per_page' => $limit]); if (! $res->successful()) { return []; } return array_values(array_filter( array_map(static fn ($t): string => is_array($t) ? (string) ($t['name'] ?? '') : '', (array) $res->json()), static fn (string $n): bool => $n !== '', )); } catch (\Throwable $e) { report($e); return []; } }); } /** @param array $inputs */ private function dispatch(string $workflowFile, array $inputs): bool { $token = (string) config('clusev.github_token'); $slug = (string) config('clusev.staging_slug'); if ($token === '' || $slug === '') { return false; } try { $res = Http::timeout(5)->withToken($token)->acceptJson()->withHeaders(['User-Agent' => 'Clusev-Panel']) ->post("https://api.github.com/repos/{$slug}/actions/workflows/{$workflowFile}/dispatches", [ 'ref' => (string) config('clusev.release_branch'), 'inputs' => $inputs, ]); return $res->successful(); // 204 No Content on success } catch (\Throwable $e) { report($e); return false; } } }