Compare commits
6 Commits
chore/pin-
...
chore/fix-
| Author | SHA1 | Date | |
|---|---|---|---|
| 8132e315f4 | |||
| 497bddd05d | |||
| 7c8e49c1a9 | |||
| 901d28b89b | |||
| 73fb38c4d1 | |||
| 9801ea3cea |
@@ -22,6 +22,8 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
apply-approved:
|
apply-approved:
|
||||||
|
# ✅ Job ne démarre QUE si state/approved (ou workflow_dispatch)
|
||||||
|
if: ${{ github.event_name == 'workflow_dispatch' || github.event.label.name == 'state/approved' }}
|
||||||
runs-on: mac-ci
|
runs-on: mac-ci
|
||||||
container:
|
container:
|
||||||
image: mcr.microsoft.com/devcontainers/javascript-node:22-bookworm
|
image: mcr.microsoft.com/devcontainers/javascript-node:22-bookworm
|
||||||
@@ -33,6 +35,7 @@ jobs:
|
|||||||
git --version
|
git --version
|
||||||
node --version
|
node --version
|
||||||
npm --version
|
npm --version
|
||||||
|
curl --version | head -n 1
|
||||||
|
|
||||||
- name: Derive context (event.json / workflow_dispatch)
|
- name: Derive context (event.json / workflow_dispatch)
|
||||||
env:
|
env:
|
||||||
@@ -95,6 +98,7 @@ jobs:
|
|||||||
|
|
||||||
function sh(s){ return JSON.stringify(String(s)); }
|
function sh(s){ return JSON.stringify(String(s)); }
|
||||||
|
|
||||||
|
// ✅ defaults antifragiles (empêchent les steps "always" de faire n'importe quoi)
|
||||||
process.stdout.write([
|
process.stdout.write([
|
||||||
`CLONE_URL=${sh(cloneUrl)}`,
|
`CLONE_URL=${sh(cloneUrl)}`,
|
||||||
`OWNER=${sh(owner)}`,
|
`OWNER=${sh(owner)}`,
|
||||||
@@ -102,24 +106,16 @@ jobs:
|
|||||||
`DEFAULT_BRANCH=${sh(defaultBranch)}`,
|
`DEFAULT_BRANCH=${sh(defaultBranch)}`,
|
||||||
`ISSUE_NUMBER=${sh(issueNumber)}`,
|
`ISSUE_NUMBER=${sh(issueNumber)}`,
|
||||||
`LABEL_NAME=${sh(labelName)}`,
|
`LABEL_NAME=${sh(labelName)}`,
|
||||||
`API_BASE=${sh(apiBase)}`
|
`API_BASE=${sh(apiBase)}`,
|
||||||
|
`SKIP=${sh("0")}`,
|
||||||
|
`SKIP_REASON=${sh("")}`,
|
||||||
|
`APPLY_RC=${sh("999")}`,
|
||||||
|
`NOOP=${sh("1")}`
|
||||||
].join("\n") + "\n");
|
].join("\n") + "\n");
|
||||||
NODE
|
NODE
|
||||||
|
|
||||||
echo "✅ context:"
|
echo "✅ context:"
|
||||||
sed -n '1,120p' /tmp/anno.env
|
sed -n '1,160p' /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"
|
|
||||||
echo "SKIP=1" >> /tmp/anno.env
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "✅ proceed (issue=$ISSUE_NUMBER)"
|
|
||||||
|
|
||||||
- name: Fetch issue + gate on Type (skip Proposer)
|
- name: Fetch issue + gate on Type (skip Proposer)
|
||||||
env:
|
env:
|
||||||
@@ -127,17 +123,19 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source /tmp/anno.env
|
source /tmp/anno.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; }
|
||||||
|
|
||||||
ISSUE_JSON="$(curl -fsS \
|
# ✅ on écrit le JSON dans un fichier (FINI JSON.parse('-'))
|
||||||
|
curl -fsS --retry 3 --retry-delay 2 --retry-all-errors --max-time 30 \
|
||||||
-H "Authorization: token $FORGE_TOKEN" \
|
-H "Authorization: token $FORGE_TOKEN" \
|
||||||
-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" \
|
||||||
|
-o /tmp/issue.json
|
||||||
|
|
||||||
node --input-type=module - <<'NODE' "$ISSUE_JSON" >> /tmp/anno.env
|
node --input-type=module - /tmp/issue.json >> /tmp/anno.env <<'NODE'
|
||||||
const issue = JSON.parse(process.argv[1] || "{}");
|
import fs from "node:fs";
|
||||||
|
const issue = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||||
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,24 +183,25 @@ 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
|
|
||||||
|
|
||||||
# message différent si Proposer
|
test -n "${FORGE_TOKEN:-}" || exit 0
|
||||||
|
test -n "${API_BASE:-}" || exit 0
|
||||||
|
|
||||||
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.\n\n✅ Action : traitement éditorial manuel."
|
||||||
elif [[ "$REASON" == unsupported_type:* ]]; then
|
elif [[ "$REASON" == unsupported_type:* ]]; then
|
||||||
MSG="ℹ️ Ticket #${ISSUE_NUMBER} ignoré : Type non supporté par le bot (${TYPE}).\n\nTypes supportés : type/media, type/reference, type/comment.\n✅ Action : traitement manuel si nécessaire."
|
MSG="ℹ️ Ticket #${ISSUE_NUMBER} ignoré : Type non supporté par le bot (${TYPE}).\n\nTypes supportés : type/media, type/reference, type/comment.\n✅ Action : traitement manuel si nécessaire."
|
||||||
else
|
else
|
||||||
MSG="ℹ️ Ticket #${ISSUE_NUMBER} ignoré : champ 'Type:' manquant ou illisible.\n\n✅ Action : corriger le ticket (ajouter 'Type: type/media|type/reference|type/comment') ou traiter manuellement."
|
MSG="ℹ️ Ticket #${ISSUE_NUMBER} ignoré : champ 'Type:' manquant ou illisible.\n\n✅ Action : corriger le ticket (Type: type/media|type/reference|type/comment) ou traiter manuellement."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
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")"
|
||||||
|
|
||||||
curl -fsS -X POST \
|
curl -fsS --retry 3 --retry-delay 2 --retry-all-errors --max-time 30 \
|
||||||
|
-X POST \
|
||||||
-H "Authorization: token $FORGE_TOKEN" \
|
-H "Authorization: token $FORGE_TOKEN" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/comments" \
|
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/comments" \
|
||||||
@@ -246,7 +245,6 @@ jobs:
|
|||||||
[[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
[[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
||||||
|
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
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"
|
||||||
ls -la dist | sed -n '1,200p' || true
|
ls -la dist | sed -n '1,200p' || true
|
||||||
@@ -294,7 +292,7 @@ jobs:
|
|||||||
END_SHA="$(git rev-parse HEAD)"
|
END_SHA="$(git rev-parse HEAD)"
|
||||||
|
|
||||||
if [[ "$RC" -ne 0 ]]; then
|
if [[ "$RC" -ne 0 ]]; then
|
||||||
echo "NOOP=0" >> /tmp/anno.env
|
echo "NOOP=1" >> /tmp/anno.env
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -312,13 +310,13 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source /tmp/anno.env || true
|
source /tmp/anno.env || true
|
||||||
[[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
[[ "${SKIP:-0}" != "1" ]] || exit 0
|
||||||
|
|
||||||
RC="${APPLY_RC:-0}"
|
RC="${APPLY_RC:-999}"
|
||||||
if [[ "$RC" == "0" ]]; then
|
[[ "$RC" != "0" ]] || { echo "ℹ️ no failure detected"; exit 0; }
|
||||||
echo "ℹ️ no failure detected"
|
|
||||||
exit 0
|
test -n "${FORGE_TOKEN:-}" || exit 0
|
||||||
fi
|
test -n "${API_BASE:-}" || 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$//')"
|
||||||
@@ -329,7 +327,8 @@ jobs:
|
|||||||
MSG="❌ apply-annotation-ticket a échoué (rc=${RC}).\n\n\`\`\`\n${BODY}\n\`\`\`\n"
|
MSG="❌ apply-annotation-ticket a échoué (rc=${RC}).\n\n\`\`\`\n${BODY}\n\`\`\`\n"
|
||||||
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")"
|
||||||
|
|
||||||
curl -fsS -X POST \
|
curl -fsS --retry 3 --retry-delay 2 --retry-all-errors --max-time 30 \
|
||||||
|
-X POST \
|
||||||
-H "Authorization: token $FORGE_TOKEN" \
|
-H "Authorization: token $FORGE_TOKEN" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/comments" \
|
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/comments" \
|
||||||
@@ -344,13 +343,17 @@ jobs:
|
|||||||
source /tmp/anno.env || true
|
source /tmp/anno.env || true
|
||||||
[[ "${SKIP:-0}" != "1" ]] || exit 0
|
[[ "${SKIP:-0}" != "1" ]] || exit 0
|
||||||
|
|
||||||
[[ "${APPLY_RC:-0}" == "0" ]] || exit 0
|
[[ "${APPLY_RC:-999}" == "0" ]] || exit 0
|
||||||
[[ "${NOOP:-0}" == "1" ]] || exit 0
|
[[ "${NOOP:-1}" == "1" ]] || exit 0
|
||||||
|
|
||||||
|
test -n "${FORGE_TOKEN:-}" || exit 0
|
||||||
|
test -n "${API_BASE:-}" || 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")"
|
||||||
|
|
||||||
curl -fsS -X POST \
|
curl -fsS --retry 3 --retry-delay 2 --retry-all-errors --max-time 30 \
|
||||||
|
-X POST \
|
||||||
-H "Authorization: token $FORGE_TOKEN" \
|
-H "Authorization: token $FORGE_TOKEN" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/comments" \
|
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/comments" \
|
||||||
@@ -365,8 +368,9 @@ jobs:
|
|||||||
source /tmp/anno.env || true
|
source /tmp/anno.env || true
|
||||||
[[ "${SKIP:-0}" != "1" ]] || exit 0
|
[[ "${SKIP:-0}" != "1" ]] || exit 0
|
||||||
|
|
||||||
[[ "${APPLY_RC:-0}" == "0" ]] || { echo "ℹ️ apply failed -> skip push"; exit 0; }
|
[[ "${APPLY_RC:-999}" == "0" ]] || { echo "ℹ️ apply not ok -> skip push"; exit 0; }
|
||||||
[[ "${NOOP:-0}" == "0" ]] || { echo "ℹ️ no-op -> skip push"; exit 0; }
|
[[ "${NOOP:-1}" == "0" ]] || { echo "ℹ️ no-op -> skip push"; exit 0; }
|
||||||
|
test -n "${BRANCH:-}" || { echo "ℹ️ no BRANCH -> 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);
|
||||||
@@ -388,8 +392,10 @@ jobs:
|
|||||||
source /tmp/anno.env || true
|
source /tmp/anno.env || true
|
||||||
[[ "${SKIP:-0}" != "1" ]] || exit 0
|
[[ "${SKIP:-0}" != "1" ]] || exit 0
|
||||||
|
|
||||||
[[ "${APPLY_RC:-0}" == "0" ]] || { echo "ℹ️ apply failed -> skip PR"; exit 0; }
|
[[ "${APPLY_RC:-999}" == "0" ]] || { echo "ℹ️ apply not ok -> skip PR"; exit 0; }
|
||||||
[[ "${NOOP:-0}" == "0" ]] || { echo "ℹ️ no-op -> skip PR"; exit 0; }
|
[[ "${NOOP:-1}" == "0" ]] || { echo "ℹ️ no-op -> skip PR"; exit 0; }
|
||||||
|
test -n "${BRANCH:-}" || { echo "ℹ️ no BRANCH -> skip PR"; exit 0; }
|
||||||
|
test -n "${END_SHA:-}" || { echo "ℹ️ no END_SHA -> 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."
|
||||||
@@ -399,7 +405,8 @@ jobs:
|
|||||||
console.log(JSON.stringify({ title, body, base, head, allow_maintainer_edit: true }));
|
console.log(JSON.stringify({ title, body, base, head, allow_maintainer_edit: true }));
|
||||||
' "$PR_TITLE" "$PR_BODY" "$DEFAULT_BRANCH" "${OWNER}:${BRANCH}")"
|
' "$PR_TITLE" "$PR_BODY" "$DEFAULT_BRANCH" "${OWNER}:${BRANCH}")"
|
||||||
|
|
||||||
PR_JSON="$(curl -fsS -X POST \
|
PR_JSON="$(curl -fsS --retry 3 --retry-delay 2 --retry-all-errors --max-time 30 \
|
||||||
|
-X POST \
|
||||||
-H "Authorization: token $FORGE_TOKEN" \
|
-H "Authorization: token $FORGE_TOKEN" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
"$API_BASE/api/v1/repos/$OWNER/$REPO/pulls" \
|
"$API_BASE/api/v1/repos/$OWNER/$REPO/pulls" \
|
||||||
@@ -415,14 +422,13 @@ jobs:
|
|||||||
MSG="✅ PR créée pour ticket #${ISSUE_NUMBER} : ${PR_URL}"
|
MSG="✅ PR créée pour ticket #${ISSUE_NUMBER} : ${PR_URL}"
|
||||||
C_PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1]||""}))' "$MSG")"
|
C_PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1]||""}))' "$MSG")"
|
||||||
|
|
||||||
curl -fsS -X POST \
|
curl -fsS --retry 3 --retry-delay 2 --retry-all-errors --max-time 30 \
|
||||||
|
-X POST \
|
||||||
-H "Authorization: token $FORGE_TOKEN" \
|
-H "Authorization: token $FORGE_TOKEN" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/comments" \
|
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/comments" \
|
||||||
--data-binary "$C_PAYLOAD"
|
--data-binary "$C_PAYLOAD"
|
||||||
|
|
||||||
echo "✅ PR: $PR_URL"
|
|
||||||
|
|
||||||
- name: Finalize (fail job if apply failed)
|
- name: Finalize (fail job if apply failed)
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
run: |
|
run: |
|
||||||
@@ -431,7 +437,7 @@ jobs:
|
|||||||
|
|
||||||
[[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
[[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
||||||
|
|
||||||
RC="${APPLY_RC:-0}"
|
RC="${APPLY_RC:-999}"
|
||||||
if [[ "$RC" != "0" ]]; then
|
if [[ "$RC" != "0" ]]; then
|
||||||
echo "❌ apply failed (rc=$RC)"
|
echo "❌ apply failed (rc=$RC)"
|
||||||
exit "$RC"
|
exit "$RC"
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
reject:
|
reject:
|
||||||
|
# ✅ Job ne démarre QUE si state/rejected (ou workflow_dispatch)
|
||||||
|
if: ${{ github.event_name == 'workflow_dispatch' || github.event.label.name == 'state/rejected' }}
|
||||||
runs-on: mac-ci
|
runs-on: mac-ci
|
||||||
container:
|
container:
|
||||||
image: mcr.microsoft.com/devcontainers/javascript-node:22-bookworm
|
image: mcr.microsoft.com/devcontainers/javascript-node:22-bookworm
|
||||||
@@ -31,6 +33,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
node --version
|
node --version
|
||||||
|
curl --version | head -n 1
|
||||||
|
|
||||||
- name: Derive context (event.json / workflow_dispatch)
|
- name: Derive context (event.json / workflow_dispatch)
|
||||||
env:
|
env:
|
||||||
@@ -80,14 +83,9 @@ jobs:
|
|||||||
ev?.label ||
|
ev?.label ||
|
||||||
"workflow_dispatch";
|
"workflow_dispatch";
|
||||||
|
|
||||||
let apiBase = "";
|
const apiBase = (process.env.FORGE_API && String(process.env.FORGE_API).trim())
|
||||||
if (process.env.FORGE_API && String(process.env.FORGE_API).trim()) {
|
? String(process.env.FORGE_API).trim().replace(/\/+$/,"")
|
||||||
apiBase = String(process.env.FORGE_API).trim().replace(/\/+$/,"");
|
: (cloneUrl ? new URL(cloneUrl).origin : "");
|
||||||
} 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)); }
|
||||||
|
|
||||||
@@ -103,37 +101,26 @@ jobs:
|
|||||||
echo "✅ context:"
|
echo "✅ context:"
|
||||||
sed -n '1,120p' /tmp/reject.env
|
sed -n '1,120p' /tmp/reject.env
|
||||||
|
|
||||||
- name: Gate on label state/rejected only
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
source /tmp/reject.env
|
|
||||||
|
|
||||||
if [[ "$LABEL_NAME" != "state/rejected" && "$LABEL_NAME" != "workflow_dispatch" ]]; then
|
|
||||||
echo "ℹ️ label=$LABEL_NAME => skip"
|
|
||||||
echo "SKIP=1" >> /tmp/reject.env
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "✅ proceed (issue=$ISSUE_NUMBER)"
|
|
||||||
|
|
||||||
- name: Comment + close (only if not conflicting with state/approved)
|
- 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; }
|
test -n "${API_BASE:-}" || { echo "❌ Missing API_BASE"; exit 1; }
|
||||||
|
|
||||||
ISSUE_JSON="$(curl -fsS \
|
curl -fsS --retry 3 --retry-delay 2 --retry-all-errors --max-time 30 \
|
||||||
-H "Authorization: token $FORGE_TOKEN" \
|
-H "Authorization: token $FORGE_TOKEN" \
|
||||||
-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" \
|
||||||
|
-o /tmp/reject.issue.json
|
||||||
|
|
||||||
# conflict guard: approved + rejected => do nothing, comment warning
|
# conflict guard: approved + rejected => do nothing, comment warning
|
||||||
node --input-type=module - <<'NODE' "$ISSUE_JSON" > /tmp/reject.flags
|
node --input-type=module - /tmp/reject.issue.json > /tmp/reject.flags <<'NODE'
|
||||||
const issue = JSON.parse(process.argv[1] || "{}");
|
import fs from "node:fs";
|
||||||
|
const issue = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||||
const labels = Array.isArray(issue.labels) ? issue.labels.map(l => String(l.name || "")).filter(Boolean) : [];
|
const labels = Array.isArray(issue.labels) ? issue.labels.map(l => String(l.name || "")).filter(Boolean) : [];
|
||||||
const hasApproved = labels.includes("state/approved");
|
const hasApproved = labels.includes("state/approved");
|
||||||
const hasRejected = labels.includes("state/rejected");
|
const hasRejected = labels.includes("state/rejected");
|
||||||
@@ -145,7 +132,8 @@ jobs:
|
|||||||
if [[ "${HAS_APPROVED:-0}" == "1" && "${HAS_REJECTED:-0}" == "1" ]]; then
|
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."
|
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")"
|
PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1]||""}))' "$MSG")"
|
||||||
curl -fsS -X POST \
|
curl -fsS --retry 3 --retry-delay 2 --retry-all-errors --max-time 30 \
|
||||||
|
-X POST \
|
||||||
-H "Authorization: token $FORGE_TOKEN" \
|
-H "Authorization: token $FORGE_TOKEN" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/comments" \
|
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/comments" \
|
||||||
@@ -158,14 +146,16 @@ jobs:
|
|||||||
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")"
|
||||||
|
|
||||||
curl -fsS -X POST \
|
curl -fsS --retry 3 --retry-delay 2 --retry-all-errors --max-time 30 \
|
||||||
|
-X POST \
|
||||||
-H "Authorization: token $FORGE_TOKEN" \
|
-H "Authorization: token $FORGE_TOKEN" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/comments" \
|
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/comments" \
|
||||||
--data-binary "$PAYLOAD"
|
--data-binary "$PAYLOAD"
|
||||||
|
|
||||||
# close issue
|
# close issue
|
||||||
curl -fsS -X PATCH \
|
curl -fsS --retry 3 --retry-delay 2 --retry-all-errors --max-time 30 \
|
||||||
|
-X PATCH \
|
||||||
-H "Authorization: token $FORGE_TOKEN" \
|
-H "Authorization: token $FORGE_TOKEN" \
|
||||||
-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" \
|
||||||
|
|||||||
Reference in New Issue
Block a user