Compare commits
2 Commits
225368a952
...
chore/fix-
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f81f572cd | |||
| 28d2fbbd2f |
@@ -136,8 +136,16 @@ jobs:
|
|||||||
-H "Accept: application/json" \
|
-H "Accept: application/json" \
|
||||||
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER")"
|
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER")"
|
||||||
|
|
||||||
node --input-type=module - <<'NODE' "$ISSUE_JSON" >> /tmp/anno.env
|
# ✅ Robust: write JSON to file (avoid argv/stdi n "-" issue)
|
||||||
const issue = JSON.parse(process.argv[1] || "{}");
|
printf '%s' "$ISSUE_JSON" > /tmp/issue.json
|
||||||
|
|
||||||
|
node --input-type=module - /tmp/issue.json >> /tmp/anno.env <<'NODE'
|
||||||
|
import fs from "node:fs";
|
||||||
|
|
||||||
|
const fp = process.argv[2] || "";
|
||||||
|
const raw = fp ? fs.readFileSync(fp, "utf8") : "{}";
|
||||||
|
const issue = JSON.parse(raw || "{}");
|
||||||
|
|
||||||
const title = String(issue.title || "");
|
const title = String(issue.title || "");
|
||||||
const body = String(issue.body || "").replace(/\r\n/g, "\n");
|
const body = String(issue.body || "").replace(/\r\n/g, "\n");
|
||||||
|
|
||||||
@@ -185,12 +193,11 @@ jobs:
|
|||||||
source /tmp/anno.env || true
|
source /tmp/anno.env || true
|
||||||
|
|
||||||
[[ "${SKIP:-0}" == "1" ]] || exit 0
|
[[ "${SKIP:-0}" == "1" ]] || exit 0
|
||||||
[[ "$LABEL_NAME" == "state/approved" || "$LABEL_NAME" == "workflow_dispatch" ]] || exit 0
|
[[ "${LABEL_NAME:-}" == "state/approved" || "${LABEL_NAME:-}" == "workflow_dispatch" ]] || exit 0
|
||||||
|
test -n "${FORGE_TOKEN:-}" || { echo "ℹ️ missing FORGE_TOKEN -> skip comment"; exit 0; }
|
||||||
|
|
||||||
# message différent si Proposer
|
|
||||||
REASON="${SKIP_REASON:-}"
|
REASON="${SKIP_REASON:-}"
|
||||||
TYPE="${ISSUE_TYPE:-}"
|
TYPE="${ISSUE_TYPE:-}"
|
||||||
TITLE="${ISSUE_TITLE:-}"
|
|
||||||
|
|
||||||
if [[ "$REASON" == proposer_type:* ]]; then
|
if [[ "$REASON" == proposer_type:* ]]; then
|
||||||
MSG="ℹ️ Ticket #${ISSUE_NUMBER} détecté comme **Proposer** (${TYPE}).\n\n- Ce type est **traité manuellement par les editors** (correction/fact-check + cat/*).\n- Le bot n'applique **jamais** Proposer et n'ajoute **jamais** state/approved automatiquement.\n\n✅ Action : traitement éditorial manuel."
|
MSG="ℹ️ Ticket #${ISSUE_NUMBER} détecté comme **Proposer** (${TYPE}).\n\n- Ce type est **traité manuellement par les editors** (correction/fact-check + cat/*).\n- Le bot n'applique **jamais** Proposer et n'ajoute **jamais** state/approved automatiquement.\n\n✅ Action : traitement éditorial manuel."
|
||||||
@@ -245,7 +252,7 @@ jobs:
|
|||||||
source /tmp/anno.env
|
source /tmp/anno.env
|
||||||
[[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
[[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
||||||
|
|
||||||
npm run build
|
npm run build:clean
|
||||||
|
|
||||||
test -f dist/para-index.json || {
|
test -f dist/para-index.json || {
|
||||||
echo "❌ missing dist/para-index.json after build"
|
echo "❌ missing dist/para-index.json after build"
|
||||||
@@ -320,6 +327,8 @@ jobs:
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
test -n "${FORGE_TOKEN:-}" || { echo "ℹ️ missing FORGE_TOKEN -> skip comment"; exit 0; }
|
||||||
|
|
||||||
if [[ -f /tmp/apply.log ]]; then
|
if [[ -f /tmp/apply.log ]]; then
|
||||||
BODY="$(tail -n 160 /tmp/apply.log | sed 's/\r$//')"
|
BODY="$(tail -n 160 /tmp/apply.log | sed 's/\r$//')"
|
||||||
else
|
else
|
||||||
@@ -347,6 +356,8 @@ jobs:
|
|||||||
[[ "${APPLY_RC:-0}" == "0" ]] || exit 0
|
[[ "${APPLY_RC:-0}" == "0" ]] || exit 0
|
||||||
[[ "${NOOP:-0}" == "1" ]] || exit 0
|
[[ "${NOOP:-0}" == "1" ]] || exit 0
|
||||||
|
|
||||||
|
test -n "${FORGE_TOKEN:-}" || { echo "ℹ️ missing FORGE_TOKEN -> skip comment"; exit 0; }
|
||||||
|
|
||||||
MSG="ℹ️ Ticket #${ISSUE_NUMBER} : rien à appliquer (déjà présent / dédupliqué)."
|
MSG="ℹ️ Ticket #${ISSUE_NUMBER} : rien à appliquer (déjà présent / dédupliqué)."
|
||||||
PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1]||""}))' "$MSG")"
|
PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1]||""}))' "$MSG")"
|
||||||
|
|
||||||
@@ -368,6 +379,10 @@ jobs:
|
|||||||
[[ "${APPLY_RC:-0}" == "0" ]] || { echo "ℹ️ apply failed -> skip push"; exit 0; }
|
[[ "${APPLY_RC:-0}" == "0" ]] || { echo "ℹ️ apply failed -> skip push"; exit 0; }
|
||||||
[[ "${NOOP:-0}" == "0" ]] || { echo "ℹ️ no-op -> skip push"; exit 0; }
|
[[ "${NOOP:-0}" == "0" ]] || { echo "ℹ️ no-op -> skip push"; exit 0; }
|
||||||
|
|
||||||
|
test -d .git || { echo "ℹ️ no git repo -> skip push"; exit 0; }
|
||||||
|
test -n "${BRANCH:-}" || { echo "ℹ️ missing BRANCH -> skip push"; exit 0; }
|
||||||
|
test -n "${FORGE_TOKEN:-}" || { echo "ℹ️ missing FORGE_TOKEN -> skip push"; exit 0; }
|
||||||
|
|
||||||
AUTH_URL="$(node --input-type=module -e '
|
AUTH_URL="$(node --input-type=module -e '
|
||||||
const [clone, tok] = process.argv.slice(1);
|
const [clone, tok] = process.argv.slice(1);
|
||||||
const u = new URL(clone);
|
const u = new URL(clone);
|
||||||
@@ -391,6 +406,10 @@ jobs:
|
|||||||
[[ "${APPLY_RC:-0}" == "0" ]] || { echo "ℹ️ apply failed -> skip PR"; exit 0; }
|
[[ "${APPLY_RC:-0}" == "0" ]] || { echo "ℹ️ apply failed -> skip PR"; exit 0; }
|
||||||
[[ "${NOOP:-0}" == "0" ]] || { echo "ℹ️ no-op -> skip PR"; exit 0; }
|
[[ "${NOOP:-0}" == "0" ]] || { echo "ℹ️ no-op -> skip PR"; exit 0; }
|
||||||
|
|
||||||
|
test -n "${BRANCH:-}" || { echo "ℹ️ missing BRANCH -> skip PR"; exit 0; }
|
||||||
|
test -n "${END_SHA:-}" || { echo "ℹ️ missing END_SHA -> skip PR"; exit 0; }
|
||||||
|
test -n "${FORGE_TOKEN:-}" || { echo "ℹ️ missing FORGE_TOKEN -> skip PR"; exit 0; }
|
||||||
|
|
||||||
PR_TITLE="anno: apply ticket #${ISSUE_NUMBER}"
|
PR_TITLE="anno: apply ticket #${ISSUE_NUMBER}"
|
||||||
PR_BODY="PR auto depuis ticket #${ISSUE_NUMBER} (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."
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
name: Anno Reject
|
name: Anno Reject (close issue)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
issues:
|
issues:
|
||||||
types: [labeled]
|
types: [labeled]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
issue:
|
||||||
|
description: "Issue number to reject/close"
|
||||||
|
required: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: --dns-result-order=ipv4first
|
NODE_OPTIONS: --dns-result-order=ipv4first
|
||||||
@@ -11,6 +16,10 @@ defaults:
|
|||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: anno-reject-${{ github.event.issue.number || inputs.issue || 'manual' }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
reject:
|
reject:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -18,7 +27,15 @@ jobs:
|
|||||||
image: mcr.microsoft.com/devcontainers/javascript-node:22-bookworm
|
image: mcr.microsoft.com/devcontainers/javascript-node:22-bookworm
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Derive context
|
- name: Tools sanity
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
node --version
|
||||||
|
|
||||||
|
- name: Derive context (event.json / workflow_dispatch)
|
||||||
|
env:
|
||||||
|
INPUT_ISSUE: ${{ inputs.issue }}
|
||||||
|
FORGE_API: ${{ vars.FORGE_API || vars.FORGE_BASE }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
export EVENT_JSON="/var/run/act/workflow/event.json"
|
export EVENT_JSON="/var/run/act/workflow/event.json"
|
||||||
@@ -29,58 +46,124 @@ jobs:
|
|||||||
|
|
||||||
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 repoObj = ev?.repository || {};
|
||||||
|
|
||||||
const cloneUrl =
|
const cloneUrl =
|
||||||
repoObj?.clone_url ||
|
repoObj?.clone_url ||
|
||||||
(repoObj?.html_url ? (repoObj.html_url.replace(/\/$/,"") + ".git") : "");
|
(repoObj?.html_url ? (repoObj.html_url.replace(/\/$/,"") + ".git") : "");
|
||||||
if (!cloneUrl) throw new Error("No repository url");
|
|
||||||
|
|
||||||
let owner =
|
let owner =
|
||||||
repoObj?.owner?.login ||
|
repoObj?.owner?.login ||
|
||||||
repoObj?.owner?.username ||
|
repoObj?.owner?.username ||
|
||||||
(repoObj?.full_name ? repoObj.full_name.split("/")[0] : "");
|
(repoObj?.full_name ? repoObj.full_name.split("/")[0] : "");
|
||||||
|
|
||||||
let repo =
|
let repo =
|
||||||
repoObj?.name ||
|
repoObj?.name ||
|
||||||
(repoObj?.full_name ? repoObj.full_name.split("/")[1] : "");
|
(repoObj?.full_name ? repoObj.full_name.split("/")[1] : "");
|
||||||
|
|
||||||
if (!owner || !repo) {
|
if ((!owner || !repo) && cloneUrl) {
|
||||||
const m = cloneUrl.match(/[:/](?<o>[^/]+)\/(?<r>[^/]+?)(?:\.git)?$/);
|
const m = cloneUrl.match(/[:/](?<o>[^/]+)\/(?<r>[^/]+?)(?:\.git)?$/);
|
||||||
if (m?.groups) { owner = owner || m.groups.o; repo = repo || m.groups.r; }
|
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) throw new Error("Cannot infer owner/repo");
|
||||||
|
|
||||||
const issueNumber = ev?.issue?.number || ev?.issue?.index;
|
const issueNumber =
|
||||||
if (!issueNumber) throw new Error("No issue number");
|
ev?.issue?.number ||
|
||||||
|
ev?.issue?.index ||
|
||||||
|
(process.env.INPUT_ISSUE ? Number(process.env.INPUT_ISSUE) : 0);
|
||||||
|
|
||||||
const labelName = ev?.label?.name || ev?.label || "";
|
if (!issueNumber || !Number.isFinite(Number(issueNumber))) {
|
||||||
const u = new URL(cloneUrl);
|
throw new Error("No issue number in event.json or workflow_dispatch input");
|
||||||
|
}
|
||||||
|
|
||||||
|
const labelName =
|
||||||
|
ev?.label?.name ||
|
||||||
|
ev?.label ||
|
||||||
|
"workflow_dispatch";
|
||||||
|
|
||||||
|
let apiBase = "";
|
||||||
|
if (process.env.FORGE_API && String(process.env.FORGE_API).trim()) {
|
||||||
|
apiBase = String(process.env.FORGE_API).trim().replace(/\/+$/,"");
|
||||||
|
} else if (cloneUrl) {
|
||||||
|
apiBase = new URL(cloneUrl).origin;
|
||||||
|
} else {
|
||||||
|
apiBase = "";
|
||||||
|
}
|
||||||
|
|
||||||
function sh(s){ return JSON.stringify(String(s)); }
|
function sh(s){ return JSON.stringify(String(s)); }
|
||||||
|
|
||||||
process.stdout.write([
|
process.stdout.write([
|
||||||
`OWNER=${sh(owner)}`,
|
`OWNER=${sh(owner)}`,
|
||||||
`REPO=${sh(repo)}`,
|
`REPO=${sh(repo)}`,
|
||||||
`ISSUE_NUMBER=${sh(issueNumber)}`,
|
`ISSUE_NUMBER=${sh(issueNumber)}`,
|
||||||
`LABEL_NAME=${sh(labelName)}`,
|
`LABEL_NAME=${sh(labelName)}`,
|
||||||
`API_BASE=${sh(u.origin)}`
|
`API_BASE=${sh(apiBase)}`
|
||||||
].join("\n") + "\n");
|
].join("\n") + "\n");
|
||||||
NODE
|
NODE
|
||||||
|
|
||||||
- name: Gate on label state/rejected
|
echo "✅ context:"
|
||||||
|
sed -n '1,120p' /tmp/reject.env
|
||||||
|
|
||||||
|
- name: Gate on label state/rejected only
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source /tmp/reject.env
|
source /tmp/reject.env
|
||||||
if [[ "$LABEL_NAME" != "state/rejected" ]]; then
|
|
||||||
|
if [[ "$LABEL_NAME" != "state/rejected" && "$LABEL_NAME" != "workflow_dispatch" ]]; then
|
||||||
echo "ℹ️ label=$LABEL_NAME => skip"
|
echo "ℹ️ label=$LABEL_NAME => skip"
|
||||||
|
echo "SKIP=1" >> /tmp/reject.env
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
echo "✅ reject issue=$ISSUE_NUMBER"
|
echo "✅ proceed (issue=$ISSUE_NUMBER)"
|
||||||
|
|
||||||
- name: Comment + close issue
|
- name: Comment + close (only if not conflicting with state/approved)
|
||||||
env:
|
env:
|
||||||
FORGE_TOKEN: ${{ secrets.FORGE_TOKEN }}
|
FORGE_TOKEN: ${{ secrets.FORGE_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source /tmp/reject.env
|
source /tmp/reject.env
|
||||||
|
[[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
||||||
|
|
||||||
test -n "${FORGE_TOKEN:-}" || { echo "❌ Missing secret FORGE_TOKEN"; exit 1; }
|
test -n "${FORGE_TOKEN:-}" || { echo "❌ Missing secret FORGE_TOKEN"; exit 1; }
|
||||||
|
test -n "${API_BASE:-}" || { echo "❌ Missing API_BASE"; exit 1; }
|
||||||
|
|
||||||
|
ISSUE_JSON="$(curl -fsS \
|
||||||
|
-H "Authorization: token $FORGE_TOKEN" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER")"
|
||||||
|
|
||||||
|
# ✅ Robust: write JSON to file (avoid argv/stdi n "-" issue)
|
||||||
|
printf '%s' "$ISSUE_JSON" > /tmp/issue.json
|
||||||
|
|
||||||
|
node --input-type=module - /tmp/issue.json > /tmp/reject.flags <<'NODE'
|
||||||
|
import fs from "node:fs";
|
||||||
|
|
||||||
|
const fp = process.argv[2] || "";
|
||||||
|
const raw = fp ? fs.readFileSync(fp, "utf8") : "{}";
|
||||||
|
const issue = JSON.parse(raw || "{}");
|
||||||
|
|
||||||
|
const labels = Array.isArray(issue.labels)
|
||||||
|
? issue.labels.map(l => String(l.name || "")).filter(Boolean)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const hasApproved = labels.includes("state/approved");
|
||||||
|
const hasRejected = labels.includes("state/rejected");
|
||||||
|
|
||||||
|
process.stdout.write(`HAS_APPROVED=${hasApproved ? "1":"0"}\nHAS_REJECTED=${hasRejected ? "1":"0"}\n`);
|
||||||
|
NODE
|
||||||
|
|
||||||
|
source /tmp/reject.flags
|
||||||
|
|
||||||
|
if [[ "${HAS_APPROVED:-0}" == "1" && "${HAS_REJECTED:-0}" == "1" ]]; then
|
||||||
|
MSG="⚠️ Conflit d'état sur le ticket #${ISSUE_NUMBER} : labels **state/approved** et **state/rejected** présents.\n\n➡️ Action manuelle requise : retirer l'un des deux labels avant relance."
|
||||||
|
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 "$PAYLOAD"
|
||||||
|
echo "ℹ️ conflict => stop"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
MSG="❌ Ticket #${ISSUE_NUMBER} refusé (label state/rejected)."
|
MSG="❌ Ticket #${ISSUE_NUMBER} refusé (label state/rejected)."
|
||||||
PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1]||""}))' "$MSG")"
|
PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1]||""}))' "$MSG")"
|
||||||
@@ -96,3 +179,5 @@ jobs:
|
|||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER" \
|
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER" \
|
||||||
--data-binary '{"state":"closed"}'
|
--data-binary '{"state":"closed"}'
|
||||||
|
|
||||||
|
echo "✅ rejected+closed"
|
||||||
Reference in New Issue
Block a user