name: Upstream merge
on:
schedule:
- cron: "0 0 1 * *" # monthly on the 1st at 00:00 UTC
workflow_dispatch:
permissions:
contents: write
pull-requests: write
issues: write
jobs:
merge-upstream:
runs-on: ubuntu-latest
steps:
- name: Checkout (full history)
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set bot identity
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Ensure upstream remote
run: |
if ! git remote | grep -q '^upstream$'; then
git remote add upstream https://github.com/cyanheads/mcp-ts-template.git
fi
git fetch upstream
- name: Create merge branch and merge upstream (no commit)
id: do_merge
run: |
BR="chore/upstream-merge-$(date +%Y%m%d)"
echo "branch=$BR" >> $GITHUB_OUTPUT
git switch -c "$BR"
# --no-commit keeps changes staged but uncommitted so the next action can commit them
# Your .gitattributes should minimize conflicts.
set -e
git merge --no-commit upstream/main || true
# Detect conflicts: non-empty 'git ls-files -u' means unmerged entries
if [ -n "$(git ls-files -u)" ]; then
echo "conflicts=true" >> $GITHUB_OUTPUT
else
echo "conflicts=false" >> $GITHUB_OUTPUT
fi
# Detect if there are actual changes vs main (fast-forward or identical)
if git diff --quiet origin/main...; then
echo "has_changes=false" >> $GITHUB_OUTPUT
else
echo "has_changes=true" >> $GITHUB_OUTPUT
fi
- name: Open issue on conflicts and exit
if: steps.do_merge.outputs.conflicts == 'true'
uses: actions/github-script@v7
with:
script: |
const title = "Upstream merge requires manual conflict resolution";
const body = [
"The scheduled upstream merge found conflicts.",
"",
"- Branch: `" + process.env.BRANCH + "`",
"- Action did not create a PR to avoid committing conflicts.",
"",
"Steps:",
"1) Pull the branch locally",
"2) Resolve conflicts (your .gitattributes should protect custom paths)",
"3) Push and open a PR"
].join("\n");
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title,
body
});
env:
BRANCH: ${{ steps.do_merge.outputs.branch }}
- name: Create PR (only if merge had no conflicts and there are changes)
if: steps.do_merge.outputs.conflicts == 'false' && steps.do_merge.outputs.has_changes == 'true'
uses: peter-evans/create-pull-request@v6
with:
branch: ${{ steps.do_merge.outputs.branch }}
title: "Merge latest from cyanheads/mcp-ts-template:main"
body: |
Automated monthly merge from template upstream.
- Source: `cyanheads/mcp-ts-template` @ `main`
- This respects `.gitattributes` path protections.
commit-message: "chore: merge upstream template"
labels: dependencies, upstream
- name: Nothing to do
if: steps.do_merge.outputs.conflicts == 'false' && steps.do_merge.outputs.has_changes == 'false'
run: echo "No upstream changes."