relase
This commit is contained in:
@@ -1,96 +1,59 @@
|
|||||||
name: ReleaseApp (auto from latest successful BuildApp)
|
name: ReleaseApp (JS finder + download)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch: {} # Manual trigger with ZERO inputs
|
workflow_dispatch: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
# --- Adjust to your instance/repo if needed ---
|
|
||||||
GITEA_BASE_URL: https://code.bim-it.pl
|
GITEA_BASE_URL: https://code.bim-it.pl
|
||||||
OWNER: mz
|
OWNER: mz
|
||||||
REPO: DiunaBI
|
REPO: DiunaBI
|
||||||
WORKFLOW_NAME: BuildApp
|
# Comma-separated artifact names that must exist
|
||||||
|
REQUIRED_ARTIFACTS: frontend,webapi
|
||||||
|
# How many recent successful runs to scan
|
||||||
|
SCAN_LIMIT: "100"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout (for tag context only)
|
- name: Checkout
|
||||||
uses: https://github.com/actions/checkout@v4
|
uses: https://github.com/actions/checkout@v4
|
||||||
|
|
||||||
- name: Install tools
|
- name: Use Node.js 20
|
||||||
|
uses: https://github.com/actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
|
||||||
|
- name: Install unzip (for extraction)
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y jq zip
|
sudo apt-get install -y unzip
|
||||||
|
|
||||||
# 1) Find latest successful run of "BuildApp" via REST API (/api/v1 ... /actions/tasks)
|
- name: Resolve latest run that exposes required artifacts
|
||||||
- name: Find latest successful BuildApp run
|
id: resolve
|
||||||
id: find_run
|
env:
|
||||||
shell: bash
|
GITEA_PAT: ${{ secrets.GITEA_PAT }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
node .gitea/scripts/getLatestRunWithArtifacts.js
|
||||||
|
echo "Resolved run_id: $(cat .gitea/.cache/run_id)"
|
||||||
|
|
||||||
# NOTE: /api/v1 is the correct REST base for Gitea.
|
- name: Download frontend artifact
|
||||||
RESP="$(curl -fsSL \
|
env:
|
||||||
-H "Authorization: token ${{ secrets.GITEA_PAT }}" \
|
GITEA_PAT: ${{ secrets.GITEA_PAT }}
|
||||||
"$GITEA_BASE_URL/api/v1/repos/$OWNER/$REPO/actions/tasks?limit=100")"
|
ARTIFACT_NAME: frontend
|
||||||
|
RUN_ID: ${{ steps.resolve.outputs.run_id }}
|
||||||
# Try to match by various fields (workflow.name in newer versions, fallbacks otherwise)
|
OUTPUT_DIR: artifacts/frontend
|
||||||
RUN_JSON="$(jq -r --arg W "$WORKFLOW_NAME" '
|
|
||||||
.workflow_runs
|
|
||||||
| map(
|
|
||||||
select(
|
|
||||||
(.status=="completed") and
|
|
||||||
(.conclusion=="success") and
|
|
||||||
(
|
|
||||||
(.workflow.name? // .workflow_name? // .display_title? // "") == $W
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
| sort_by(.run_number // .id)
|
|
||||||
| reverse
|
|
||||||
| .[0]
|
|
||||||
' <<< "$RESP")"
|
|
||||||
|
|
||||||
if [[ -z "$RUN_JSON" || "$RUN_JSON" == "null" ]]; then
|
|
||||||
echo "No successful run found for workflow name: $WORKFLOW_NAME"
|
|
||||||
echo "Available runs (debug):"
|
|
||||||
echo "$RESP" | jq -r '.workflow_runs[] | {id, status, conclusion, display_title, workflow_name: .workflow_name, wname: .workflow.name}'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
RUN_ID="$(jq -r '.id // .run_id' <<< "$RUN_JSON")"
|
|
||||||
HEAD_SHA="$(jq -r '.head_sha // .head_commit?.id // empty' <<< "$RUN_JSON")"
|
|
||||||
|
|
||||||
echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "head_sha=$HEAD_SHA" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Latest successful $WORKFLOW_NAME run: $RUN_ID ($HEAD_SHA)"
|
|
||||||
|
|
||||||
# 2) Download artifacts using GUI-style URLs
|
|
||||||
# These URLs serve ZIPs directly. Authorization via token MAY or MAY NOT work depending on instance config.
|
|
||||||
# If it fails with 302/403, consider using a community download action as a fallback.
|
|
||||||
- name: Download 'frontend' artifact (GUI URL)
|
|
||||||
shell: bash
|
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
node .gitea/scripts/downloadArtifactByName.js
|
||||||
mkdir -p artifacts/frontend
|
|
||||||
# -L to follow redirects; --fail-with-body to fail on non-2xx
|
|
||||||
curl -LfS --fail-with-body \
|
|
||||||
-H "Authorization: token ${{ secrets.GITEA_PAT }}" \
|
|
||||||
"$GITEA_BASE_URL/$OWNER/$REPO/actions/runs/${{ steps.find_run.outputs.run_id }}/artifacts/frontend" \
|
|
||||||
-o frontend.zip
|
|
||||||
unzip -q frontend.zip -d artifacts/frontend
|
|
||||||
rm -f frontend.zip
|
|
||||||
|
|
||||||
- name: Download 'webapi' artifact (GUI URL)
|
- name: Download webapi artifact
|
||||||
shell: bash
|
env:
|
||||||
|
GITEA_PAT: ${{ secrets.GITEA_PAT }}
|
||||||
|
ARTIFACT_NAME: webapi
|
||||||
|
RUN_ID: ${{ steps.resolve.outputs.run_id }}
|
||||||
|
OUTPUT_DIR: artifacts/webapi
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
node .gitea/scripts/downloadArtifactByName.js
|
||||||
mkdir -p artifacts/webapi
|
|
||||||
curl -LfS --fail-with-body \
|
|
||||||
-H "Authorization: token ${{ secrets.GITEA_PAT }}" \
|
|
||||||
"$GITEA_BASE_URL/$OWNER/$REPO/actions/runs/${{ steps.find_run.outputs.run_id }}/artifacts/webapi" \
|
|
||||||
-o webapi.zip
|
|
||||||
unzip -q webapi.zip -d artifacts/webapi
|
|
||||||
rm -f webapi.zip
|
|
||||||
|
|
||||||
- name: Show artifact structure
|
- name: Show artifact structure
|
||||||
run: |
|
run: |
|
||||||
@@ -99,32 +62,4 @@ jobs:
|
|||||||
echo "::endgroup::"
|
echo "::endgroup::"
|
||||||
echo "::group::webapi"
|
echo "::group::webapi"
|
||||||
ls -laR artifacts/webapi || true
|
ls -laR artifacts/webapi || true
|
||||||
echo "::endgroup::"
|
echo "::endgroup::"
|
||||||
|
|
||||||
# 3) Package artifacts as ZIPs for the release assets
|
|
||||||
- name: Package artifacts as ZIPs
|
|
||||||
run: |
|
|
||||||
mkdir -p build
|
|
||||||
(cd artifacts/frontend && zip -rq ../../build/frontend.zip .)
|
|
||||||
(cd artifacts/webapi && zip -rq ../../build/webapi.zip .)
|
|
||||||
ls -la build
|
|
||||||
|
|
||||||
# 4) Auto-generate tag/title and create the release on Gitea
|
|
||||||
- name: Generate tag and title
|
|
||||||
id: meta
|
|
||||||
run: |
|
|
||||||
TAG="v$(date -u +%Y%m%d-%H%M)-run${{ steps.find_run.outputs.run_id }}"
|
|
||||||
TITLE="Release ${TAG}"
|
|
||||||
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "title=$TITLE" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Create Gitea release with assets
|
|
||||||
uses: https://gitea.com/actions/release-action@main
|
|
||||||
with:
|
|
||||||
api_key: ${{ secrets.GITEA_PAT }}
|
|
||||||
tag_name: ${{ steps.meta.outputs.tag }}
|
|
||||||
name: ${{ steps.meta.outputs.title }}
|
|
||||||
body: "Automated release from latest successful **${{ env.WORKFLOW_NAME }}** run (`run_id=${{ steps.find_run.outputs.run_id }}`, `commit=${{ steps.find_run.outputs.head_sha }}`)."
|
|
||||||
files: |
|
|
||||||
build/frontend.zip
|
|
||||||
build/webapi.zip
|
|
||||||
67
.gitea/workflows/scripts/downloadArtifactByName.js
Normal file
67
.gitea/workflows/scripts/downloadArtifactByName.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// .gitea/scripts/downloadArtifactByName.js
|
||||||
|
// Purpose: Download and extract a single artifact by name from a given run.
|
||||||
|
// Env inputs:
|
||||||
|
// GITEA_BASE_URL, OWNER, REPO, GITEA_PAT
|
||||||
|
// RUN_ID -> numeric/string
|
||||||
|
// ARTIFACT_NAME -> e.g. "frontend" or "webapi"
|
||||||
|
// OUTPUT_DIR -> e.g. "artifacts/frontend"
|
||||||
|
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const { execSync } = require("child_process");
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const BASE = process.env.GITEA_BASE_URL;
|
||||||
|
const OWNER = process.env.OWNER;
|
||||||
|
const REPO = process.env.REPO;
|
||||||
|
const TOKEN = process.env.GITEA_PAT;
|
||||||
|
const RUN_ID = process.env.RUN_ID;
|
||||||
|
const NAME = process.env.ARTIFACT_NAME;
|
||||||
|
const OUT_DIR = process.env.OUTPUT_DIR || path.join("artifacts", NAME || "");
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
if (!RUN_ID || !NAME) {
|
||||||
|
console.error("Missing RUN_ID or ARTIFACT_NAME");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${BASE}/${OWNER}/${REPO}/actions/runs/${RUN_ID}/artifacts/${encodeURIComponent(NAME)}`;
|
||||||
|
const zipPath = path.join(process.cwd(), `${NAME}.zip`);
|
||||||
|
fs.mkdirSync(OUT_DIR, { recursive: true });
|
||||||
|
|
||||||
|
console.log(`Downloading artifact "${NAME}" from run ${RUN_ID}`);
|
||||||
|
console.log(`GET ${url}`);
|
||||||
|
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
redirect: "follow",
|
||||||
|
headers: { Authorization: `token ${TOKEN}` }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const text = await res.text().catch(() => "");
|
||||||
|
console.error(`Download failed: ${res.status} ${res.statusText}\n${text}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const buf = Buffer.from(await res.arrayBuffer());
|
||||||
|
fs.writeFileSync(zipPath, buf);
|
||||||
|
|
||||||
|
console.log(`Saved ZIP -> ${zipPath}`);
|
||||||
|
console.log(`Extracting to -> ${OUT_DIR}`);
|
||||||
|
|
||||||
|
execSync(`unzip -o "${zipPath}" -d "${OUT_DIR}"`, { stdio: "inherit" });
|
||||||
|
fs.unlinkSync(zipPath);
|
||||||
|
|
||||||
|
console.log("Done.");
|
||||||
|
})().catch(err => {
|
||||||
|
console.error(err.stack || err.message || String(err));
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
126
.gitea/workflows/scripts/getLatestRunWithArtifacts.js
Normal file
126
.gitea/workflows/scripts/getLatestRunWithArtifacts.js
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
// .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.
|
||||||
|
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
(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
|
||||||
|
const listUrl = `${BASE}/api/v1/repos/${OWNER}/${REPO}/actions/tasks?limit=${SCAN_LIMIT}`;
|
||||||
|
const resp = await api(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));
|
||||||
|
|
||||||
|
if (!candidates.length) {
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!picked) {
|
||||||
|
console.error("No run exposes all required artifacts. Consider increasing SCAN_LIMIT or verify artifact names.");
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
})().catch(err => {
|
||||||
|
console.error(err.stack || err.message || String(err));
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user