From 6ef538a0c4bdb80e63511b966e21f32c3b242938 Mon Sep 17 00:00:00 2001 From: Archicratia Date: Wed, 25 Feb 2026 18:26:03 +0100 Subject: [PATCH] ci: fix anno apply/reject workflows (yaml valid) --- .gitea/workflows/anno-apply-pr.yml | 198 ++++++++++++----------------- .gitea/workflows/anno-reject.yml | 72 +++++------ 2 files changed, 112 insertions(+), 158 deletions(-) diff --git a/.gitea/workflows/anno-apply-pr.yml b/.gitea/workflows/anno-apply-pr.yml index 5d492eb..c160606 100644 --- a/.gitea/workflows/anno-apply-pr.yml +++ b/.gitea/workflows/anno-apply-pr.yml @@ -34,106 +34,106 @@ jobs: - name: Derive context (event.json / workflow_dispatch) env: INPUT_ISSUE: ${{ inputs.issue }} + FORGE_API: ${{ vars.FORGE_API || vars.FORGE_BASE }} run: | set -euo pipefail export EVENT_JSON="/var/run/act/workflow/event.json" test -f "$EVENT_JSON" || { echo "❌ Missing $EVENT_JSON"; exit 1; } node --input-type=module - <<'NODE' > /tmp/anno.env -import fs from "node:fs"; + import fs from "node:fs"; -const ev = JSON.parse(fs.readFileSync(process.env.EVENT_JSON, "utf8")); + const ev = JSON.parse(fs.readFileSync(process.env.EVENT_JSON, "utf8")); -const repoObj = ev?.repository || {}; -const cloneUrl = - repoObj?.clone_url || - (repoObj?.html_url ? (repoObj.html_url.replace(/\/$/,"") + ".git") : ""); + const repoObj = ev?.repository || {}; + const cloneUrl = + repoObj?.clone_url || + (repoObj?.html_url ? (repoObj.html_url.replace(/\/$/,"") + ".git") : ""); -if (!cloneUrl) throw new Error("No repository clone_url/html_url in event.json"); + if (!cloneUrl) throw new Error("No repository clone_url/html_url in event.json"); -let owner = - repoObj?.owner?.login || - repoObj?.owner?.username || - (repoObj?.full_name ? repoObj.full_name.split("/")[0] : ""); + let owner = + repoObj?.owner?.login || + repoObj?.owner?.username || + (repoObj?.full_name ? repoObj.full_name.split("/")[0] : ""); -let repo = - repoObj?.name || - (repoObj?.full_name ? repoObj.full_name.split("/")[1] : ""); + let repo = + repoObj?.name || + (repoObj?.full_name ? repoObj.full_name.split("/")[1] : ""); -if (!owner || !repo) { - // fallback parse from clone url - const m = cloneUrl.match(/[:/](?[^/]+)\/(?[^/]+?)(?:\.git)?$/); - if (m?.groups) { owner = owner || m.groups.o; repo = repo || m.groups.r; } -} -if (!owner || !repo) throw new Error("Cannot infer owner/repo"); + if (!owner || !repo) { + const m = cloneUrl.match(/[:/](?[^/]+)\/(?[^/]+?)(?:\.git)?$/); + if (m?.groups) { owner = owner || m.groups.o; repo = repo || m.groups.r; } + } + if (!owner || !repo) throw new Error("Cannot infer owner/repo"); -const defaultBranch = repoObj?.default_branch || "master"; + const defaultBranch = repoObj?.default_branch || "main"; -const issueNumber = - ev?.issue?.number || - ev?.issue?.index || - (process.env.INPUT_ISSUE ? Number(process.env.INPUT_ISSUE) : 0); + const issueNumber = + ev?.issue?.number || + ev?.issue?.index || + (process.env.INPUT_ISSUE ? Number(process.env.INPUT_ISSUE) : 0); -if (!issueNumber || !Number.isFinite(Number(issueNumber))) { - throw new Error("No issue number in event.json or workflow_dispatch input"); -} + if (!issueNumber || !Number.isFinite(Number(issueNumber))) { + throw new Error("No issue number in event.json or workflow_dispatch input"); + } -const labelName = - ev?.label?.name || - ev?.label || - "workflow_dispatch"; + const labelName = + ev?.label?.name || + ev?.label || + "workflow_dispatch"; -const u = new URL(cloneUrl); -const origin = u.origin; // https://gitea... -const apiBase = (process.env.FORGE_API && process.env.FORGE_API.trim()) - ? process.env.FORGE_API.trim().replace(/\/+$/,"") - : origin; + const u = new URL(cloneUrl); + const origin = u.origin; -function sh(s){ return JSON.stringify(String(s)); } + const apiBase = (process.env.FORGE_API && String(process.env.FORGE_API).trim()) + ? String(process.env.FORGE_API).trim().replace(/\/+$/,"") + : origin; -process.stdout.write([ - `CLONE_URL=${sh(cloneUrl)}`, - `OWNER=${sh(owner)}`, - `REPO=${sh(repo)}`, - `DEFAULT_BRANCH=${sh(defaultBranch)}`, - `ISSUE_NUMBER=${sh(issueNumber)}`, - `LABEL_NAME=${sh(labelName)}`, - `API_BASE=${sh(apiBase)}`, -].join("\n") + "\n"); -NODE + function sh(s){ return JSON.stringify(String(s)); } + + process.stdout.write([ + `CLONE_URL=${sh(cloneUrl)}`, + `OWNER=${sh(owner)}`, + `REPO=${sh(repo)}`, + `DEFAULT_BRANCH=${sh(defaultBranch)}`, + `ISSUE_NUMBER=${sh(issueNumber)}`, + `LABEL_NAME=${sh(labelName)}`, + `API_BASE=${sh(apiBase)}` + ].join("\n") + "\n"); + NODE echo "✅ context:" - sed -n '1,80p' /tmp/anno.env + sed -n '1,120p' /tmp/anno.env - name: Gate on label state/approved run: | set -euo pipefail source /tmp/anno.env if [[ "$LABEL_NAME" != "state/approved" && "$LABEL_NAME" != "workflow_dispatch" ]]; then - echo "ℹ️ label=$LABEL_NAME => skip (only state/approved triggers apply)" + echo "ℹ️ label=$LABEL_NAME => skip" + echo 'SKIP=1' >> /tmp/anno.env exit 0 fi - echo "✅ proceed (label=$LABEL_NAME issue=$ISSUE_NUMBER)" + echo "✅ proceed (issue=$ISSUE_NUMBER)" - - name: Checkout default branch (from event.json, no external actions) + - name: Checkout default branch run: | set -euo pipefail source /tmp/anno.env + [[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; } rm -rf .git git init -q git remote add origin "$CLONE_URL" - - echo "Repo URL: $CLONE_URL" - echo "Base: $DEFAULT_BRANCH" - git fetch --depth 1 origin "$DEFAULT_BRANCH" git -c advice.detachedHead=false checkout -q FETCH_HEAD - git log -1 --oneline - name: Install deps run: | set -euo pipefail + source /tmp/anno.env + [[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; } npm ci - name: Apply ticket on bot branch (strict+verify, commit) @@ -144,21 +144,18 @@ NODE run: | set -euo pipefail source /tmp/anno.env - + [[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; } test -n "${FORGE_TOKEN:-}" || { echo "❌ Missing secret FORGE_TOKEN"; exit 1; } - # git identity (required for commits) git config user.name "${BOT_GIT_NAME:-archicratie-bot}" git config user.email "${BOT_GIT_EMAIL:-bot@archicratie.local}" START_SHA="$(git rev-parse HEAD)" - TS="$(date -u +%Y%m%d-%H%M%S)" BR="bot/anno-${ISSUE_NUMBER}-${TS}" echo "BRANCH=$BR" >> /tmp/anno.env git checkout -b "$BR" - # env for script export FORGE_API="$API_BASE" export GITEA_OWNER="$OWNER" export GITEA_REPO="$REPO" @@ -169,11 +166,9 @@ NODE RC=$? set -e - echo "== apply log (tail) ==" - tail -n 120 "$LOG" || true + tail -n 160 "$LOG" || true END_SHA="$(git rev-parse HEAD)" - if [[ "$RC" -ne 0 ]]; then echo "APPLY_RC=$RC" >> /tmp/anno.env exit "$RC" @@ -186,46 +181,17 @@ NODE echo "END_SHA=$END_SHA" >> /tmp/anno.env fi - - name: Comment issue on failure (strict/verify/etc) - if: ${{ always() }} - env: - FORGE_TOKEN: ${{ secrets.FORGE_TOKEN }} - run: | - set -euo pipefail - source /tmp/anno.env - # si apply a échoué, la step précédente s'arrête => ce step tourne quand même (always) - if [[ -z "${APPLY_RC:-}" ]]; then - echo "ℹ️ no failure detected" - exit 0 - fi - - BODY="$(tail -n 120 /tmp/apply.log | sed 's/\r$//' )" - MSG="❌ apply-annotation-ticket a échoué (rc=${APPLY_RC}).\n\n\`\`\`\n${BODY}\n\`\`\`\n" - - PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.env.MSG}))' \ - MSG="$MSG")" - - curl -fsS -X POST \ - -H "Authorization: token $FORGE_TOKEN" \ - -H "Content-Type: application/json" \ - "$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/comments" \ - --data-binary "$PAYLOAD" - - exit "${APPLY_RC}" - - name: Comment issue if no-op (already applied) env: FORGE_TOKEN: ${{ secrets.FORGE_TOKEN }} run: | set -euo pipefail source /tmp/anno.env - if [[ "${NOOP:-0}" != "1" ]]; then - echo "ℹ️ changes exist -> will create PR" - exit 0 - fi + [[ "${SKIP:-0}" != "1" ]] || exit 0 + [[ "${NOOP:-0}" == "1" ]] || exit 0 MSG="ℹ️ Ticket #${ISSUE_NUMBER} : rien à appliquer (déjà présent / dédupliqué)." - PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.env.MSG}))' MSG="$MSG")" + PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1]||""}))' "$MSG")" curl -fsS -X POST \ -H "Authorization: token $FORGE_TOKEN" \ @@ -233,24 +199,22 @@ NODE "$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/comments" \ --data-binary "$PAYLOAD" - echo "✅ no-op handled" - exit 0 - - name: Push bot branch env: FORGE_TOKEN: ${{ secrets.FORGE_TOKEN }} run: | set -euo pipefail source /tmp/anno.env - test "${NOOP:-0}" = "0" || { echo "ℹ️ no-op -> skip push"; exit 0; } + [[ "${SKIP:-0}" != "1" ]] || exit 0 + [[ "${NOOP:-0}" == "0" ]] || { echo "ℹ️ no-op -> skip push"; exit 0; } - # auth remote (Gitea supports oauth2:) AUTH_URL="$(node --input-type=module -e ' - const u = new URL(process.env.CLONE_URL); + const [clone, tok] = process.argv.slice(1); + const u = new URL(clone); u.username = "oauth2"; - u.password = process.env.FORGE_TOKEN; + u.password = tok; console.log(u.toString()); - ' CLONE_URL="$CLONE_URL" FORGE_TOKEN="$FORGE_TOKEN")" + ' "$CLONE_URL" "$FORGE_TOKEN")" git remote set-url origin "$AUTH_URL" git push -u origin "$BRANCH" @@ -261,20 +225,16 @@ NODE run: | set -euo pipefail source /tmp/anno.env - test "${NOOP:-0}" = "0" || { echo "ℹ️ no-op -> skip PR"; exit 0; } + [[ "${SKIP:-0}" != "1" ]] || exit 0 + [[ "${NOOP:-0}" == "0" ]] || { echo "ℹ️ no-op -> skip PR"; exit 0; } PR_TITLE="anno: apply ticket #${ISSUE_NUMBER}" - PR_BODY="PR générée automatiquement à partir du ticket #${ISSUE_NUMBER} (label state/approved).\n\n- Branche: ${BRANCH}\n- Commit: ${END_SHA}\n\nMerge si CI OK." + PR_BODY="PR auto depuis ticket #${ISSUE_NUMBER} (state/approved).\n\n- Branche: ${BRANCH}\n- Commit: ${END_SHA}\n\nMerge si CI OK." PR_PAYLOAD="$(node --input-type=module -e ' - console.log(JSON.stringify({ - title: process.env.PR_TITLE, - body: process.env.PR_BODY, - base: process.env.DEFAULT_BRANCH, - head: `${process.env.OWNER}:${process.env.BRANCH}`, - allow_maintainer_edit: true - })); - ' PR_TITLE="$PR_TITLE" PR_BODY="$PR_BODY" OWNER="$OWNER" BRANCH="$BRANCH" DEFAULT_BRANCH="$DEFAULT_BRANCH")" + const [title, body, base, head] = process.argv.slice(1); + console.log(JSON.stringify({ title, body, base, head, allow_maintainer_edit: true })); + ' "$PR_TITLE" "$PR_BODY" "$DEFAULT_BRANCH" "${OWNER}:${BRANCH}")" PR_JSON="$(curl -fsS -X POST \ -H "Authorization: token $FORGE_TOKEN" \ @@ -283,19 +243,17 @@ NODE --data-binary "$PR_PAYLOAD")" PR_URL="$(node --input-type=module -e ' - const pr = JSON.parse(process.env.PR_JSON); + const pr = JSON.parse(process.argv[1] || "{}"); console.log(pr.html_url || pr.url || ""); - ' PR_JSON="$PR_JSON")" + ' "$PR_JSON")" test -n "$PR_URL" || { echo "❌ PR URL missing. Raw: $PR_JSON"; exit 1; } MSG="✅ PR créée pour ticket #${ISSUE_NUMBER} : ${PR_URL}" - C_PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.env.MSG}))' MSG="$MSG")" + C_PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1]||""}))' "$MSG")" curl -fsS -X POST \ -H "Authorization: token $FORGE_TOKEN" \ -H "Content-Type: application/json" \ "$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/comments" \ - --data-binary "$C_PAYLOAD" - - echo "✅ PR: $PR_URL" \ No newline at end of file + --data-binary "$C_PAYLOAD" \ No newline at end of file diff --git a/.gitea/workflows/anno-reject.yml b/.gitea/workflows/anno-reject.yml index 33f4d91..2017283 100644 --- a/.gitea/workflows/anno-reject.yml +++ b/.gitea/workflows/anno-reject.yml @@ -25,46 +25,44 @@ jobs: test -f "$EVENT_JSON" || { echo "❌ Missing $EVENT_JSON"; exit 1; } node --input-type=module - <<'NODE' > /tmp/reject.env -import fs from "node:fs"; + import fs from "node:fs"; -const ev = JSON.parse(fs.readFileSync(process.env.EVENT_JSON, "utf8")); -const repoObj = ev?.repository || {}; -const cloneUrl = - repoObj?.clone_url || - (repoObj?.html_url ? (repoObj.html_url.replace(/\/$/,"") + ".git") : ""); -if (!cloneUrl) throw new Error("No repository url"); + const ev = JSON.parse(fs.readFileSync(process.env.EVENT_JSON, "utf8")); + const repoObj = ev?.repository || {}; + const cloneUrl = + repoObj?.clone_url || + (repoObj?.html_url ? (repoObj.html_url.replace(/\/$/,"") + ".git") : ""); + if (!cloneUrl) throw new Error("No repository url"); -let owner = - repoObj?.owner?.login || - repoObj?.owner?.username || - (repoObj?.full_name ? repoObj.full_name.split("/")[0] : ""); -let repo = - repoObj?.name || - (repoObj?.full_name ? repoObj.full_name.split("/")[1] : ""); + let owner = + repoObj?.owner?.login || + repoObj?.owner?.username || + (repoObj?.full_name ? repoObj.full_name.split("/")[0] : ""); + let repo = + repoObj?.name || + (repoObj?.full_name ? repoObj.full_name.split("/")[1] : ""); -if (!owner || !repo) { - const m = cloneUrl.match(/[:/](?[^/]+)\/(?[^/]+?)(?:\.git)?$/); - if (m?.groups) { owner = owner || m.groups.o; repo = repo || m.groups.r; } -} -if (!owner || !repo) throw new Error("Cannot infer owner/repo"); + if (!owner || !repo) { + const m = cloneUrl.match(/[:/](?[^/]+)\/(?[^/]+?)(?:\.git)?$/); + if (m?.groups) { owner = owner || m.groups.o; repo = repo || m.groups.r; } + } + if (!owner || !repo) throw new Error("Cannot infer owner/repo"); -const issueNumber = ev?.issue?.number || ev?.issue?.index; -if (!issueNumber) throw new Error("No issue number"); + const issueNumber = ev?.issue?.number || ev?.issue?.index; + if (!issueNumber) throw new Error("No issue number"); -const labelName = ev?.label?.name || ev?.label || ""; + const labelName = ev?.label?.name || ev?.label || ""; + const u = new URL(cloneUrl); -const u = new URL(cloneUrl); -const apiBase = u.origin; - -function sh(s){ return JSON.stringify(String(s)); } -process.stdout.write([ - `OWNER=${sh(owner)}`, - `REPO=${sh(repo)}`, - `ISSUE_NUMBER=${sh(issueNumber)}`, - `LABEL_NAME=${sh(labelName)}`, - `API_BASE=${sh(apiBase)}` -].join("\n") + "\n"); -NODE + function sh(s){ return JSON.stringify(String(s)); } + process.stdout.write([ + `OWNER=${sh(owner)}`, + `REPO=${sh(repo)}`, + `ISSUE_NUMBER=${sh(issueNumber)}`, + `LABEL_NAME=${sh(labelName)}`, + `API_BASE=${sh(u.origin)}` + ].join("\n") + "\n"); + NODE - name: Gate on label state/rejected run: | @@ -85,7 +83,7 @@ NODE test -n "${FORGE_TOKEN:-}" || { echo "❌ Missing secret FORGE_TOKEN"; exit 1; } MSG="❌ Ticket #${ISSUE_NUMBER} refusé (label state/rejected)." - PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.env.MSG}))' MSG="$MSG")" + PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1]||""}))' "$MSG")" curl -fsS -X POST \ -H "Authorization: token $FORGE_TOKEN" \ @@ -97,6 +95,4 @@ NODE -H "Authorization: token $FORGE_TOKEN" \ -H "Content-Type: application/json" \ "$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER" \ - --data-binary '{"state":"closed"}' - - echo "✅ closed #$ISSUE_NUMBER" \ No newline at end of file + --data-binary '{"state":"closed"}' \ No newline at end of file -- 2.49.1