diff --git a/.github/workflows/dev-release.yml b/.github/workflows/dev-release.yml index c13926d3..e048677e 100644 --- a/.github/workflows/dev-release.yml +++ b/.github/workflows/dev-release.yml @@ -25,6 +25,7 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 + ref: dev # Always use the built-in workflow token; PAT rotation/breakage must not block dev releases. token: ${{ github.token }} @@ -101,8 +102,8 @@ jobs: COMMIT_TEXT=$(git log $RANGE --pretty=format:"%s%n%b" 2>/dev/null || true) ISSUES_FROM_COMMITS=$(printf '%s\n' "$COMMIT_TEXT" | \ - grep -oE "(Fixes|Closes|Resolves|Refs?) #[0-9]+" | \ - grep -oE "#[0-9]+" || true) + perl -ne 'if (/(fixes|closes|resolves|refs?)(.*)/i) { print "$2\n"; }' | \ + grep -oE '#[0-9]+' || true) PR_CANDIDATES=$(printf '%s\n' "$COMMIT_TEXT" | \ grep -oE "Merge pull request #[0-9]+|\\(#[0-9]+\\)" | \ @@ -117,8 +118,8 @@ jobs: done ISSUES_FROM_PRS=$(printf '%s\n' "$PR_TEXT" | \ - grep -oE "(Fixes|Closes|Resolves|Refs?) #[0-9]+" | \ - grep -oE "#[0-9]+" || true) + perl -ne 'if (/(fixes|closes|resolves|refs?)(.*)/i) { print "$2\n"; }' | \ + grep -oE '#[0-9]+' || true) ISSUES=$(printf '%s\n%s\n' "$ISSUES_FROM_COMMITS" "$ISSUES_FROM_PRS" | \ grep -oE "#[0-9]+" | sort -u || true) diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 6fd5c00a..ad46ad84 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -77,12 +77,12 @@ jobs: OWNER_LOWER=$(echo "${GITHUB_REPOSITORY_OWNER}" | tr '[:upper:]' '[:lower:]') IMAGE="ghcr.io/${OWNER_LOWER}/ccs-dashboard" REVISION=$(git rev-parse HEAD) - TAGS="${IMAGE}:${VERSION} - ${IMAGE}:${MINOR} - ${IMAGE}:${MAJOR}" + TAGS="${IMAGE}:${VERSION}" if [[ "${GITHUB_EVENT_NAME}" == "release" ]]; then TAGS="${TAGS} + ${IMAGE}:${MINOR} + ${IMAGE}:${MAJOR} ${IMAGE}:latest" fi diff --git a/.github/workflows/label-pending-release.yml b/.github/workflows/label-pending-release.yml index 8a5c604a..dffe8a2d 100644 --- a/.github/workflows/label-pending-release.yml +++ b/.github/workflows/label-pending-release.yml @@ -13,36 +13,59 @@ jobs: pull-requests: read steps: - - name: Label linked issues as pending-release + - name: Reconcile pending-release labels for open dev PRs env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number }} - PR_TITLE: ${{ github.event.pull_request.title }} - PR_BODY: ${{ github.event.pull_request.body }} run: | - # Extract issue numbers from PR title/body plus current PR commits. - # Using env vars prevents shell injection from backticks in markdown. - COMMIT_TEXT=$(gh api "repos/${{ github.repository }}/pulls/${PR_NUMBER}/commits" --paginate --jq '.[].commit.message' 2>/dev/null || true) - PR_TEXT="$PR_TITLE - $PR_BODY - $COMMIT_TEXT" - ISSUES=$(echo "$PR_TEXT" | grep -oE "(Fixes|Closes|Resolves|Refs?) #[0-9]+" | grep -oE "#[0-9]+" | sort -u || true) + # Recompute the complete open-PR issue set so edited/synchronized PRs + # can remove stale pending-release labels as well as add new ones. + OPEN_PRS=$(gh pr list \ + --repo "${{ github.repository }}" \ + --base dev \ + --state open \ + --json number,title,body \ + --jq '.[] | @base64' 2>/dev/null || true) + + ALL_REFERENCED_ISSUES="" + while IFS= read -r PR_ROW; do + [[ -z "$PR_ROW" ]] && continue + PR_JSON=$(printf '%s' "$PR_ROW" | base64 --decode) + PR_NUM=$(printf '%s' "$PR_JSON" | jq -r '.number') + PR_TITLE=$(printf '%s' "$PR_JSON" | jq -r '.title') + PR_BODY=$(printf '%s' "$PR_JSON" | jq -r '.body // ""') + COMMIT_TEXT=$(gh api "repos/${{ github.repository }}/pulls/${PR_NUM}/commits" --paginate --jq '.[].commit.message' 2>/dev/null || true) + PR_TEXT="$PR_TITLE + $PR_BODY + $COMMIT_TEXT" + PR_ISSUES=$(printf '%s\n' "$PR_TEXT" | \ + perl -ne 'if (/(fixes|closes|resolves|refs?)(.*)/i) { print "$2\n"; }' | \ + grep -oE '#[0-9]+' || true) + if [[ -n "$PR_ISSUES" ]]; then + ALL_REFERENCED_ISSUES=$(printf '%s\n%s\n' "$ALL_REFERENCED_ISSUES" "$PR_ISSUES") + fi + done <<< "$OPEN_PRS" + + ISSUES=$(printf '%s\n' "$ALL_REFERENCED_ISSUES" | grep -oE '#[0-9]+' | sort -u || true) + CURRENT_PENDING=$(gh issue list --repo "${{ github.repository }}" --label "pending-release" --state all --json number --jq '.[].number' 2>/dev/null || true) + + gh label create "pending-release" \ + --color "fbca04" \ + --description "Fix in review, awaiting merge" \ + --repo ${{ github.repository }} 2>/dev/null || true if [[ -z "$ISSUES" ]]; then - echo "No linked issues found in PR" - exit 0 + echo "No linked issues found across open dev PRs" + else + for ISSUE in $ISSUES; do + NUM=${ISSUE#\#} + echo "Ensuring issue #$NUM has pending-release" + gh issue edit "$NUM" --add-label "pending-release" --repo "${{ github.repository }}" || true + done fi - for ISSUE in $ISSUES; do - NUM=${ISSUE#\#} - echo "Labeling issue #$NUM as pending-release" - - # Create label if doesn't exist - gh label create "pending-release" \ - --color "fbca04" \ - --description "Fix in review, awaiting merge" \ - --repo ${{ github.repository }} 2>/dev/null || true - - # Add label to issue - gh issue edit $NUM --add-label "pending-release" --repo ${{ github.repository }} || true + for NUM in $CURRENT_PENDING; do + if ! printf '%s\n' "$ISSUES" | grep -qx "#${NUM}"; then + echo "Removing stale pending-release from issue #$NUM" + gh issue edit "$NUM" --remove-label "pending-release" --repo "${{ github.repository }}" 2>/dev/null || true + fi done diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cd5a4eec..5b0e2220 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -93,12 +93,54 @@ jobs: run: | VERSION=$(jq -r '.version' package.json) RELEASE_BODY=$(gh release view "v${VERSION}" --repo "${{ github.repository }}" --json body --jq '.body' 2>/dev/null || echo "") - RELEASE_ISSUES=$(printf '%s\n' "$RELEASE_BODY" | \ + PREVIOUS_STABLE_TAG=$(git tag -l "v[0-9]*.[0-9]*.[0-9]" --sort=-v:refname | \ + grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | \ + grep -vx "v${VERSION}" | head -1 || echo "") + RANGE="${PREVIOUS_STABLE_TAG:+${PREVIOUS_STABLE_TAG}..HEAD~1}" + if [[ -z "$RANGE" ]]; then + RANGE="HEAD~50..HEAD~1" + fi + + COMMIT_TEXT=$(git log $RANGE --pretty=format:"%s%n%b" 2>/dev/null || true) + PR_CANDIDATES=$(printf '%s\n' "$COMMIT_TEXT" | \ + grep -oE "Merge pull request #[0-9]+|\\(#[0-9]+\\)" | \ + grep -oE "[0-9]+" | sort -u || true) + + PR_TEXT="" + for PR_NUM in $PR_CANDIDATES; do + DETAILS=$(gh pr view "$PR_NUM" --repo "${{ github.repository }}" --json title,body --jq '.title + "\n" + (.body // "")' 2>/dev/null || true) + if [[ -n "$DETAILS" ]]; then + PR_TEXT="${PR_TEXT}"$'\n'"${DETAILS}" + fi + done + + RELEASE_ISSUES_FROM_BODY=$(printf '%s\n' "$RELEASE_BODY" | \ perl -ne 'if (/(fixes|closes|resolves|refs?)(.*)/i) { print "$2\n"; }' | \ - grep -oE '#[0-9]+' | tr -d '#' | sort -u || true) - RESOLVED_ISSUES=$(printf '%s\n' "$RELEASE_BODY" | \ + grep -oE '#[0-9]+' | tr -d '#' || true) + RELEASE_ISSUES_FROM_COMMITS=$(printf '%s\n' "$COMMIT_TEXT" | \ + perl -ne 'if (/(fixes|closes|resolves|refs?)(.*)/i) { print "$2\n"; }' | \ + grep -oE '#[0-9]+' | tr -d '#' || true) + RELEASE_ISSUES_FROM_PRS=$(printf '%s\n' "$PR_TEXT" | \ + perl -ne 'if (/(fixes|closes|resolves|refs?)(.*)/i) { print "$2\n"; }' | \ + grep -oE '#[0-9]+' | tr -d '#' || true) + RELEASE_ISSUES=$(printf '%s\n%s\n%s\n' \ + "$RELEASE_ISSUES_FROM_BODY" \ + "$RELEASE_ISSUES_FROM_COMMITS" \ + "$RELEASE_ISSUES_FROM_PRS" | sort -u || true) + + RESOLVED_ISSUES_FROM_BODY=$(printf '%s\n' "$RELEASE_BODY" | \ perl -ne 'if (/(fixes|closes|resolves)(.*)/i) { print "$2\n"; }' | \ - grep -oE '#[0-9]+' | tr -d '#' | sort -u || true) + grep -oE '#[0-9]+' | tr -d '#' || true) + RESOLVED_ISSUES_FROM_COMMITS=$(printf '%s\n' "$COMMIT_TEXT" | \ + perl -ne 'if (/(fixes|closes|resolves)(.*)/i) { print "$2\n"; }' | \ + grep -oE '#[0-9]+' | tr -d '#' || true) + RESOLVED_ISSUES_FROM_PRS=$(printf '%s\n' "$PR_TEXT" | \ + perl -ne 'if (/(fixes|closes|resolves)(.*)/i) { print "$2\n"; }' | \ + grep -oE '#[0-9]+' | tr -d '#' || true) + RESOLVED_ISSUES=$(printf '%s\n%s\n%s\n' \ + "$RESOLVED_ISSUES_FROM_BODY" \ + "$RESOLVED_ISSUES_FROM_COMMITS" \ + "$RESOLVED_ISSUES_FROM_PRS" | sort -u || true) if [[ -z "$RELEASE_ISSUES" ]]; then echo "No release-scoped issues found for v${VERSION}"