goldenChat base source add
This commit is contained in:
+628
@@ -0,0 +1,628 @@
|
||||
name: Release Health Check
|
||||
|
||||
concurrency:
|
||||
group: release-health-${{ github.repository }}
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
schedule:
|
||||
- cron: "0 9 * * *"
|
||||
workflow_run:
|
||||
workflows: ["Publish Release to Maven Central", "Publish Snapshot to Maven Central"]
|
||||
types:
|
||||
- completed
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
staleDays:
|
||||
description: "Days before a release PR is considered stale."
|
||||
required: false
|
||||
default: "7"
|
||||
type: string
|
||||
dryRun:
|
||||
description: "Run release health in dry-run mode (audit only; no discussion mutation)."
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
discussions: write
|
||||
|
||||
jobs:
|
||||
health:
|
||||
name: Release health checks
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.event_name != 'workflow_run' ||
|
||||
(
|
||||
github.event.workflow_run.head_repository.full_name == github.repository
|
||||
)
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Normalize dry run input
|
||||
id: dry_run
|
||||
run: |
|
||||
if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then
|
||||
raw="${{ github.event.inputs.dryRun }}"
|
||||
if [ -z "$raw" ]; then
|
||||
raw=true
|
||||
fi
|
||||
normalized=$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
if [ "$normalized" = "false" ] || [ "$normalized" = "0" ] || [ "$normalized" = "no" ]; then
|
||||
dry_run=false
|
||||
else
|
||||
dry_run=true
|
||||
fi
|
||||
else
|
||||
raw=""
|
||||
dry_run=false
|
||||
fi
|
||||
|
||||
echo "audit:dry_run_input_raw='${raw}'"
|
||||
echo "audit:dry_run_normalized=$dry_run"
|
||||
echo "dryRun=$dry_run" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Fetch tags
|
||||
run: git fetch --tags
|
||||
|
||||
- name: Resolve default branch
|
||||
id: default_branch
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "::group::Resolve default branch"
|
||||
|
||||
branch="${{ github.event.repository.default_branch }}"
|
||||
if [ -z "$branch" ]; then
|
||||
branch=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || true)
|
||||
fi
|
||||
if [ -z "$branch" ]; then
|
||||
branch=$(git remote show origin 2>/dev/null | awk '/HEAD branch/ {print $NF}' || true)
|
||||
fi
|
||||
if [ -z "$branch" ]; then
|
||||
echo "Default branch not provided by event or git." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "branch=$branch" >> $GITHUB_OUTPUT
|
||||
echo "audit:default_branch=$branch"
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Collect tag and version state
|
||||
id: state
|
||||
env:
|
||||
DEFAULT_BRANCH: ${{ steps.default_branch.outputs.branch }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "::group::Collect release tag and version state"
|
||||
|
||||
git fetch origin "$DEFAULT_BRANCH" --tags
|
||||
|
||||
target_ref="origin/${DEFAULT_BRANCH}"
|
||||
tag_state_file=$(mktemp)
|
||||
scripts/resolve-release-tags.sh "$target_ref" > "$tag_state_file"
|
||||
|
||||
latest_tag="none"
|
||||
while IFS='=' read -r key value; do
|
||||
case "$key" in
|
||||
latest_tag) latest_tag="$value" ;;
|
||||
esac
|
||||
done < "$tag_state_file"
|
||||
|
||||
cat "$tag_state_file" >> "$GITHUB_OUTPUT"
|
||||
cp "$tag_state_file" tag-resolution.txt
|
||||
rm -f "$tag_state_file"
|
||||
|
||||
pom_version=$(python3 - <<'PY'
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
def find_version(path):
|
||||
tree = ET.parse(path)
|
||||
root = tree.getroot()
|
||||
def find_first(elem):
|
||||
if elem is None:
|
||||
return None
|
||||
for child in elem:
|
||||
if child.tag.endswith("version"):
|
||||
text = (child.text or "").strip()
|
||||
if text:
|
||||
return text
|
||||
return None
|
||||
return find_first(root) or find_first(root.find("./{*}parent"))
|
||||
|
||||
ver = find_version("pom.xml")
|
||||
if not ver:
|
||||
sys.exit(1)
|
||||
print(ver)
|
||||
PY
|
||||
) || { echo "Could not read pom.xml version" >&2; exit 1; }
|
||||
|
||||
pom_snapshot=false
|
||||
pom_base="$pom_version"
|
||||
if [[ "$pom_version" == *-SNAPSHOT ]]; then
|
||||
pom_snapshot=true
|
||||
pom_base="${pom_version%-SNAPSHOT}"
|
||||
fi
|
||||
|
||||
snapshot_ok="n/a"
|
||||
if [ "$latest_tag" != "none" ]; then
|
||||
latest_base="${latest_tag#v}"
|
||||
if [ "$pom_snapshot" != "true" ]; then
|
||||
snapshot_ok=false
|
||||
else
|
||||
highest=$(printf '%s\n' "$latest_base" "$pom_base" | sort -V | tail -n1)
|
||||
if [ "$highest" = "$pom_base" ] && [ "$pom_base" != "$latest_base" ]; then
|
||||
snapshot_ok=true
|
||||
else
|
||||
snapshot_ok=false
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
notes_present="n/a"
|
||||
if [ "$latest_tag" != "none" ]; then
|
||||
if git cat-file -e "${latest_tag}:release/${latest_tag}.md" 2>/dev/null; then
|
||||
notes_present=true
|
||||
elif git cat-file -e "${latest_tag}:release/${latest_tag#v}.md" 2>/dev/null; then
|
||||
notes_present=true
|
||||
else
|
||||
notes_present=false
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "pom_version=$pom_version" >> $GITHUB_OUTPUT
|
||||
echo "pom_base=$pom_base" >> $GITHUB_OUTPUT
|
||||
echo "pom_snapshot=$pom_snapshot" >> $GITHUB_OUTPUT
|
||||
echo "snapshot_ok=$snapshot_ok" >> $GITHUB_OUTPUT
|
||||
echo "release_notes_present=$notes_present" >> $GITHUB_OUTPUT
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Resolve snapshot publication state
|
||||
id: snapshot_publication
|
||||
env:
|
||||
POM_VERSION: ${{ steps.state.outputs.pom_version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 scripts/release/release_helpers.py snapshot-publication \
|
||||
--version "${POM_VERSION}" \
|
||||
--output snapshot-publication.json \
|
||||
--github-output "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Resolve snapshot publication policy
|
||||
id: snapshot_policy
|
||||
env:
|
||||
HEALTH_EVENT_NAME: ${{ github.event_name }}
|
||||
HEALTH_TRIGGER_WORKFLOW: ${{ github.event.workflow_run.name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 scripts/release/release_helpers.py snapshot-publication-policy \
|
||||
--event-name "${HEALTH_EVENT_NAME}" \
|
||||
--workflow-name "${HEALTH_TRIGGER_WORKFLOW}" \
|
||||
--output snapshot-publication-policy.json \
|
||||
--github-output "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Check for stale release PRs
|
||||
id: stale_prs
|
||||
uses: actions/github-script@v9
|
||||
env:
|
||||
INPUT_STALE_DAYS: ${{ github.event.inputs.staleDays }}
|
||||
VAR_STALE_DAYS: ${{ vars.RELEASE_PR_STALE_DAYS }}
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
script: |
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const inputDays = process.env.INPUT_STALE_DAYS;
|
||||
const varDays = process.env.VAR_STALE_DAYS;
|
||||
const staleDays = Number(inputDays || varDays || "7");
|
||||
|
||||
if (!Number.isFinite(staleDays) || staleDays <= 0) {
|
||||
throw new Error(`Invalid stale day threshold: ${inputDays || varDays}`);
|
||||
}
|
||||
|
||||
const cutoff = new Date(Date.now() - staleDays * 24 * 60 * 60 * 1000);
|
||||
const prs = await github.paginate(github.rest.pulls.list, {
|
||||
owner,
|
||||
repo,
|
||||
state: "open",
|
||||
per_page: 100
|
||||
});
|
||||
|
||||
const stale = prs.filter((pr) => {
|
||||
const labels = pr.labels || [];
|
||||
const hasReleaseLabel = labels.some((label) => label.name === "release");
|
||||
if (!hasReleaseLabel) {
|
||||
return false;
|
||||
}
|
||||
return new Date(pr.created_at) < cutoff;
|
||||
});
|
||||
|
||||
const lines = stale.map((pr) => {
|
||||
const ageDays = Math.floor((Date.now() - new Date(pr.created_at)) / (24 * 60 * 60 * 1000));
|
||||
return `#${pr.number} ${pr.title} (${ageDays} days old)`;
|
||||
});
|
||||
|
||||
core.setOutput("stale_days", staleDays.toString());
|
||||
core.setOutput("stale_count", stale.length.toString());
|
||||
core.setOutput("stale_list", lines.join("\n") || "(none)");
|
||||
|
||||
- name: Build health summary
|
||||
id: summary
|
||||
if: always()
|
||||
env:
|
||||
DEFAULT_BRANCH: ${{ steps.default_branch.outputs.branch }}
|
||||
LATEST_TAG: ${{ steps.state.outputs.latest_tag }}
|
||||
LAST_REACHABLE_TAG: ${{ steps.state.outputs.last_reachable_tag }}
|
||||
LAST_FIRST_PARENT_TAG: ${{ steps.state.outputs.last_first_parent_tag }}
|
||||
LATEST_TAG_REACHABLE: ${{ steps.state.outputs.latest_tag_reachable }}
|
||||
POM_VERSION: ${{ steps.state.outputs.pom_version }}
|
||||
POM_BASE: ${{ steps.state.outputs.pom_base }}
|
||||
POM_SNAPSHOT: ${{ steps.state.outputs.pom_snapshot }}
|
||||
SNAPSHOT_OK: ${{ steps.state.outputs.snapshot_ok }}
|
||||
SNAPSHOT_PUBLICATION: ${{ steps.snapshot_publication.outputs.snapshot_publication }}
|
||||
SNAPSHOT_PUBLICATION_LATEST: ${{ steps.snapshot_publication.outputs.snapshot_publication_latest }}
|
||||
SNAPSHOT_PUBLICATION_LAST_UPDATED: ${{ steps.snapshot_publication.outputs.snapshot_publication_last_updated }}
|
||||
SNAPSHOT_PUBLICATION_SOURCE: ${{ steps.snapshot_publication.outputs.snapshot_publication_source }}
|
||||
SNAPSHOT_PUBLICATION_ENFORCED: ${{ steps.snapshot_policy.outputs.snapshot_publication_enforced }}
|
||||
SNAPSHOT_PUBLICATION_PENDING_REASON: ${{ steps.snapshot_policy.outputs.snapshot_publication_pending_reason }}
|
||||
NOTES_PRESENT: ${{ steps.state.outputs.release_notes_present }}
|
||||
STALE_DAYS: ${{ steps.stale_prs.outputs.stale_days }}
|
||||
STALE_COUNT: ${{ steps.stale_prs.outputs.stale_count }}
|
||||
STALE_LIST: ${{ steps.stale_prs.outputs.stale_list }}
|
||||
RELEASE_NOTIFY_USER: ${{ vars.RELEASE_NOTIFY_USER }}
|
||||
HEALTH_EVENT_NAME: ${{ github.event_name }}
|
||||
HEALTH_TRIGGER_WORKFLOW: ${{ github.event.workflow_run.name }}
|
||||
DRY_RUN: ${{ steps.dry_run.outputs.dryRun }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ -z "${DEFAULT_BRANCH:-}" ]; then
|
||||
DEFAULT_BRANCH="(unknown)"
|
||||
fi
|
||||
if [ -z "${LATEST_TAG:-}" ]; then
|
||||
LATEST_TAG="(unknown)"
|
||||
fi
|
||||
if [ -z "${LAST_REACHABLE_TAG:-}" ]; then
|
||||
LAST_REACHABLE_TAG="(unknown)"
|
||||
fi
|
||||
if [ -z "${LAST_FIRST_PARENT_TAG:-}" ]; then
|
||||
LAST_FIRST_PARENT_TAG="(unknown)"
|
||||
fi
|
||||
if [ -z "${LATEST_TAG_REACHABLE:-}" ]; then
|
||||
LATEST_TAG_REACHABLE="(unknown)"
|
||||
fi
|
||||
if [ -z "${POM_VERSION:-}" ]; then
|
||||
POM_VERSION="(unknown)"
|
||||
fi
|
||||
if [ -z "${POM_BASE:-}" ]; then
|
||||
POM_BASE="(unknown)"
|
||||
fi
|
||||
if [ -z "${POM_SNAPSHOT:-}" ]; then
|
||||
POM_SNAPSHOT="(unknown)"
|
||||
fi
|
||||
if [ -z "${SNAPSHOT_OK:-}" ]; then
|
||||
SNAPSHOT_OK="(unknown)"
|
||||
fi
|
||||
if [ -z "${SNAPSHOT_PUBLICATION:-}" ]; then
|
||||
SNAPSHOT_PUBLICATION="unknown"
|
||||
fi
|
||||
if [ -z "${SNAPSHOT_PUBLICATION_LATEST:-}" ]; then
|
||||
SNAPSHOT_PUBLICATION_LATEST="(unknown)"
|
||||
fi
|
||||
if [ -z "${SNAPSHOT_PUBLICATION_LAST_UPDATED:-}" ]; then
|
||||
SNAPSHOT_PUBLICATION_LAST_UPDATED="(unknown)"
|
||||
fi
|
||||
if [ -z "${SNAPSHOT_PUBLICATION_SOURCE:-}" ]; then
|
||||
SNAPSHOT_PUBLICATION_SOURCE="(unknown)"
|
||||
fi
|
||||
if [ -z "${SNAPSHOT_PUBLICATION_ENFORCED:-}" ]; then
|
||||
SNAPSHOT_PUBLICATION_ENFORCED="false"
|
||||
fi
|
||||
if [ -z "${SNAPSHOT_PUBLICATION_PENDING_REASON:-}" ]; then
|
||||
SNAPSHOT_PUBLICATION_PENDING_REASON="(none)"
|
||||
fi
|
||||
if [ -z "${NOTES_PRESENT:-}" ]; then
|
||||
NOTES_PRESENT="(unknown)"
|
||||
fi
|
||||
if [ -z "${STALE_DAYS:-}" ]; then
|
||||
STALE_DAYS="(unknown)"
|
||||
fi
|
||||
if [ -z "${STALE_COUNT:-}" ]; then
|
||||
STALE_COUNT="0"
|
||||
fi
|
||||
if [ -z "${STALE_LIST:-}" ]; then
|
||||
STALE_LIST="(none)"
|
||||
fi
|
||||
if [ -z "${HEALTH_EVENT_NAME:-}" ]; then
|
||||
HEALTH_EVENT_NAME="(unknown)"
|
||||
fi
|
||||
if [ -z "${HEALTH_TRIGGER_WORKFLOW:-}" ]; then
|
||||
HEALTH_TRIGGER_WORKFLOW="(none)"
|
||||
fi
|
||||
if [ -z "${DRY_RUN:-}" ]; then
|
||||
DRY_RUN="false"
|
||||
fi
|
||||
|
||||
effective_snapshot_publication="${SNAPSHOT_PUBLICATION}"
|
||||
snapshot_publication_note="(none)"
|
||||
if [ "${SNAPSHOT_PUBLICATION_ENFORCED}" != "true" ] && [[ "${SNAPSHOT_PUBLICATION}" == "false" || "${SNAPSHOT_PUBLICATION}" == "unknown" ]]; then
|
||||
effective_snapshot_publication="pending"
|
||||
snapshot_publication_note="${SNAPSHOT_PUBLICATION_PENDING_REASON}"
|
||||
fi
|
||||
|
||||
drift=false
|
||||
reasons=()
|
||||
|
||||
if [ "$LATEST_TAG_REACHABLE" = "false" ]; then
|
||||
drift=true
|
||||
reasons+=("latest tag not reachable from ${DEFAULT_BRANCH}")
|
||||
fi
|
||||
|
||||
if [ "$SNAPSHOT_OK" = "false" ]; then
|
||||
drift=true
|
||||
reasons+=("pom.xml snapshot version not ahead of latest tag")
|
||||
fi
|
||||
|
||||
if [ "$effective_snapshot_publication" = "false" ]; then
|
||||
drift=true
|
||||
reasons+=("current snapshot version not published to Maven snapshot repository")
|
||||
fi
|
||||
|
||||
if [ "$effective_snapshot_publication" = "unknown" ]; then
|
||||
drift=true
|
||||
reasons+=("snapshot publication state could not be verified")
|
||||
fi
|
||||
|
||||
if [ "$NOTES_PRESENT" = "false" ]; then
|
||||
drift=true
|
||||
reasons+=("missing release notes for latest tag")
|
||||
fi
|
||||
|
||||
if [ "${STALE_COUNT:-0}" -gt 0 ]; then
|
||||
drift=true
|
||||
reasons+=("stale release PRs detected")
|
||||
fi
|
||||
|
||||
if [ "${#reasons[@]}" -eq 0 ]; then
|
||||
reason_text="(none)"
|
||||
else
|
||||
reason_text=$(printf '%s\n' "${reasons[@]}")
|
||||
fi
|
||||
|
||||
if [ "$drift" = "true" ]; then
|
||||
status="FAIL"
|
||||
else
|
||||
status="OK"
|
||||
fi
|
||||
|
||||
run_url="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
|
||||
timestamp=$(date '+%-I:%M:%S %p on %-m.%-d.%Y')
|
||||
notify_user="${RELEASE_NOTIFY_USER:-TheCookieLab}"
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
marker_run="dry-run"
|
||||
mutation_plan="would post the release-health summary to the Release Scheduler discussion"
|
||||
rerun_guidance="rerun release-health.yml with dryRun=false, or let schedule/push/workflow-run triggers publish the health post"
|
||||
else
|
||||
marker_run="real"
|
||||
mutation_plan="posts the latest release-health summary to the Release Scheduler discussion"
|
||||
rerun_guidance="not needed for this non-dry-run health publication"
|
||||
fi
|
||||
|
||||
cat > notification-body.txt <<EOF
|
||||
<!-- ta4j:post-type=release-health;run=${marker_run} -->
|
||||
@${notify_user}
|
||||
|
||||
**Release Health Check ${status} at ${timestamp}**
|
||||
- Repository: ${GITHUB_REPOSITORY}
|
||||
- Run: ${run_url}
|
||||
- Default branch: ${DEFAULT_BRANCH}
|
||||
- Event: ${HEALTH_EVENT_NAME}
|
||||
- Upstream workflow: ${HEALTH_TRIGGER_WORKFLOW}
|
||||
- Dry run: ${DRY_RUN}
|
||||
|
||||
Tag Health:
|
||||
- latest tag: ${LATEST_TAG}
|
||||
- last reachable tag: ${LAST_REACHABLE_TAG}
|
||||
- last first-parent tag: ${LAST_FIRST_PARENT_TAG}
|
||||
- latest tag reachable: ${LATEST_TAG_REACHABLE}
|
||||
- release notes present: ${NOTES_PRESENT}
|
||||
|
||||
Version Health:
|
||||
- pom.xml version: ${POM_VERSION}
|
||||
- pom.xml base: ${POM_BASE}
|
||||
- pom.xml snapshot: ${POM_SNAPSHOT}
|
||||
- snapshot ahead of latest tag: ${SNAPSHOT_OK}
|
||||
- snapshot publication enforced: ${SNAPSHOT_PUBLICATION_ENFORCED}
|
||||
- snapshot published: ${effective_snapshot_publication}
|
||||
- snapshot publication note: ${snapshot_publication_note}
|
||||
- snapshot metadata latest: ${SNAPSHOT_PUBLICATION_LATEST}
|
||||
- snapshot metadata last updated: ${SNAPSHOT_PUBLICATION_LAST_UPDATED}
|
||||
|
||||
Stale Release PRs (>${STALE_DAYS} days):
|
||||
- count: ${STALE_COUNT}
|
||||
${STALE_LIST}
|
||||
|
||||
Drift reasons:
|
||||
${reason_text}
|
||||
|
||||
Dry-Run / Mutation Plan:
|
||||
- mutation plan: ${mutation_plan}
|
||||
- rerun guidance: ${rerun_guidance}
|
||||
EOF
|
||||
|
||||
echo "drift_found=$drift" >> $GITHUB_OUTPUT
|
||||
cat > release-health-audit.json <<EOF
|
||||
{
|
||||
"status": "${status}",
|
||||
"drift": "${drift}",
|
||||
"dryRun": "${DRY_RUN}",
|
||||
"defaultBranch": "${DEFAULT_BRANCH}",
|
||||
"latestTag": "${LATEST_TAG}",
|
||||
"lastReachableTag": "${LAST_REACHABLE_TAG}",
|
||||
"lastFirstParentTag": "${LAST_FIRST_PARENT_TAG}",
|
||||
"latestTagReachable": "${LATEST_TAG_REACHABLE}",
|
||||
"pomVersion": "${POM_VERSION}",
|
||||
"pomBase": "${POM_BASE}",
|
||||
"pomSnapshot": "${POM_SNAPSHOT}",
|
||||
"snapshotOk": "${SNAPSHOT_OK}",
|
||||
"snapshotPublicationEnforced": "${SNAPSHOT_PUBLICATION_ENFORCED}",
|
||||
"snapshotPublished": "${effective_snapshot_publication}",
|
||||
"snapshotPublicationNote": "${snapshot_publication_note}",
|
||||
"snapshotMetadataLatest": "${SNAPSHOT_PUBLICATION_LATEST}",
|
||||
"snapshotMetadataLastUpdated": "${SNAPSHOT_PUBLICATION_LAST_UPDATED}",
|
||||
"snapshotMetadataSource": "${SNAPSHOT_PUBLICATION_SOURCE}",
|
||||
"releaseNotesPresent": "${NOTES_PRESENT}",
|
||||
"staleReleasePrCount": "${STALE_COUNT}",
|
||||
"mutationPlan": "${mutation_plan}",
|
||||
"rerunGuidance": "${rerun_guidance}",
|
||||
"runUrl": "${run_url}"
|
||||
}
|
||||
EOF
|
||||
{
|
||||
echo "## Release Health"
|
||||
echo ""
|
||||
echo "- status: ${status}"
|
||||
echo "- drift: ${drift}"
|
||||
echo "- dry run: ${DRY_RUN}"
|
||||
echo "- latest tag: ${LATEST_TAG}"
|
||||
echo "- last reachable tag: ${LAST_REACHABLE_TAG}"
|
||||
echo "- latest tag reachable: ${LATEST_TAG_REACHABLE}"
|
||||
echo "- pom version: ${POM_VERSION}"
|
||||
echo "- snapshot ahead of latest tag: ${SNAPSHOT_OK}"
|
||||
echo "- snapshot publication enforced: ${SNAPSHOT_PUBLICATION_ENFORCED}"
|
||||
echo "- snapshot published: ${effective_snapshot_publication}"
|
||||
echo "- stale release PR count: ${STALE_COUNT}"
|
||||
echo "- mutation plan: ${mutation_plan}"
|
||||
echo "- rerun guidance: ${rerun_guidance}"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Upload release health audit artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: release-health-audit-${{ github.run_id }}
|
||||
path: |
|
||||
tag-resolution.txt
|
||||
snapshot-publication.json
|
||||
snapshot-publication-policy.json
|
||||
release-health-audit.json
|
||||
notification-body.txt
|
||||
if-no-files-found: ignore
|
||||
retention-days: 14
|
||||
|
||||
- name: Post to Release Scheduler discussion
|
||||
if: always() && steps.dry_run.outputs.dryRun != 'true'
|
||||
uses: actions/github-script@v9
|
||||
env:
|
||||
RELEASE_SCHEDULER_DISCUSSION_NUMBER: ${{ vars.RELEASE_SCHEDULER_DISCUSSION_NUMBER }}
|
||||
RELEASE_NOTIFY_USER: ${{ vars.RELEASE_NOTIFY_USER }}
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
script: |
|
||||
const fs = require("fs");
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const notifyUser = process.env.RELEASE_NOTIFY_USER || "TheCookieLab";
|
||||
const runUrl = process.env.GITHUB_RUN_ID
|
||||
? `https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`
|
||||
: "(unknown)";
|
||||
let body;
|
||||
if (fs.existsSync("notification-body.txt")) {
|
||||
body = fs.readFileSync("notification-body.txt", "utf8");
|
||||
} else {
|
||||
core.warning("notification-body.txt not found; posting fallback message.");
|
||||
body = `<!-- ta4j:post-type=release-health;run=real -->\n@${notifyUser}\n\n**Release Health Check summary unavailable**\n- Repository: ${process.env.GITHUB_REPOSITORY || `${owner}/${repo}`}\n- Run: ${runUrl}\n\nThe summary step did not produce notification-body.txt. Check earlier steps for failures.`;
|
||||
}
|
||||
const rawNumber = process.env.RELEASE_SCHEDULER_DISCUSSION_NUMBER;
|
||||
const number = rawNumber ? Number(rawNumber) : 1414;
|
||||
if (!Number.isFinite(number)) {
|
||||
throw new Error(`Invalid Release Scheduler discussion number: ${rawNumber}`);
|
||||
}
|
||||
|
||||
const query = `
|
||||
query($owner: String!, $repo: String!, $number: Int!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
discussion(number: $number) {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
const queryResult = await github.graphql(query, { owner, repo, number });
|
||||
const discussionId = queryResult?.repository?.discussion?.id;
|
||||
if (!discussionId) {
|
||||
throw new Error(`Discussion ${number} not found in ${owner}/${repo}`);
|
||||
}
|
||||
|
||||
const commentQuery = `
|
||||
query($owner: String!, $repo: String!, $number: Int!, $cursor: String) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
discussion(number: $number) {
|
||||
comments(first: 100, after: $cursor) {
|
||||
nodes {
|
||||
id
|
||||
body
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
const deleteMutation = `
|
||||
mutation($id: ID!) {
|
||||
deleteDiscussionComment(input: { id: $id }) {
|
||||
clientMutationId
|
||||
}
|
||||
}
|
||||
`;
|
||||
const healthCheckMarker = "<!-- ta4j:post-type=release-health;run=real -->";
|
||||
let cursor = null;
|
||||
let deletedCount = 0;
|
||||
let scannedCount = 0;
|
||||
do {
|
||||
const commentResult = await github.graphql(commentQuery, { owner, repo, number, cursor });
|
||||
const page = commentResult?.repository?.discussion?.comments;
|
||||
const nodes = page?.nodes || [];
|
||||
scannedCount += nodes.length;
|
||||
const deletions = nodes.filter((comment) => {
|
||||
if (!comment?.body) {
|
||||
return false;
|
||||
}
|
||||
return comment.body.includes(healthCheckMarker);
|
||||
});
|
||||
for (const comment of deletions) {
|
||||
await github.graphql(deleteMutation, { id: comment.id });
|
||||
deletedCount += 1;
|
||||
}
|
||||
cursor = page?.pageInfo?.hasNextPage ? page.pageInfo.endCursor : null;
|
||||
} while (cursor);
|
||||
core.info(`Scanned ${scannedCount} discussion comments; removed ${deletedCount} prior health check posts.`);
|
||||
|
||||
const mutation = `
|
||||
mutation($discussionId: ID!, $body: String!) {
|
||||
addDiscussionComment(input: { discussionId: $discussionId, body: $body }) {
|
||||
comment {
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
await github.graphql(mutation, { discussionId, body });
|
||||
|
||||
- name: Fail if drift detected
|
||||
if: steps.summary.outputs.drift_found == 'true'
|
||||
run: |
|
||||
echo "::error::Release health check failed due to detected drift."
|
||||
exit 1
|
||||
Reference in New Issue
Block a user