From 687f5b2be3ad85d76bebaa50e603b23943141c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zieli=C5=84ski?= Date: Thu, 18 Sep 2025 08:58:49 +0200 Subject: [PATCH] wip release --- .gitea/scripts/getLatestRunWithArtifacts.js | 171 ++++++++------------ .gitea/workflows/morskaRelease.yml | 14 +- 2 files changed, 78 insertions(+), 107 deletions(-) diff --git a/.gitea/scripts/getLatestRunWithArtifacts.js b/.gitea/scripts/getLatestRunWithArtifacts.js index b79ff87..9dc448d 100644 --- a/.gitea/scripts/getLatestRunWithArtifacts.js +++ b/.gitea/scripts/getLatestRunWithArtifacts.js @@ -1,125 +1,96 @@ // .gitea/scripts/getLatestRunWithArtifacts.js -// Purpose: Find latest successful run that exposes all REQUIRED_ARTIFACTS via GUI URLs. -// Outputs: sets `run_id` to GITHUB_OUTPUT and writes .gitea/.cache/run_id file. +// Robust finder: uses only Gitea API (no GUI URLs). const fs = require("fs"); const path = require("path"); +const BASE = process.env.GITEA_BASE_URL; // e.g. https://code.bim-it.pl +const OWNER = process.env.OWNER; // e.g. mz +const REPO = process.env.REPO; // e.g. DiunaBI +const TOKEN = process.env.GITEA_PAT; +const SCAN_LIMIT = Number(process.env.SCAN_LIMIT || "100"); +const REQUIRED_ARTIFACTS = (process.env.REQUIRED_ARTIFACTS || "frontend,webapi") + .split(",").map(s => s.trim()).filter(Boolean); + +if (!BASE || !OWNER || !REPO) { + console.error("Missing one of: GITEA_BASE_URL, OWNER, REPO"); process.exit(1); +} +if (!TOKEN) { + console.error("Missing GITEA_PAT"); process.exit(1); +} + +const cacheDir = path.join(".gitea", ".cache"); +fs.mkdirSync(cacheDir, { recursive: true }); + +async function apiJSON(url) { + const res = await fetch(url, { headers: { Authorization: `token ${TOKEN}` } }); + if (!res.ok) { + const t = await res.text().catch(()=>""); + throw new Error(`API ${res.status} for ${url}\n${t}`); + } + return res.json(); +} + (async () => { - // --- Config from environment --- - const BASE = process.env.GITEA_BASE_URL; // e.g. https://code.bim-it.pl - const OWNER = process.env.OWNER; // e.g. mz - const REPO = process.env.REPO; // e.g. DiunaBI - const TOKEN = process.env.GITEA_PAT; // PAT - const SCAN_LIMIT = Number(process.env.SCAN_LIMIT || "100"); - const REQUIRED_ARTIFACTS = (process.env.REQUIRED_ARTIFACTS || "frontend,webapi") - .split(",") - .map(s => s.trim()) - .filter(Boolean); - - if (!BASE || !OWNER || !REPO) { - console.error("Missing one of: GITEA_BASE_URL, OWNER, REPO"); - process.exit(1); - } - if (!TOKEN) { - console.error("Missing GITEA_PAT"); - process.exit(1); - } - - // Ensure cache dir exists - const cacheDir = path.join(".gitea", ".cache"); - fs.mkdirSync(cacheDir, { recursive: true }); - - // Helpers - const api = async (url) => { - const res = await fetch(url, { - headers: { Authorization: `token ${TOKEN}` } - }); - if (!res.ok) { - const text = await res.text().catch(() => ""); - throw new Error(`API ${res.status} ${res.statusText} for ${url}\n${text}`); - } - return res.json(); - }; - - const headOk = async (url) => { - // Try HEAD first; some instances may require GET for redirects - let res = await fetch(url, { - method: "HEAD", - redirect: "follow", - headers: { Authorization: `token ${TOKEN}` } - }); - if (res.ok) return true; - - // Fallback to GET (no download) just to test availability - res = await fetch(url, { - method: "GET", - redirect: "manual", - headers: { Authorization: `token ${TOKEN}` } - }); - // Accept 200 OK, or 3xx redirect to a signed download URL - return res.status >= 200 && res.status < 400; - }; - - // 1) Get recent workflow runs (a.k.a. tasks) via REST + // 1) Pobierz listę tasków (runs) const listUrl = `${BASE}/api/v1/repos/${OWNER}/${REPO}/actions/tasks?limit=${SCAN_LIMIT}`; - const resp = await api(listUrl); + const resp = await apiJSON(listUrl); - // 2) Build candidate list: only status == "success", newest first by id - const runs = Array.isArray(resp.workflow_runs) ? resp.workflow_runs : []; - const candidates = runs - .filter(r => r && r.status === "success") - .sort((a, b) => (b.id ?? 0) - (a.id ?? 0)); + // Gitea 1.24 może zwracać różne kształty – normalizujemy: + const raw = Array.isArray(resp) + ? resp + : (resp.workflow_runs || resp.tasks || resp.data || []); + if (!Array.isArray(raw) || raw.length === 0) { + console.error("No runs returned by API."); process.exit(1); + } - if (!candidates.length) { - console.error("No successful runs found."); - process.exit(1); + // 2) Filtrujemy „successful” + const isSuccess = (r) => { + const s = (r.status || "").toLowerCase(); // "success" / "completed" + const c = (r.conclusion || "").toLowerCase(); // "success" (czasem brak) + return s === "success" || (s === "completed" && c === "success"); + }; + + const candidates = raw + .filter(r => r && (r.id != null)) + .sort((a, b) => (b.id ?? 0) - (a.id ?? 0)) + .filter(isSuccess); + + if (candidates.length === 0) { + console.error("No successful runs found."); process.exit(1); } console.log(`Scanning ${candidates.length} successful runs for artifacts: ${REQUIRED_ARTIFACTS.join(", ")}`); - // 3) Find the first run that exposes all required artifacts via GUI URLs - let picked = null; + // 3) Sprawdź artefakty przez API + let pickedId = null; for (const r of candidates) { const runId = r.id; - const urls = REQUIRED_ARTIFACTS.map(name => - `${BASE}/${OWNER}/${REPO}/actions/runs/${runId}/artifacts/${encodeURIComponent(name)}` - ); - - let allPresent = true; - for (const u of urls) { - const ok = await headOk(u).catch(() => false); - if (!ok) { - allPresent = false; - console.log(`Run ${runId}: artifact not accessible -> ${u}`); - break; - } - } - if (allPresent) { - picked = { id: runId }; - console.log(`Picked run_id=${runId}`); - break; + const artsUrl = `${BASE}/api/v1/repos/${OWNER}/${REPO}/actions/runs/${runId}/artifacts`; + try { + const arts = await apiJSON(artsUrl); + const names = (Array.isArray(arts?.artifacts) ? arts.artifacts : arts || []) + .map(a => a?.name) + .filter(Boolean); + const ok = REQUIRED_ARTIFACTS.every(req => names.includes(req)); + if (ok) { pickedId = runId; break; } + console.log(`Run ${runId}: lacks required artifacts (has: ${names.join(", ") || "none"})`); + } catch (e) { + console.log(`Run ${runId}: cannot read artifacts via API -> ${e.message.split("\n")[0]}`); } } - if (!picked) { - console.error("No run exposes all required artifacts. Consider increasing SCAN_LIMIT or verify artifact names."); + if (!pickedId) { + console.error("No run exposes all required artifacts via API."); process.exit(1); } - // 4) Write outputs - const runIdStr = String(picked.id); - // Write to cache (handy for debugging) - fs.writeFileSync(path.join(cacheDir, "run_id"), runIdStr, "utf8"); - - // Export as GitHub-style output (supported by Gitea runners) - const outFile = process.env.GITHUB_OUTPUT; - if (outFile) { - fs.appendFileSync(outFile, `run_id=${runIdStr}\n`); - } else { - // Fallback: also print for visibility - console.log(`::set-output name=run_id::${runIdStr}`); + // 4) Zapisz outputy + fs.writeFileSync(path.join(cacheDir, "run_id"), String(pickedId), "utf8"); + if (process.env.GITHUB_OUTPUT) { + fs.appendFileSync(process.env.GITHUB_OUTPUT, `run_id=${pickedId}\n`); } + console.log(`Picked run_id=${pickedId}`); })().catch(err => { console.error(err.stack || err.message || String(err)); process.exit(1); diff --git a/.gitea/workflows/morskaRelease.yml b/.gitea/workflows/morskaRelease.yml index d5fc3d0..102d92a 100644 --- a/.gitea/workflows/morskaRelease.yml +++ b/.gitea/workflows/morskaRelease.yml @@ -28,13 +28,13 @@ jobs: sudo apt-get install -y unzip - name: Resolve latest run that exposes required artifacts - id: resolve - env: - GITEA_PAT: ${{ secrets.GITEATOKEN }} - run: | - node .gitea/scripts/getLatestRunWithArtifacts.js - echo "Resolved run_id: $(cat .gitea/.cache/run_id)" - echo "run_id=$(cat .gitea/.cache/run_id)" >> "$GITHUB_OUTPUT" + id: resolve + env: + GITEA_PAT: ${{ secrets.GITEATOKEN }} + run: | + node .gitea/scripts/getLatestRunWithArtifacts.js + echo "Resolved run_id: $(cat .gitea/.cache/run_id)" + echo "run_id=$(cat .gitea/.cache/run_id)" >> "$GITHUB_OUTPUT" - name: Download frontend artifact env: