Compare commits
3 Commits
hotfix/fix
...
hotfix/fix
| Author | SHA1 | Date | |
|---|---|---|---|
| b9629b43ff | |||
| f2e4ae5ac2 | |||
| 71baf0f6da |
@@ -41,7 +41,7 @@ jobs:
|
|||||||
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"
|
||||||
test -f "$EVENT_JSON" || { echo "❌ Missing $EVENT_JSON"; exit 1; }
|
test -f "$EVENT_JSON" || { echo "Missing $EVENT_JSON"; exit 1; }
|
||||||
|
|
||||||
node --input-type=module - <<'NODE' > /tmp/anno.env
|
node --input-type=module - <<'NODE' > /tmp/anno.env
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
@@ -66,7 +66,10 @@ jobs:
|
|||||||
|
|
||||||
if (!owner || !repo) {
|
if (!owner || !repo) {
|
||||||
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");
|
||||||
|
|
||||||
@@ -81,7 +84,6 @@ jobs:
|
|||||||
throw new Error("No issue number in event.json or workflow_dispatch input");
|
throw new Error("No issue number in event.json or workflow_dispatch input");
|
||||||
}
|
}
|
||||||
|
|
||||||
// label name: best-effort (non-bloquant)
|
|
||||||
let labelName = "workflow_dispatch";
|
let labelName = "workflow_dispatch";
|
||||||
const lab = ev?.label;
|
const lab = ev?.label;
|
||||||
if (typeof lab === "string") labelName = lab;
|
if (typeof lab === "string") labelName = lab;
|
||||||
@@ -95,7 +97,7 @@ jobs:
|
|||||||
? String(process.env.FORGE_API).trim().replace(/\/+$/,"")
|
? String(process.env.FORGE_API).trim().replace(/\/+$/,"")
|
||||||
: origin;
|
: origin;
|
||||||
|
|
||||||
function sh(s){ return JSON.stringify(String(s)); }
|
function sh(s) { return JSON.stringify(String(s)); }
|
||||||
|
|
||||||
process.stdout.write([
|
process.stdout.write([
|
||||||
`CLONE_URL=${sh(cloneUrl)}`,
|
`CLONE_URL=${sh(cloneUrl)}`,
|
||||||
@@ -108,7 +110,7 @@ jobs:
|
|||||||
].join("\n") + "\n");
|
].join("\n") + "\n");
|
||||||
NODE
|
NODE
|
||||||
|
|
||||||
echo "✅ context:"
|
echo "context:"
|
||||||
sed -n '1,120p' /tmp/anno.env
|
sed -n '1,120p' /tmp/anno.env
|
||||||
|
|
||||||
- name: Early gate (label event fast-skip, but tolerant)
|
- name: Early gate (label event fast-skip, but tolerant)
|
||||||
@@ -116,18 +118,16 @@ jobs:
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source /tmp/anno.env
|
source /tmp/anno.env
|
||||||
|
|
||||||
echo "ℹ️ event label = $LABEL_NAME"
|
echo "event label = $LABEL_NAME"
|
||||||
|
|
||||||
# Fast skip on obvious non-approved label events (avoid noise),
|
|
||||||
# BUT do NOT skip if label payload is weird/unknown.
|
|
||||||
if [[ "$LABEL_NAME" != "state/approved" && "$LABEL_NAME" != "workflow_dispatch" && "$LABEL_NAME" != "" && "$LABEL_NAME" != "[object Object]" ]]; then
|
if [[ "$LABEL_NAME" != "state/approved" && "$LABEL_NAME" != "workflow_dispatch" && "$LABEL_NAME" != "" && "$LABEL_NAME" != "[object Object]" ]]; then
|
||||||
echo "ℹ️ label=$LABEL_NAME => skip early"
|
echo "label=$LABEL_NAME => skip early"
|
||||||
echo "SKIP=1" >> /tmp/anno.env
|
echo "SKIP=1" >> /tmp/anno.env
|
||||||
echo "SKIP_REASON=\"label_not_approved_event\"" >> /tmp/anno.env
|
echo "SKIP_REASON=\"label_not_approved_event\"" >> /tmp/anno.env
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✅ continue to API gating (issue=$ISSUE_NUMBER)"
|
echo "continue to API gating (issue=$ISSUE_NUMBER)"
|
||||||
|
|
||||||
- name: Fetch issue + hard gate on labels + Type
|
- name: Fetch issue + hard gate on labels + Type
|
||||||
env:
|
env:
|
||||||
@@ -135,9 +135,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source /tmp/anno.env
|
source /tmp/anno.env
|
||||||
[[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
[[ "${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; }
|
||||||
|
|
||||||
curl -fsS \
|
curl -fsS \
|
||||||
-H "Authorization: token $FORGE_TOKEN" \
|
-H "Authorization: token $FORGE_TOKEN" \
|
||||||
@@ -148,11 +148,12 @@ jobs:
|
|||||||
node --input-type=module - <<'NODE' >> /tmp/anno.env
|
node --input-type=module - <<'NODE' >> /tmp/anno.env
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
|
|
||||||
const issue = JSON.parse(fs.readFileSync("/tmp/issue.json","utf8"));
|
const issue = JSON.parse(fs.readFileSync("/tmp/issue.json", "utf8"));
|
||||||
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");
|
||||||
|
|
||||||
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");
|
||||||
|
|
||||||
function pickLine(key) {
|
function pickLine(key) {
|
||||||
@@ -164,14 +165,12 @@ jobs:
|
|||||||
const typeRaw = pickLine("Type");
|
const typeRaw = pickLine("Type");
|
||||||
const type = String(typeRaw || "").trim().toLowerCase();
|
const type = String(typeRaw || "").trim().toLowerCase();
|
||||||
|
|
||||||
const allowed = new Set(["type/media","type/reference","type/comment"]);
|
const allowedAnno = new Set(["type/media", "type/reference", "type/comment"]);
|
||||||
const proposer = new Set(["type/correction","type/fact-check"]);
|
const proposerTypes = new Set(["type/correction", "type/fact-check"]);
|
||||||
|
|
||||||
const out = [];
|
const out = [];
|
||||||
out.push(`ISSUE_TITLE=${JSON.stringify(title)}`);
|
|
||||||
out.push(`ISSUE_TYPE=${JSON.stringify(type)}`);
|
out.push(`ISSUE_TYPE=${JSON.stringify(type)}`);
|
||||||
|
|
||||||
// HARD gate: must currently have state/approved (avoids depending on event payload)
|
|
||||||
if (!hasApproved) {
|
if (!hasApproved) {
|
||||||
out.push(`SKIP=1`);
|
out.push(`SKIP=1`);
|
||||||
out.push(`SKIP_REASON=${JSON.stringify("not_approved_label_present")}`);
|
out.push(`SKIP_REASON=${JSON.stringify("not_approved_label_present")}`);
|
||||||
@@ -182,23 +181,23 @@ jobs:
|
|||||||
if (!type) {
|
if (!type) {
|
||||||
out.push(`SKIP=1`);
|
out.push(`SKIP=1`);
|
||||||
out.push(`SKIP_REASON=${JSON.stringify("missing_type")}`);
|
out.push(`SKIP_REASON=${JSON.stringify("missing_type")}`);
|
||||||
} else if (allowed.has(type)) {
|
} else if (allowedAnno.has(type)) {
|
||||||
// proceed
|
// proceed
|
||||||
} else if (proposer.has(type)) {
|
} else if (proposerTypes.has(type)) {
|
||||||
out.push(`SKIP=1`);
|
out.push(`SKIP=1`);
|
||||||
out.push(`SKIP_REASON=${JSON.stringify("proposer_type:"+type)}`);
|
out.push(`SKIP_REASON=${JSON.stringify("proposer_type:" + type)}`);
|
||||||
} else {
|
} else {
|
||||||
out.push(`SKIP=1`);
|
out.push(`SKIP=1`);
|
||||||
out.push(`SKIP_REASON=${JSON.stringify("unsupported_type:"+type)}`);
|
out.push(`SKIP_REASON=${JSON.stringify("unsupported_type:" + type)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
process.stdout.write(out.join("\n") + "\n");
|
process.stdout.write(out.join("\n") + "\n");
|
||||||
NODE
|
NODE
|
||||||
|
|
||||||
echo "✅ gating result:"
|
echo "gating result:"
|
||||||
grep -E '^(ISSUE_TYPE|SKIP|SKIP_REASON)=' /tmp/anno.env || true
|
grep -E '^(ISSUE_TYPE|SKIP|SKIP_REASON)=' /tmp/anno.env || true
|
||||||
|
|
||||||
- name: Comment issue if skipped (Proposer / unsupported / missing Type)
|
- name: Comment issue if skipped (unsupported / missing Type only)
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
env:
|
env:
|
||||||
FORGE_TOKEN: ${{ secrets.FORGE_TOKEN }}
|
FORGE_TOKEN: ${{ secrets.FORGE_TOKEN }}
|
||||||
@@ -208,9 +207,13 @@ jobs:
|
|||||||
|
|
||||||
[[ "${SKIP:-0}" == "1" ]] || exit 0
|
[[ "${SKIP:-0}" == "1" ]] || exit 0
|
||||||
|
|
||||||
# IMPORTANT: do NOT comment for "not_approved_label_present" (avoid spam on other label events)
|
|
||||||
if [[ "${SKIP_REASON:-}" == "not_approved_label_present" || "${SKIP_REASON:-}" == "label_not_approved_event" ]]; then
|
if [[ "${SKIP_REASON:-}" == "not_approved_label_present" || "${SKIP_REASON:-}" == "label_not_approved_event" ]]; then
|
||||||
echo "ℹ️ skip reason=${SKIP_REASON} -> no comment"
|
echo "skip reason=${SKIP_REASON} -> no comment"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${SKIP_REASON:-}" == proposer_type:* ]]; then
|
||||||
|
echo "proposer ticket detected -> anno stays silent"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -219,15 +222,13 @@ jobs:
|
|||||||
REASON="${SKIP_REASON:-}"
|
REASON="${SKIP_REASON:-}"
|
||||||
TYPE="${ISSUE_TYPE:-}"
|
TYPE="${ISSUE_TYPE:-}"
|
||||||
|
|
||||||
if [[ "$REASON" == proposer_type:* ]]; then
|
if [[ "$REASON" == unsupported_type:* ]]; then
|
||||||
MSG="ℹ️ Ticket #${ISSUE_NUMBER} détecté comme **Proposer** (${TYPE}).\n\n- Ce type est **traité manuellement par les editors**.\n✅ Aucun traitement automatique."
|
MSG="Ticket #${ISSUE_NUMBER} ignored: unsupported Type (${TYPE}). Supported types: type/media, type/reference, type/comment."
|
||||||
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."
|
|
||||||
else
|
else
|
||||||
MSG="ℹ️ Ticket #${ISSUE_NUMBER} ignoré : champ 'Type:' manquant ou illisible.\n\nAjoute : Type: type/media|type/reference|type/comment"
|
MSG="Ticket #${ISSUE_NUMBER} ignored: missing or unreadable 'Type:'. Expected: type/media|type/reference|type/comment"
|
||||||
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 -X POST \
|
||||||
-H "Authorization: token $FORGE_TOKEN" \
|
-H "Authorization: token $FORGE_TOKEN" \
|
||||||
@@ -239,7 +240,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source /tmp/anno.env
|
source /tmp/anno.env
|
||||||
[[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
[[ "${SKIP:-0}" != "1" ]] || { echo "skipped"; exit 0; }
|
||||||
|
|
||||||
rm -rf .git
|
rm -rf .git
|
||||||
git init -q
|
git init -q
|
||||||
@@ -252,16 +253,16 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source /tmp/anno.env
|
source /tmp/anno.env
|
||||||
[[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
[[ "${SKIP:-0}" != "1" ]] || { echo "skipped"; exit 0; }
|
||||||
npm ci --no-audit --no-fund
|
npm ci --no-audit --no-fund
|
||||||
|
|
||||||
- name: Check apply script exists
|
- name: Check apply script exists
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source /tmp/anno.env
|
source /tmp/anno.env
|
||||||
[[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
[[ "${SKIP:-0}" != "1" ]] || { echo "skipped"; exit 0; }
|
||||||
test -f scripts/apply-annotation-ticket.mjs || {
|
test -f scripts/apply-annotation-ticket.mjs || {
|
||||||
echo "❌ missing scripts/apply-annotation-ticket.mjs on $DEFAULT_BRANCH"
|
echo "missing scripts/apply-annotation-ticket.mjs on $DEFAULT_BRANCH"
|
||||||
ls -la scripts | sed -n '1,200p' || true
|
ls -la scripts | sed -n '1,200p' || true
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
@@ -270,16 +271,16 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
echo "✅ dist/para-index.json present"
|
echo "dist/para-index.json present"
|
||||||
|
|
||||||
- name: Apply ticket on bot branch (strict+verify, commit)
|
- name: Apply ticket on bot branch (strict+verify, commit)
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
@@ -290,10 +291,10 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source /tmp/anno.env
|
source /tmp/anno.env
|
||||||
[[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
[[ "${SKIP:-0}" != "1" ]] || { echo "skipped"; exit 0; }
|
||||||
test -d .git || { echo "❌ not a git repo (checkout failed)"; echo "APPLY_RC=90" >> /tmp/anno.env; exit 0; }
|
test -d .git || { echo "not a git repo (checkout failed)"; echo "APPLY_RC=90" >> /tmp/anno.env; exit 0; }
|
||||||
|
|
||||||
test -n "${FORGE_TOKEN:-}" || { echo "❌ Missing secret FORGE_TOKEN"; exit 1; }
|
test -n "${FORGE_TOKEN:-}" || { echo "Missing secret FORGE_TOKEN"; exit 1; }
|
||||||
|
|
||||||
git config user.name "${BOT_GIT_NAME:-archicratie-bot}"
|
git config user.name "${BOT_GIT_NAME:-archicratie-bot}"
|
||||||
git config user.email "${BOT_GIT_EMAIL:-bot@archicratie.local}"
|
git config user.email "${BOT_GIT_EMAIL:-bot@archicratie.local}"
|
||||||
@@ -340,11 +341,11 @@ 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" ]] || { echo "skipped"; exit 0; }
|
||||||
|
|
||||||
RC="${APPLY_RC:-0}"
|
RC="${APPLY_RC:-0}"
|
||||||
if [[ "$RC" == "0" ]]; then
|
if [[ "$RC" == "0" ]]; then
|
||||||
echo "ℹ️ no failure detected"
|
echo "no failure detected"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -356,8 +357,8 @@ jobs:
|
|||||||
BODY="(no apply log found)"
|
BODY="(no apply log found)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
MSG="❌ apply-annotation-ticket a échoué (rc=${RC}).\n\n\`\`\`\n${BODY}\n\`\`\`\n"
|
MSG="apply-annotation-ticket failed (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 -X POST \
|
||||||
-H "Authorization: token $FORGE_TOKEN" \
|
-H "Authorization: token $FORGE_TOKEN" \
|
||||||
@@ -374,9 +375,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:-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 -d .git || { echo "no git repo -> 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);
|
||||||
@@ -398,8 +399,8 @@ 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:-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; }
|
||||||
|
|
||||||
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."
|
||||||
@@ -420,10 +421,10 @@ jobs:
|
|||||||
console.log(pr.html_url || pr.url || "");
|
console.log(pr.html_url || pr.url || "");
|
||||||
' "$PR_JSON")"
|
' "$PR_JSON")"
|
||||||
|
|
||||||
test -n "$PR_URL" || { echo "❌ PR URL missing. Raw: $PR_JSON"; exit 1; }
|
test -n "$PR_URL" || { echo "PR URL missing. Raw: $PR_JSON"; exit 1; }
|
||||||
|
|
||||||
MSG="✅ PR créée pour ticket #${ISSUE_NUMBER} : ${PR_URL}"
|
MSG="PR created for 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 -X POST \
|
||||||
-H "Authorization: token $FORGE_TOKEN" \
|
-H "Authorization: token $FORGE_TOKEN" \
|
||||||
@@ -431,7 +432,7 @@ jobs:
|
|||||||
"$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"
|
echo "PR: $PR_URL"
|
||||||
|
|
||||||
- name: Finalize (fail job if apply failed)
|
- name: Finalize (fail job if apply failed)
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
@@ -439,11 +440,11 @@ jobs:
|
|||||||
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" ]] || { echo "skipped"; exit 0; }
|
||||||
|
|
||||||
RC="${APPLY_RC:-0}"
|
RC="${APPLY_RC:-0}"
|
||||||
if [[ "$RC" != "0" ]]; then
|
if [[ "$RC" != "0" ]]; then
|
||||||
echo "❌ apply failed (rc=$RC)"
|
echo "apply failed (rc=$RC)"
|
||||||
exit "$RC"
|
exit "$RC"
|
||||||
fi
|
fi
|
||||||
echo "✅ apply ok"
|
echo "apply ok"
|
||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
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"
|
||||||
test -f "$EVENT_JSON" || { echo "❌ Missing $EVENT_JSON"; exit 1; }
|
test -f "$EVENT_JSON" || { echo "Missing $EVENT_JSON"; exit 1; }
|
||||||
|
|
||||||
node --input-type=module - <<'NODE' > /tmp/proposer.env
|
node --input-type=module - <<'NODE' > /tmp/proposer.env
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
|
|
||||||
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 clone_url/html_url in event.json");
|
if (!cloneUrl) throw new Error("No repository clone_url/html_url in event.json");
|
||||||
|
|
||||||
@@ -70,8 +70,12 @@ jobs:
|
|||||||
|
|
||||||
if (!owner || !repo) {
|
if (!owner || !repo) {
|
||||||
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 defaultBranch = repoObj?.default_branch || "main";
|
const defaultBranch = repoObj?.default_branch || "main";
|
||||||
@@ -94,11 +98,15 @@ jobs:
|
|||||||
const u = new URL(cloneUrl);
|
const u = new URL(cloneUrl);
|
||||||
const origin = u.origin;
|
const origin = u.origin;
|
||||||
|
|
||||||
const apiBase = (process.env.FORGE_API && String(process.env.FORGE_API).trim())
|
const apiBase =
|
||||||
? String(process.env.FORGE_API).trim().replace(/\/+$/,"")
|
(process.env.FORGE_API && String(process.env.FORGE_API).trim())
|
||||||
: origin;
|
? String(process.env.FORGE_API).trim().replace(/\/+$/, "")
|
||||||
|
: origin;
|
||||||
|
|
||||||
|
function sh(s) {
|
||||||
|
return JSON.stringify(String(s));
|
||||||
|
}
|
||||||
|
|
||||||
function sh(s){ return JSON.stringify(String(s)); }
|
|
||||||
process.stdout.write([
|
process.stdout.write([
|
||||||
`CLONE_URL=${sh(cloneUrl)}`,
|
`CLONE_URL=${sh(cloneUrl)}`,
|
||||||
`OWNER=${sh(owner)}`,
|
`OWNER=${sh(owner)}`,
|
||||||
@@ -111,7 +119,7 @@ jobs:
|
|||||||
].join("\n") + "\n");
|
].join("\n") + "\n");
|
||||||
NODE
|
NODE
|
||||||
|
|
||||||
echo "✅ context:"
|
echo "Context:"
|
||||||
sed -n '1,200p' /tmp/proposer.env
|
sed -n '1,200p' /tmp/proposer.env
|
||||||
|
|
||||||
- name: Early gate
|
- name: Early gate
|
||||||
@@ -121,14 +129,51 @@ jobs:
|
|||||||
|
|
||||||
if [[ "$EVENT_NAME" == "issues" ]]; then
|
if [[ "$EVENT_NAME" == "issues" ]]; then
|
||||||
if [[ "$LABEL_NAME" != "state/approved" ]]; then
|
if [[ "$LABEL_NAME" != "state/approved" ]]; then
|
||||||
echo "ℹ️ issues/labeled but label=$LABEL_NAME -> skip"
|
echo "issues/labeled but label=$LABEL_NAME -> skip"
|
||||||
echo 'SKIP=1' >> /tmp/proposer.env
|
echo 'SKIP=1' >> /tmp/proposer.env
|
||||||
echo 'SKIP_REASON="label_not_state_approved"' >> /tmp/proposer.env
|
echo 'SKIP_REASON="label_not_state_approved"' >> /tmp/proposer.env
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✅ proceed"
|
echo "Proceed"
|
||||||
|
|
||||||
|
- name: Checkout default branch
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
source /tmp/proposer.env
|
||||||
|
[[ "${SKIP:-0}" != "1" ]] || { echo "Skipped"; exit 0; }
|
||||||
|
|
||||||
|
rm -rf .git
|
||||||
|
git init -q
|
||||||
|
git remote add origin "$CLONE_URL"
|
||||||
|
git fetch --depth 1 origin "$DEFAULT_BRANCH"
|
||||||
|
git -c advice.detachedHead=false checkout -q FETCH_HEAD
|
||||||
|
git log -1 --oneline
|
||||||
|
|
||||||
|
- name: Detect app dir (repo-root vs ./site)
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
source /tmp/proposer.env
|
||||||
|
[[ "${SKIP:-0}" != "1" ]] || { echo "Skipped"; exit 0; }
|
||||||
|
|
||||||
|
APP_DIR="."
|
||||||
|
if [[ -d "site" && -f "site/package.json" ]]; then
|
||||||
|
APP_DIR="site"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "APP_DIR=$APP_DIR" >> /tmp/proposer.env
|
||||||
|
echo "APP_DIR=$APP_DIR"
|
||||||
|
|
||||||
|
test -f "$APP_DIR/package.json" || {
|
||||||
|
echo "package.json missing in APP_DIR=$APP_DIR"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
test -d "$APP_DIR/scripts" || {
|
||||||
|
echo "scripts/ missing in APP_DIR=$APP_DIR"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
- name: Select next proposer batch (by path)
|
- name: Select next proposer batch (by path)
|
||||||
env:
|
env:
|
||||||
@@ -136,14 +181,25 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source /tmp/proposer.env
|
source /tmp/proposer.env
|
||||||
[[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
[[ "${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
|
||||||
|
}
|
||||||
|
|
||||||
export GITEA_OWNER="$OWNER"
|
export GITEA_OWNER="$OWNER"
|
||||||
export GITEA_REPO="$REPO"
|
export GITEA_REPO="$REPO"
|
||||||
export FORGE_API="$API_BASE"
|
export FORGE_API="$API_BASE"
|
||||||
|
|
||||||
|
cd "$APP_DIR"
|
||||||
|
|
||||||
|
test -f scripts/pick-proposer-issue.mjs || {
|
||||||
|
echo "missing scripts/pick-proposer-issue.mjs in APP_DIR=$APP_DIR"
|
||||||
|
ls -la scripts | sed -n '1,200p' || true
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
node scripts/pick-proposer-issue.mjs "${ISSUE_NUMBER:-0}" > /tmp/proposer.pick.env
|
node scripts/pick-proposer-issue.mjs "${ISSUE_NUMBER:-0}" > /tmp/proposer.pick.env
|
||||||
cat /tmp/proposer.pick.env >> /tmp/proposer.env
|
cat /tmp/proposer.pick.env >> /tmp/proposer.env
|
||||||
source /tmp/proposer.pick.env
|
source /tmp/proposer.pick.env
|
||||||
@@ -151,11 +207,11 @@ jobs:
|
|||||||
if [[ "${TARGET_FOUND:-0}" != "1" ]]; then
|
if [[ "${TARGET_FOUND:-0}" != "1" ]]; then
|
||||||
echo 'SKIP=1' >> /tmp/proposer.env
|
echo 'SKIP=1' >> /tmp/proposer.env
|
||||||
echo "SKIP_REASON=${TARGET_REASON:-no_target}" >> /tmp/proposer.env
|
echo "SKIP_REASON=${TARGET_REASON:-no_target}" >> /tmp/proposer.env
|
||||||
echo "ℹ️ no target batch"
|
echo "No target batch"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✅ target batch:"
|
echo "Target batch:"
|
||||||
grep -E '^(TARGET_PRIMARY_ISSUE|TARGET_ISSUES|TARGET_COUNT|TARGET_CHEMIN)=' /tmp/proposer.env
|
grep -E '^(TARGET_PRIMARY_ISSUE|TARGET_ISSUES|TARGET_COUNT|TARGET_CHEMIN)=' /tmp/proposer.env
|
||||||
|
|
||||||
- name: Inspect open proposer PRs
|
- name: Inspect open proposer PRs
|
||||||
@@ -164,7 +220,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source /tmp/proposer.env
|
source /tmp/proposer.env
|
||||||
[[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
[[ "${SKIP:-0}" != "1" ]] || { echo "Skipped"; exit 0; }
|
||||||
|
|
||||||
curl -fsS \
|
curl -fsS \
|
||||||
-H "Authorization: token $FORGE_TOKEN" \
|
-H "Authorization: token $FORGE_TOKEN" \
|
||||||
@@ -177,21 +233,22 @@ jobs:
|
|||||||
node --input-type=module - <<'NODE' >> /tmp/proposer.env
|
node --input-type=module - <<'NODE' >> /tmp/proposer.env
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
|
|
||||||
const pulls = JSON.parse(fs.readFileSync("/tmp/open_pulls.json","utf8"));
|
const pulls = JSON.parse(fs.readFileSync("/tmp/open_pulls.json", "utf8"));
|
||||||
const issues = String(process.env.TARGET_ISSUES || "")
|
const issues = String(process.env.TARGET_ISSUES || "")
|
||||||
.trim()
|
.trim()
|
||||||
.split(/\s+/)
|
.split(/\s+/)
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
const proposerOpen = Array.isArray(pulls)
|
const proposerOpen = Array.isArray(pulls)
|
||||||
? pulls.filter(pr => String(pr?.head?.ref || "").startsWith("bot/proposer-"))
|
? pulls.filter((pr) => String(pr?.head?.ref || "").startsWith("bot/proposer-"))
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const current = proposerOpen.find((pr) => {
|
const current = proposerOpen.find((pr) => {
|
||||||
const ref = String(pr?.head?.ref || "");
|
const ref = String(pr?.head?.ref || "");
|
||||||
const title = String(pr?.title || "");
|
const title = String(pr?.title || "");
|
||||||
const body = String(pr?.body || "");
|
const body = String(pr?.body || "");
|
||||||
return issues.some(n =>
|
|
||||||
|
return issues.some((n) =>
|
||||||
ref.startsWith(`bot/proposer-${n}-`) ||
|
ref.startsWith(`bot/proposer-${n}-`) ||
|
||||||
title.includes(`#${n}`) ||
|
title.includes(`#${n}`) ||
|
||||||
body.includes(`#${n}`) ||
|
body.includes(`#${n}`) ||
|
||||||
@@ -200,13 +257,14 @@ jobs:
|
|||||||
});
|
});
|
||||||
|
|
||||||
const out = [];
|
const out = [];
|
||||||
|
|
||||||
if (current) {
|
if (current) {
|
||||||
out.push(`SKIP=1`);
|
out.push("SKIP=1");
|
||||||
out.push(`SKIP_REASON=${JSON.stringify("issue_already_has_open_pr")}`);
|
out.push(`SKIP_REASON=${JSON.stringify("issue_already_has_open_pr")}`);
|
||||||
out.push(`OPEN_PR_URL=${JSON.stringify(String(current.html_url || current.url || ""))}`);
|
out.push(`OPEN_PR_URL=${JSON.stringify(String(current.html_url || current.url || ""))}`);
|
||||||
} else if (proposerOpen.length > 0) {
|
} else if (proposerOpen.length > 0) {
|
||||||
const first = proposerOpen[0];
|
const first = proposerOpen[0];
|
||||||
out.push(`SKIP=1`);
|
out.push("SKIP=1");
|
||||||
out.push(`SKIP_REASON=${JSON.stringify("queue_busy_open_proposer_pr")}`);
|
out.push(`SKIP_REASON=${JSON.stringify("queue_busy_open_proposer_pr")}`);
|
||||||
out.push(`OPEN_PR_URL=${JSON.stringify(String(first.html_url || first.url || ""))}`);
|
out.push(`OPEN_PR_URL=${JSON.stringify(String(first.html_url || first.url || ""))}`);
|
||||||
out.push(`OPEN_PR_BRANCH=${JSON.stringify(String(first?.head?.ref || ""))}`);
|
out.push(`OPEN_PR_BRANCH=${JSON.stringify(String(first?.head?.ref || ""))}`);
|
||||||
@@ -236,72 +294,48 @@ jobs:
|
|||||||
|
|
||||||
case "${SKIP_REASON:-}" in
|
case "${SKIP_REASON:-}" in
|
||||||
queue_busy_open_proposer_pr)
|
queue_busy_open_proposer_pr)
|
||||||
MSG="ℹ️ Ticket mis en file d’attente Proposer.\n\nUne PR Proposer est déjà ouverte : ${OPEN_PR_URL:-"(URL indisponible)"}\n\nLe workflow reprendra automatiquement le prochain lot après intégration sur main."
|
MSG="Ticket queued in proposer queue. An open proposer PR already exists: ${OPEN_PR_URL:-"(URL unavailable)"}. The workflow will resume after merge on main."
|
||||||
;;
|
;;
|
||||||
issue_already_has_open_pr)
|
issue_already_has_open_pr)
|
||||||
MSG="ℹ️ Ce ticket a déjà une PR Proposer ouverte : ${OPEN_PR_URL:-"(URL indisponible)"}"
|
MSG="This ticket already has an open proposer PR: ${OPEN_PR_URL:-"(URL unavailable)"}"
|
||||||
;;
|
;;
|
||||||
explicit_issue_missing_chemin)
|
explicit_issue_missing_chemin)
|
||||||
MSG="ℹ️ Proposer Apply: impossible de traiter ce ticket automatiquement car le champ **Chemin** est manquant ou illisible."
|
MSG="Proposer Apply: cannot process this ticket automatically because field Chemin is missing or unreadable."
|
||||||
;;
|
;;
|
||||||
explicit_issue_missing_type)
|
explicit_issue_missing_type)
|
||||||
MSG="ℹ️ Proposer Apply: impossible de traiter ce ticket automatiquement car le champ **Type** est manquant ou illisible."
|
MSG="Proposer Apply: cannot process this ticket automatically because field Type is missing or unreadable."
|
||||||
;;
|
;;
|
||||||
explicit_issue_not_approved)
|
explicit_issue_not_approved)
|
||||||
MSG="ℹ️ Proposer Apply: ce ticket n’est pas actuellement marqué **state/approved**."
|
MSG="Proposer Apply: this ticket is not currently labeled state/approved."
|
||||||
;;
|
;;
|
||||||
explicit_issue_rejected)
|
explicit_issue_rejected)
|
||||||
MSG="ℹ️ Proposer Apply: ce ticket porte **state/rejected** et n’entre donc pas dans la file Proposer."
|
MSG="Proposer Apply: this ticket has state/rejected and is not eligible for the proposer queue."
|
||||||
;;
|
;;
|
||||||
no_open_approved_proposer_issue)
|
no_open_approved_proposer_issue)
|
||||||
MSG="ℹ️ Aucun ticket Proposer approuvé n’est actuellement en attente."
|
MSG="No approved proposer ticket is currently waiting."
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
MSG="ℹ️ Proposer Apply: skip — ${SKIP_REASON:-raison non précisée}."
|
MSG="Proposer Apply: skip - ${SKIP_REASON:-unspecified reason}."
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1]||""}))' "$MSG")"
|
node --input-type=module - <<'NODE' > /tmp/proposer.skip.comment.json
|
||||||
|
const msg = process.env.MSG || "";
|
||||||
|
process.stdout.write(JSON.stringify({ body: msg }));
|
||||||
|
NODE
|
||||||
|
|
||||||
curl -fsS -X POST \
|
curl -fsS -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_TO_COMMENT/comments" \
|
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE_TO_COMMENT/comments" \
|
||||||
--data-binary "$PAYLOAD" || true
|
--data-binary @/tmp/proposer.skip.comment.json || true
|
||||||
|
|
||||||
- name: Checkout default branch
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
source /tmp/proposer.env
|
|
||||||
[[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
|
||||||
|
|
||||||
rm -rf .git
|
|
||||||
git init -q
|
|
||||||
git remote add origin "$CLONE_URL"
|
|
||||||
git fetch --depth 1 origin "$DEFAULT_BRANCH"
|
|
||||||
git -c advice.detachedHead=false checkout -q FETCH_HEAD
|
|
||||||
git log -1 --oneline
|
|
||||||
|
|
||||||
- name: Detect app dir (repo-root vs ./site)
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
source /tmp/proposer.env
|
|
||||||
[[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
|
||||||
|
|
||||||
APP_DIR="."
|
|
||||||
if [[ -d "site" && -f "site/package.json" ]]; then
|
|
||||||
APP_DIR="site"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "APP_DIR=$APP_DIR" >> /tmp/proposer.env
|
|
||||||
echo "✅ APP_DIR=$APP_DIR"
|
|
||||||
test -f "$APP_DIR/package.json" || { echo "❌ package.json missing in APP_DIR=$APP_DIR"; exit 1; }
|
|
||||||
test -d "$APP_DIR/scripts" || { echo "❌ scripts/ missing in APP_DIR=$APP_DIR"; exit 1; }
|
|
||||||
|
|
||||||
- name: NPM harden
|
- name: NPM harden
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source /tmp/proposer.env
|
source /tmp/proposer.env
|
||||||
[[ "${SKIP:-0}" != "1" ]] || exit 0
|
[[ "${SKIP:-0}" != "1" ]] || exit 0
|
||||||
|
|
||||||
cd "$APP_DIR"
|
cd "$APP_DIR"
|
||||||
npm config set fetch-retries 5
|
npm config set fetch-retries 5
|
||||||
npm config set fetch-retry-mintimeout 20000
|
npm config set fetch-retry-mintimeout 20000
|
||||||
@@ -313,6 +347,7 @@ jobs:
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source /tmp/proposer.env
|
source /tmp/proposer.env
|
||||||
[[ "${SKIP:-0}" != "1" ]] || exit 0
|
[[ "${SKIP:-0}" != "1" ]] || exit 0
|
||||||
|
|
||||||
cd "$APP_DIR"
|
cd "$APP_DIR"
|
||||||
npm ci --no-audit --no-fund
|
npm ci --no-audit --no-fund
|
||||||
|
|
||||||
@@ -321,6 +356,7 @@ jobs:
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source /tmp/proposer.env
|
source /tmp/proposer.env
|
||||||
[[ "${SKIP:-0}" != "1" ]] || exit 0
|
[[ "${SKIP:-0}" != "1" ]] || exit 0
|
||||||
|
|
||||||
cd "$APP_DIR"
|
cd "$APP_DIR"
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
@@ -333,9 +369,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source /tmp/proposer.env
|
source /tmp/proposer.env
|
||||||
[[ "${SKIP:-0}" != "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
[[ "${SKIP:-0}" != "1" ]] || { echo "Skipped"; exit 0; }
|
||||||
|
|
||||||
git config user.name "${BOT_GIT_NAME:-archicratie-bot}"
|
git config user.name "${BOT_GIT_NAME:-archicratie-bot}"
|
||||||
git config user.email "${BOT_GIT_EMAIL:-bot@archicratie.local}"
|
git config user.email "${BOT_GIT_EMAIL:-bot@archicratie.local}"
|
||||||
|
|
||||||
START_SHA="$(git rev-parse HEAD)"
|
START_SHA="$(git rev-parse HEAD)"
|
||||||
@@ -353,13 +389,16 @@ jobs:
|
|||||||
|
|
||||||
RC=0
|
RC=0
|
||||||
FAILED_ISSUE=""
|
FAILED_ISSUE=""
|
||||||
|
|
||||||
for ISSUE in $TARGET_ISSUES; do
|
for ISSUE in $TARGET_ISSUES; do
|
||||||
echo "" >>"$LOG"
|
echo "" >> "$LOG"
|
||||||
echo "== ticket #$ISSUE ==" >>"$LOG"
|
echo "== ticket #$ISSUE ==" >> "$LOG"
|
||||||
|
|
||||||
set +e
|
set +e
|
||||||
(cd "$APP_DIR" && node scripts/apply-ticket.mjs "$ISSUE" --alias --commit) >>"$LOG" 2>&1
|
(cd "$APP_DIR" && node scripts/apply-ticket.mjs "$ISSUE" --alias --commit) >> "$LOG" 2>&1
|
||||||
STEP_RC=$?
|
STEP_RC=$?
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [[ "$STEP_RC" -ne 0 ]]; then
|
if [[ "$STEP_RC" -ne 0 ]]; then
|
||||||
RC="$STEP_RC"
|
RC="$STEP_RC"
|
||||||
FAILED_ISSUE="$ISSUE"
|
FAILED_ISSUE="$ISSUE"
|
||||||
@@ -370,10 +409,11 @@ jobs:
|
|||||||
echo "APPLY_RC=$RC" >> /tmp/proposer.env
|
echo "APPLY_RC=$RC" >> /tmp/proposer.env
|
||||||
echo "FAILED_ISSUE=${FAILED_ISSUE}" >> /tmp/proposer.env
|
echo "FAILED_ISSUE=${FAILED_ISSUE}" >> /tmp/proposer.env
|
||||||
|
|
||||||
echo "== apply log (tail) =="
|
echo "Apply log (tail):"
|
||||||
tail -n 220 "$LOG" || true
|
tail -n 220 "$LOG" || true
|
||||||
|
|
||||||
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/proposer.env
|
echo "NOOP=0" >> /tmp/proposer.env
|
||||||
exit 0
|
exit 0
|
||||||
@@ -400,7 +440,7 @@ jobs:
|
|||||||
git fetch origin "$DEFAULT_BRANCH"
|
git fetch origin "$DEFAULT_BRANCH"
|
||||||
|
|
||||||
set +e
|
set +e
|
||||||
git rebase "origin/$DEFAULT_BRANCH" >>"$LOG" 2>&1
|
git rebase "origin/$DEFAULT_BRANCH" >> "$LOG" 2>&1
|
||||||
RC=$?
|
RC=$?
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
@@ -410,7 +450,7 @@ jobs:
|
|||||||
|
|
||||||
echo "REBASE_RC=$RC" >> /tmp/proposer.env
|
echo "REBASE_RC=$RC" >> /tmp/proposer.env
|
||||||
|
|
||||||
echo "== rebase log (tail) =="
|
echo "Rebase log (tail):"
|
||||||
tail -n 220 "$LOG" || true
|
tail -n 220 "$LOG" || true
|
||||||
|
|
||||||
- name: Comment issues on failure
|
- name: Comment issues on failure
|
||||||
@@ -426,7 +466,7 @@ jobs:
|
|||||||
REBASE_RC="${REBASE_RC:-0}"
|
REBASE_RC="${REBASE_RC:-0}"
|
||||||
|
|
||||||
if [[ "$APPLY_RC" == "0" && "$REBASE_RC" == "0" ]]; then
|
if [[ "$APPLY_RC" == "0" && "$REBASE_RC" == "0" ]]; then
|
||||||
echo "ℹ️ no failure detected"
|
echo "No failure detected"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -438,20 +478,35 @@ jobs:
|
|||||||
BODY="(no proposer log found)"
|
BODY="(no proposer log found)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
export BODY APPLY_RC REBASE_RC FAILED_ISSUE
|
||||||
|
|
||||||
if [[ "$APPLY_RC" != "0" ]]; then
|
if [[ "$APPLY_RC" != "0" ]]; then
|
||||||
MSG="❌ Batch Proposer en échec sur le ticket #${FAILED_ISSUE:-"(inconnu)"} (rc=${APPLY_RC}).\n\n\`\`\`\n${BODY}\n\`\`\`\n"
|
export FAILURE_KIND="apply"
|
||||||
else
|
else
|
||||||
MSG="❌ Rebase Proposer en échec sur main (rc=${REBASE_RC}).\n\n\`\`\`\n${BODY}\n\`\`\`\n"
|
export FAILURE_KIND="rebase"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1]||""}))' "$MSG")"
|
node --input-type=module - <<'NODE' > /tmp/proposer.failure.comment.json
|
||||||
|
const body = process.env.BODY || "";
|
||||||
|
const applyRc = process.env.APPLY_RC || "0";
|
||||||
|
const rebaseRc = process.env.REBASE_RC || "0";
|
||||||
|
const failedIssue = process.env.FAILED_ISSUE || "unknown";
|
||||||
|
const kind = process.env.FAILURE_KIND || "apply";
|
||||||
|
|
||||||
|
const msg =
|
||||||
|
kind === "apply"
|
||||||
|
? `Batch proposer failed on ticket #${failedIssue} (rc=${applyRc}).\n\n\`\`\`\n${body}\n\`\`\`\n`
|
||||||
|
: `Rebase proposer failed on main (rc=${rebaseRc}).\n\n\`\`\`\n${body}\n\`\`\`\n`;
|
||||||
|
|
||||||
|
process.stdout.write(JSON.stringify({ body: msg }));
|
||||||
|
NODE
|
||||||
|
|
||||||
for ISSUE in ${TARGET_ISSUES:-}; do
|
for ISSUE in ${TARGET_ISSUES:-}; do
|
||||||
curl -fsS -X POST \
|
curl -fsS -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/comments" \
|
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE/comments" \
|
||||||
--data-binary "$PAYLOAD" || true
|
--data-binary @/tmp/proposer.failure.comment.json || true
|
||||||
done
|
done
|
||||||
|
|
||||||
- name: Push bot branch
|
- name: Push bot branch
|
||||||
@@ -462,10 +517,10 @@ jobs:
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source /tmp/proposer.env || true
|
source /tmp/proposer.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:-0}" == "0" ]] || { echo "Apply failed -> skip push"; exit 0; }
|
||||||
[[ "${REBASE_RC:-0}" == "0" ]] || { echo "ℹ️ rebase failed -> skip push"; exit 0; }
|
[[ "${REBASE_RC:-0}" == "0" ]] || { echo "Rebase 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; }
|
||||||
[[ -n "${BRANCH:-}" ]] || { echo "ℹ️ BRANCH unset -> skip push"; exit 0; }
|
[[ -n "${BRANCH:-}" ]] || { echo "BRANCH unset -> 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);
|
||||||
@@ -489,7 +544,7 @@ jobs:
|
|||||||
[[ "${APPLY_RC:-0}" == "0" ]] || exit 0
|
[[ "${APPLY_RC:-0}" == "0" ]] || exit 0
|
||||||
[[ "${REBASE_RC:-0}" == "0" ]] || exit 0
|
[[ "${REBASE_RC:-0}" == "0" ]] || exit 0
|
||||||
[[ "${NOOP:-0}" == "0" ]] || exit 0
|
[[ "${NOOP:-0}" == "0" ]] || exit 0
|
||||||
[[ -n "${BRANCH:-}" ]] || { echo "ℹ️ BRANCH unset -> skip PR"; exit 0; }
|
[[ -n "${BRANCH:-}" ]] || { echo "BRANCH unset -> skip PR"; exit 0; }
|
||||||
|
|
||||||
if [[ "${TARGET_COUNT:-0}" == "1" ]]; then
|
if [[ "${TARGET_COUNT:-0}" == "1" ]]; then
|
||||||
PR_TITLE="proposer: apply ticket #${TARGET_PRIMARY_ISSUE}"
|
PR_TITLE="proposer: apply ticket #${TARGET_PRIMARY_ISSUE}"
|
||||||
@@ -497,64 +552,74 @@ jobs:
|
|||||||
PR_TITLE="proposer: apply ${TARGET_COUNT} tickets on ${TARGET_CHEMIN}"
|
PR_TITLE="proposer: apply ${TARGET_COUNT} tickets on ${TARGET_CHEMIN}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PR_PAYLOAD="$(
|
export TITLE="$PR_TITLE"
|
||||||
TITLE="$PR_TITLE" \
|
export CHEMIN="$TARGET_CHEMIN"
|
||||||
CHEMIN="$TARGET_CHEMIN" \
|
export ISSUES="$TARGET_ISSUES"
|
||||||
ISSUES="$TARGET_ISSUES" \
|
export BRANCH="$BRANCH"
|
||||||
BRANCH="$BRANCH" \
|
export END_SHA="${END_SHA:-unknown}"
|
||||||
END_SHA="${END_SHA:-unknown}" \
|
export DEFAULT_BRANCH="$DEFAULT_BRANCH"
|
||||||
DEFAULT_BRANCH="$DEFAULT_BRANCH" \
|
export OWNER="$OWNER"
|
||||||
OWNER="$OWNER" \
|
|
||||||
node --input-type=module <<'NODE'
|
|
||||||
const issues = String(process.env.ISSUES || "")
|
|
||||||
.trim()
|
|
||||||
.split(/\s+/)
|
|
||||||
.filter(Boolean);
|
|
||||||
|
|
||||||
const body = [
|
node --input-type=module - <<'NODE' > /tmp/proposer.pr.json
|
||||||
`PR auto depuis ticket${issues.length > 1 ? "s" : ""} ${issues.map(n => `#${n}`).join(", ")} (state/approved).`,
|
const issues = String(process.env.ISSUES || "")
|
||||||
"",
|
.trim()
|
||||||
`- Chemin: ${process.env.CHEMIN || "(inconnu)"}`,
|
.split(/\s+/)
|
||||||
"- Tickets:",
|
.filter(Boolean);
|
||||||
...issues.map(n => ` - #${n}`),
|
|
||||||
`- Branche: ${process.env.BRANCH}`,
|
|
||||||
`- Commit: ${process.env.END_SHA || "unknown"}`,
|
|
||||||
"",
|
|
||||||
"Merge si CI OK."
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
console.log(JSON.stringify({
|
const body = [
|
||||||
title: process.env.TITLE,
|
`PR auto depuis ticket${issues.length > 1 ? "s" : ""} ${issues.map((n) => `#${n}`).join(", ")} (state/approved).`,
|
||||||
body,
|
"",
|
||||||
base: process.env.DEFAULT_BRANCH,
|
`- Chemin: ${process.env.CHEMIN || "(inconnu)"}`,
|
||||||
head: `${process.env.OWNER}:${process.env.BRANCH}`,
|
"- Tickets:",
|
||||||
allow_maintainer_edit: true
|
...issues.map((n) => ` - #${n}`),
|
||||||
}));
|
`- Branche: ${process.env.BRANCH || ""}`,
|
||||||
|
`- Commit: ${process.env.END_SHA || "unknown"}`,
|
||||||
|
"",
|
||||||
|
"Merge si CI OK."
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
process.stdout.write(JSON.stringify({
|
||||||
|
title: process.env.TITLE || "proposer: apply tickets",
|
||||||
|
body,
|
||||||
|
base: process.env.DEFAULT_BRANCH || "main",
|
||||||
|
head: `${process.env.OWNER}:${process.env.BRANCH}`,
|
||||||
|
allow_maintainer_edit: true
|
||||||
|
}));
|
||||||
NODE
|
NODE
|
||||||
)"
|
|
||||||
|
|
||||||
PR_JSON="$(curl -fsS -X POST \
|
PR_JSON="$(curl -fsS -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" \
|
||||||
--data-binary "$PR_PAYLOAD")"
|
--data-binary @/tmp/proposer.pr.json)"
|
||||||
|
|
||||||
PR_URL="$(node --input-type=module -e '
|
PR_URL="$(node --input-type=module -e '
|
||||||
const pr = JSON.parse(process.argv[1] || "{}");
|
const pr = JSON.parse(process.argv[1] || "{}");
|
||||||
console.log(pr.html_url || pr.url || "");
|
console.log(pr.html_url || pr.url || "");
|
||||||
' "$PR_JSON")"
|
' "$PR_JSON")"
|
||||||
|
|
||||||
test -n "$PR_URL" || { echo "❌ PR URL missing. Raw: $PR_JSON"; exit 1; }
|
test -n "$PR_URL" || {
|
||||||
|
echo "PR URL missing. Raw: $PR_JSON"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
for ISSUE in $TARGET_ISSUES; do
|
for ISSUE in $TARGET_ISSUES; do
|
||||||
MSG="✅ PR Proposer créée pour le ticket #${ISSUE} : ${PR_URL}\n\nLe ticket est clôturé automatiquement ; la discussion peut se poursuivre dans la PR."
|
export ISSUE PR_URL
|
||||||
C_PAYLOAD="$(node --input-type=module -e 'console.log(JSON.stringify({body: process.argv[1]||""}))' "$MSG")"
|
node --input-type=module - <<'NODE' > /tmp/proposer.issue.close.comment.json
|
||||||
|
const issue = process.env.ISSUE || "";
|
||||||
|
const url = process.env.PR_URL || "";
|
||||||
|
const msg =
|
||||||
|
`PR proposer created for ticket #${issue}: ${url}\n\n` +
|
||||||
|
`The ticket is closed automatically. Discussion can continue in the PR.`;
|
||||||
|
|
||||||
|
process.stdout.write(JSON.stringify({ body: msg }));
|
||||||
|
NODE
|
||||||
|
|
||||||
curl -fsS -X POST \
|
curl -fsS -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/comments" \
|
"$API_BASE/api/v1/repos/$OWNER/$REPO/issues/$ISSUE/comments" \
|
||||||
--data-binary "$C_PAYLOAD"
|
--data-binary @/tmp/proposer.issue.close.comment.json
|
||||||
|
|
||||||
curl -fsS -X PATCH \
|
curl -fsS -X PATCH \
|
||||||
-H "Authorization: token $FORGE_TOKEN" \
|
-H "Authorization: token $FORGE_TOKEN" \
|
||||||
@@ -563,7 +628,7 @@ jobs:
|
|||||||
--data-binary '{"state":"closed"}'
|
--data-binary '{"state":"closed"}'
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "✅ PR: $PR_URL"
|
echo "PR: $PR_URL"
|
||||||
|
|
||||||
- name: Finalize
|
- name: Finalize
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
@@ -573,13 +638,13 @@ jobs:
|
|||||||
[[ "${SKIP:-0}" != "1" ]] || exit 0
|
[[ "${SKIP:-0}" != "1" ]] || exit 0
|
||||||
|
|
||||||
if [[ "${APPLY_RC:-0}" != "0" ]]; then
|
if [[ "${APPLY_RC:-0}" != "0" ]]; then
|
||||||
echo "❌ apply failed (rc=${APPLY_RC})"
|
echo "Apply failed (rc=${APPLY_RC})"
|
||||||
exit "${APPLY_RC}"
|
exit "${APPLY_RC}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${REBASE_RC:-0}" != "0" ]]; then
|
if [[ "${REBASE_RC:-0}" != "0" ]]; then
|
||||||
echo "❌ rebase failed (rc=${REBASE_RC})"
|
echo "Rebase failed (rc=${REBASE_RC})"
|
||||||
exit "${REBASE_RC}"
|
exit "${REBASE_RC}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✅ proposer queue ok"
|
echo "Proposer queue OK"
|
||||||
Reference in New Issue
Block a user