Skip to content

Commit fc1fd70

Browse files
committed
ci: add changeset bot for PR comments
- Add /changeset command to create changesets via PR comments - Update changeset-check to post helpful instructions - Supports: /changeset patch|minor|major [packages]
1 parent 9d8821f commit fc1fd70

2 files changed

Lines changed: 220 additions & 24 deletions

File tree

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
name: Changeset Bot
2+
3+
on:
4+
issue_comment:
5+
types: [created]
6+
7+
jobs:
8+
changeset:
9+
name: Create Changeset
10+
if: |
11+
github.event.issue.pull_request &&
12+
startsWith(github.event.comment.body, '/changeset')
13+
runs-on: ubuntu-latest
14+
permissions:
15+
contents: write
16+
pull-requests: write
17+
18+
steps:
19+
- name: Get PR branch
20+
id: pr
21+
uses: actions/github-script@v7
22+
with:
23+
script: |
24+
const pr = await github.rest.pulls.get({
25+
owner: context.repo.owner,
26+
repo: context.repo.repo,
27+
pull_number: context.issue.number
28+
});
29+
core.setOutput('branch', pr.data.head.ref);
30+
core.setOutput('title', pr.data.title);
31+
32+
- name: Checkout PR branch
33+
uses: actions/checkout@v4
34+
with:
35+
ref: ${{ steps.pr.outputs.branch }}
36+
fetch-depth: 0
37+
38+
- name: Install pnpm
39+
uses: pnpm/action-setup@v4
40+
41+
- name: Setup Node.js
42+
uses: actions/setup-node@v4
43+
with:
44+
node-version: '20'
45+
cache: 'pnpm'
46+
47+
- name: Install dependencies
48+
run: pnpm install --frozen-lockfile
49+
50+
- name: Parse command
51+
id: parse
52+
uses: actions/github-script@v7
53+
with:
54+
script: |
55+
const comment = context.payload.comment.body.trim();
56+
const lines = comment.split('\n');
57+
const firstLine = lines[0].trim();
58+
59+
// Parse: /changeset [patch|minor|major] [package1,package2]
60+
const match = firstLine.match(/^\/changeset\s+(patch|minor|major)(?:\s+(.+))?$/i);
61+
62+
if (!match) {
63+
core.setFailed('Invalid format. Use: /changeset <patch|minor|major> [package1,package2]');
64+
return;
65+
}
66+
67+
const bump = match[1].toLowerCase();
68+
const packagesArg = match[2];
69+
70+
// Get summary from rest of comment or PR title
71+
let summary = lines.slice(1).join('\n').trim();
72+
if (!summary) {
73+
summary = '${{ steps.pr.outputs.title }}';
74+
}
75+
76+
core.setOutput('bump', bump);
77+
core.setOutput('packages', packagesArg || '');
78+
core.setOutput('summary', summary);
79+
80+
- name: Get changed packages
81+
id: packages
82+
run: |
83+
if [ -n "${{ steps.parse.outputs.packages }}" ]; then
84+
echo "packages=${{ steps.parse.outputs.packages }}" >> $GITHUB_OUTPUT
85+
else
86+
# Auto-detect from changed files
87+
CHANGED=$(git diff --name-only origin/main...HEAD | grep -E "^(ts-cache|storages)/" | cut -d'/' -f1-2 | sort -u | tr '\n' ',' | sed 's/,$//')
88+
if [ -z "$CHANGED" ]; then
89+
CHANGED="ts-cache"
90+
fi
91+
echo "packages=$CHANGED" >> $GITHUB_OUTPUT
92+
fi
93+
94+
- name: Create changeset
95+
run: |
96+
BUMP="${{ steps.parse.outputs.bump }}"
97+
PACKAGES="${{ steps.packages.outputs.packages }}"
98+
SUMMARY="${{ steps.parse.outputs.summary }}"
99+
100+
# Generate random filename
101+
FILENAME=".changeset/$(echo $RANDOM | md5sum | head -c 12).md"
102+
103+
# Map folder names to package names
104+
get_package_name() {
105+
case "$1" in
106+
ts-cache) echo "@node-ts-cache/core" ;;
107+
storages/lru) echo "@node-ts-cache/lru-storage" ;;
108+
storages/redis) echo "@node-ts-cache/redis-storage" ;;
109+
storages/redisio) echo "@node-ts-cache/ioredis-storage" ;;
110+
storages/node-cache) echo "@node-ts-cache/node-cache-storage" ;;
111+
storages/lru-redis) echo "@node-ts-cache/lru-redis-storage" ;;
112+
*) echo "$1" ;;
113+
esac
114+
}
115+
116+
# Create changeset content
117+
echo "---" > "$FILENAME"
118+
119+
IFS=',' read -ra PKGS <<< "$PACKAGES"
120+
for pkg in "${PKGS[@]}"; do
121+
pkg=$(echo "$pkg" | xargs) # trim whitespace
122+
pkg_name=$(get_package_name "$pkg")
123+
echo "\"$pkg_name\": $BUMP" >> "$FILENAME"
124+
done
125+
126+
echo "---" >> "$FILENAME"
127+
echo "" >> "$FILENAME"
128+
echo "$SUMMARY" >> "$FILENAME"
129+
130+
echo "Created changeset:"
131+
cat "$FILENAME"
132+
133+
- name: Commit and push
134+
run: |
135+
git config --global user.name "github-actions[bot]"
136+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
137+
git add .changeset/
138+
git commit -m "chore: add changeset"
139+
git push
140+
141+
- name: Comment success
142+
uses: actions/github-script@v7
143+
with:
144+
script: |
145+
await github.rest.issues.createComment({
146+
owner: context.repo.owner,
147+
repo: context.repo.repo,
148+
issue_number: context.issue.number,
149+
body: `✅ Changeset created!\n\n**Type:** ${{ steps.parse.outputs.bump }}\n**Packages:** ${{ steps.packages.outputs.packages }}\n**Summary:** ${{ steps.parse.outputs.summary }}`
150+
});
151+
152+
- name: Comment failure
153+
if: failure()
154+
uses: actions/github-script@v7
155+
with:
156+
script: |
157+
await github.rest.issues.createComment({
158+
owner: context.repo.owner,
159+
repo: context.repo.repo,
160+
issue_number: context.issue.number,
161+
body: `❌ Failed to create changeset. Please check the [workflow run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.\n\n**Usage:**\n\`\`\`\n/changeset patch\n/changeset minor\n/changeset major @node-ts-cache/core,@node-ts-cache/lru-storage\n\`\`\`\n\nAdd a summary on the next line:\n\`\`\`\n/changeset patch\nFixed a bug in cache expiration\n\`\`\``
162+
});

.github/workflows/changeset-check.yml

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,44 +9,78 @@ jobs:
99
check:
1010
name: Check for changeset
1111
runs-on: ubuntu-latest
12+
permissions:
13+
pull-requests: write
1214

1315
steps:
1416
- name: Checkout repository
1517
uses: actions/checkout@v4
1618
with:
1719
fetch-depth: 0
1820

19-
- name: Install pnpm
20-
uses: pnpm/action-setup@v4
21-
22-
- name: Setup Node.js
23-
uses: actions/setup-node@v4
24-
with:
25-
node-version: '20'
26-
cache: 'pnpm'
27-
28-
- name: Install dependencies
29-
run: pnpm install --frozen-lockfile
30-
3121
- name: Check for changeset
22+
id: check
3223
run: |
33-
# Skip check for release PRs and bot PRs
24+
# Skip check for release PRs
3425
if [[ "${{ github.head_ref }}" == "changeset-release/"* ]]; then
35-
echo "Skipping changeset check for release PR"
26+
echo "skip=true" >> $GITHUB_OUTPUT
3627
exit 0
3728
fi
3829
39-
# Check if any changeset files exist (excluding config.json)
40-
CHANGESETS=$(find .changeset -name "*.md" -type f 2>/dev/null | wc -l)
30+
# Check if any changeset files exist (excluding config.json and README)
31+
CHANGESETS=$(find .changeset -name "*.md" -type f ! -name "README.md" 2>/dev/null | wc -l)
4132
4233
if [ "$CHANGESETS" -eq 0 ]; then
43-
echo "::error::No changeset found! Please run 'pnpm changeset' to create one."
44-
echo ""
45-
echo "A changeset describes what changes you made and how they affect the packages."
46-
echo ""
47-
echo "Run: pnpm changeset"
48-
echo "Then select the packages you changed and the type of change (patch/minor/major)."
49-
exit 1
34+
echo "found=false" >> $GITHUB_OUTPUT
35+
exit 0
5036
fi
5137
52-
echo "Found $CHANGESETS changeset(s)"
38+
echo "found=true" >> $GITHUB_OUTPUT
39+
echo "count=$CHANGESETS" >> $GITHUB_OUTPUT
40+
41+
- name: Post or update comment
42+
if: steps.check.outputs.skip != 'true'
43+
uses: actions/github-script@v7
44+
with:
45+
script: |
46+
const found = '${{ steps.check.outputs.found }}' === 'true';
47+
const count = '${{ steps.check.outputs.count }}';
48+
49+
const marker = '<!-- changeset-check-comment -->';
50+
51+
let body;
52+
if (found) {
53+
body = `${marker}\n✅ **Changeset found** (${count} changeset file(s))\n\nThis PR will be included in the next release.`;
54+
} else {
55+
body = `${marker}\n⚠️ **No changeset found**\n\nThis PR doesn't have a changeset. If this PR should trigger a release, please add one:\n\n### Option 1: Comment command\nComment on this PR:\n\`\`\`\n/changeset patch\nYour change description here\n\`\`\`\n\nOr specify packages:\n\`\`\`\n/changeset minor @node-ts-cache/core\nAdded new caching feature\n\`\`\`\n\n### Option 2: Manual\n\`\`\`bash\npnpm changeset\ngit add .changeset && git commit -m "chore: add changeset" && git push\n\`\`\`\n\n---\n*If this PR doesn't need a release (e.g., docs, CI changes), you can ignore this message.*`;
56+
}
57+
58+
// Find existing comment
59+
const comments = await github.rest.issues.listComments({
60+
owner: context.repo.owner,
61+
repo: context.repo.repo,
62+
issue_number: context.issue.number
63+
});
64+
65+
const existing = comments.data.find(c => c.body.includes(marker));
66+
67+
if (existing) {
68+
await github.rest.issues.updateComment({
69+
owner: context.repo.owner,
70+
repo: context.repo.repo,
71+
comment_id: existing.id,
72+
body
73+
});
74+
} else {
75+
await github.rest.issues.createComment({
76+
owner: context.repo.owner,
77+
repo: context.repo.repo,
78+
issue_number: context.issue.number,
79+
body
80+
});
81+
}
82+
83+
// Fail the check if no changeset found
84+
if (!found) {
85+
core.setFailed('No changeset found. See comment for instructions.');
86+
}

0 commit comments

Comments
 (0)