Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/reusable-dependabot-auto-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ on:
description: "Pull request URL (e.g. https://github.com/OWNER/REPO/pull/123)"
required: true
type: string
approve_pr:
description: "Whether to auto-approve the PR (required only if branch protection requires approvals)"
required: false
default: false
type: boolean
merge_method:
description: "Merge method (squash|merge|rebase)"
required: false
Expand Down Expand Up @@ -72,6 +77,17 @@ jobs:
set -euo pipefail
gh pr update-branch --repo "$GH_REPO" --rebase "$PR_URL" || true

- name: Approve PR (optional)
if: inputs.approve_pr
shell: bash
env:
PR_URL: ${{ inputs.pr_url }}
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
gh pr review --repo "$GH_REPO" --approve "$PR_URL" --body "Auto-approved Dependabot PR." || true

- name: Enable auto-merge
shell: bash
env:
Expand Down
18 changes: 18 additions & 0 deletions .github/workflows/reusable-dependabot-refresh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ on:
required: false
default: true
type: boolean
approve_prs:
description: "Whether to auto-approve Dependabot PRs (use only if branch protection requires approvals)"
required: false
default: false
type: boolean
enable_auto_merge:
description: "Whether to enable auto-merge on PRs (true recommended)"
required: false
Expand Down Expand Up @@ -109,6 +114,19 @@ jobs:
gh pr update-branch --repo "$GH_REPO" --rebase "$pr" || true
done < pr_urls.txt

- name: Approve PRs (optional)
if: steps.list.outputs.count != '0' && inputs.approve_prs
shell: bash
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
set -euo pipefail
while IFS= read -r pr; do
echo "Approving: $pr"
gh pr review --repo "$GH_REPO" --approve "$pr" --body "Auto-approved Dependabot PR." || true
done < pr_urls.txt

- name: Enable auto-merge
if: steps.list.outputs.count != '0' && inputs.enable_auto_merge
shell: bash
Expand Down
257 changes: 257 additions & 0 deletions .github/workflows/reusable-dependabot-release-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
##
# Reusable: Create release PR after Dependabot merge to develop
#
# Goal:
# - When develop receives commits from a Dependabot PR, automatically prepare a release PR into main.
# - Bumps patch version (x.y.(z+1)) in package.json and creates a release branch release/vX.Y.Z.
# - Opens (or reuses) a PR from release/vX.Y.Z -> main.
# - Optionally enables auto-merge.
#
# Why this approach:
# - Your existing prerelease/release pipelines are PR/main based.
# - Creating a release PR is the cleanest way to hook into that system.
#
# Caller example:
# name: Dependabot (create release PR)
# on:
# push:
# branches: [develop]
# jobs:
# create-release-pr:
# permissions:
# contents: write
# pull-requests: write
# uses: winccoa-tools-pack/.github/.github/workflows/reusable-dependabot-release-pr.yml@main
# secrets: inherit
# with:
# base_branch: develop
# target_branch: main
# enable_auto_merge: false
#

name: "Reusable: Dependabot → Release PR"

on:
workflow_call:
inputs:
base_branch:
description: "Branch to watch for Dependabot merges (usually develop)"
required: false
default: "develop"
type: string
target_branch:
description: "Stable branch to release into (usually main)"
required: false
default: "main"
type: string
version_file:
description: "Version file (default: package.json)"
required: false
default: "package.json"
type: string
version_key:
description: "JSON key containing version (default: version)"
required: false
default: "version"
type: string
enable_auto_merge:
description: "If true, enable auto-merge on the created release PR"
required: false
default: false
type: boolean
auto_merge_method:
description: "Auto-merge method (squash|merge|rebase)"
required: false
default: "squash"
type: string
secrets:
REPO_ADMIN_TOKEN:
description: "PAT with repo access (recommended). Helps ensure PR creation triggers downstream workflows reliably."
required: false

permissions: {}

concurrency:
group: dependabot-release-pr-${{ github.repository }}-${{ inputs.base_branch }}
cancel-in-progress: false

jobs:
create-release-pr:
name: Create release PR (Dependabot)
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write

steps:
- name: Checkout base branch
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ inputs.base_branch }}
token: ${{ secrets.REPO_ADMIN_TOKEN != '' && secrets.REPO_ADMIN_TOKEN || github.token }}

- name: Configure git identity
shell: bash
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

- name: Detect whether this push includes a Dependabot PR
id: detect
shell: bash
env:
GH_TOKEN: ${{ secrets.REPO_ADMIN_TOKEN != '' && secrets.REPO_ADMIN_TOKEN || github.token }}
GH_REPO: ${{ github.repository }}
SHA: ${{ github.sha }}
run: |
set -euo pipefail

echo "Checking associated PR(s) for commit: $SHA"

# Find PRs associated with this commit.
# Accept header kept permissive for compatibility.
prs_json=$(gh api \
-H "Accept: application/vnd.github+json" \
"/repos/$GH_REPO/commits/$SHA/pulls")

count=$(echo "$prs_json" | jq 'length')
echo "Associated PR count: $count"

if [ "$count" -eq 0 ]; then
echo "dependabot=false" >> "$GITHUB_OUTPUT"
exit 0
fi

# True if ANY associated PR author is dependabot[bot]
is_dep=$(echo "$prs_json" | jq -r 'any(.[]; .user.login == "dependabot[bot]")')
echo "dependabot=$is_dep" >> "$GITHUB_OUTPUT"

if [ "$is_dep" != "true" ]; then
echo "No Dependabot PR associated with this commit."
exit 0
fi

pr_list=$(echo "$prs_json" | jq -r '.[].html_url' | tr '\n' ' ')
echo "Dependabot PR(s): $pr_list"

- name: Stop (not a Dependabot change)
if: steps.detect.outputs.dependabot != 'true'
run: echo "No Dependabot-associated PR detected for this push; skipping release PR creation."

- name: Compute next patch version
if: steps.detect.outputs.dependabot == 'true'
id: ver
shell: bash
env:
VERSION_FILE: ${{ inputs.version_file }}
VERSION_KEY: ${{ inputs.version_key }}
run: |
set -euo pipefail

if [ ! -f "$VERSION_FILE" ]; then
echo "ERROR: version_file not found: $VERSION_FILE" >&2
exit 1
fi

base=$(node -e "const fs=require('fs'); const p=process.env.VERSION_FILE; const k=process.env.VERSION_KEY; const obj=JSON.parse(fs.readFileSync(p,'utf8')); const v=String(obj[k]||'').trim(); if(!/^\d+\.\d+\.\d+$/.test(v)) { console.error('Invalid version: '+v); process.exit(1);} console.log(v);")
IFS='.' read -r major minor patch <<< "$base"
new_patch=$((patch+1))
next="$major.$minor.$new_patch"

echo "base=$base" >> "$GITHUB_OUTPUT"
echo "next=$next" >> "$GITHUB_OUTPUT"
echo "release_branch=release/v$next" >> "$GITHUB_OUTPUT"
echo "Next version: $next"

- name: Skip if a release PR already exists
if: steps.detect.outputs.dependabot == 'true'
id: existing
shell: bash
env:
GH_TOKEN: ${{ secrets.REPO_ADMIN_TOKEN != '' && secrets.REPO_ADMIN_TOKEN || github.token }}
GH_REPO: ${{ github.repository }}
TARGET_BRANCH: ${{ inputs.target_branch }}
run: |
set -euo pipefail

# If any open PR from a release/v* branch into target exists, do not create a second one.
prs=$(gh pr list --repo "$GH_REPO" --state open --base "$TARGET_BRANCH" --json number,headRefName,url)
any=$(echo "$prs" | jq -r 'map(select(.headRefName | startswith("release/v"))) | length')

if [ "$any" -gt 0 ]; then
url=$(echo "$prs" | jq -r 'map(select(.headRefName | startswith("release/v"))) | .[0].url')
echo "found=true" >> "$GITHUB_OUTPUT"
echo "url=$url" >> "$GITHUB_OUTPUT"
echo "Existing open release PR found: $url"
else
echo "found=false" >> "$GITHUB_OUTPUT"
fi

- name: Create or update release branch (version bump)
if: steps.detect.outputs.dependabot == 'true' && steps.existing.outputs.found != 'true'
shell: bash
env:
GH_TOKEN: ${{ secrets.REPO_ADMIN_TOKEN != '' && secrets.REPO_ADMIN_TOKEN || github.token }}
VERSION_FILE: ${{ inputs.version_file }}
VERSION_KEY: ${{ inputs.version_key }}
NEXT: ${{ steps.ver.outputs.next }}
RELEASE_BRANCH: ${{ steps.ver.outputs.release_branch }}
BASE_BRANCH: ${{ inputs.base_branch }}
run: |
set -euo pipefail

git fetch origin --prune

# Create branch from current base branch state
git checkout -B "$RELEASE_BRANCH" "origin/$BASE_BRANCH"

# Update version in JSON file
node -e "const fs=require('fs'); const f=process.env.VERSION_FILE; const k=process.env.VERSION_KEY; const next=process.env.NEXT; const obj=JSON.parse(fs.readFileSync(f,'utf8')); obj[k]=next; fs.writeFileSync(f, JSON.stringify(obj,null,2)+'\n');"

git add "$VERSION_FILE"
git commit -m "chore(release): v$NEXT"

git push -u origin "$RELEASE_BRANCH"

- name: Open release PR
if: steps.detect.outputs.dependabot == 'true' && steps.existing.outputs.found != 'true'
shell: bash
env:
GH_TOKEN: ${{ secrets.REPO_ADMIN_TOKEN != '' && secrets.REPO_ADMIN_TOKEN || github.token }}
GH_REPO: ${{ github.repository }}
TARGET_BRANCH: ${{ inputs.target_branch }}
RELEASE_BRANCH: ${{ steps.ver.outputs.release_branch }}
NEXT: ${{ steps.ver.outputs.next }}
run: |
set -euo pipefail

title="chore(release): v$NEXT"
body=$(cat <<EOF
This PR was created automatically after a Dependabot PR was merged into \`${{ inputs.base_branch }}\`.

- Version bump: \`${{ inputs.version_file }}\` → \`$NEXT\` (patch +0.0.1)
- Branch: \`$RELEASE_BRANCH\`

Once this PR is merged, the normal prerelease/release pipelines should run.
EOF
)

pr_url=$(gh pr create \
--repo "$GH_REPO" \
--base "$TARGET_BRANCH" \
--head "$RELEASE_BRANCH" \
--title "$title" \
--body "$body")

echo "Created release PR: $pr_url"

if [ "${{ inputs.enable_auto_merge }}" = "true" ]; then
method="${{ inputs.auto_merge_method }}"
case "$method" in
squash|merge|rebase) ;;
*) echo "Invalid auto_merge_method: '$method'" >&2; exit 2;;
esac
gh pr merge --repo "$GH_REPO" --auto --"$method" "$pr_url" || true
fi