Skip to content

Commit c56998e

Browse files
authored
Merge pull request #137 from mailtrap/MT-22748-implement-draft-release-workflow-for-mailtrap-nodejs
Implement draft-release.yml workflow
2 parents 897d263 + 062db10 commit c56998e

1 file changed

Lines changed: 160 additions & 6 deletions

File tree

.github/workflows/draft-release.yml

Lines changed: 160 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
# Placeholder draft-release workflow
1+
# Drafts new mailtrap-nodejs release: bumps the version, regenerates the changelog from
2+
# merged PRs, and opens a release PR against main.
23
#
34
name: Draft Node.js Release
45
on:
@@ -14,15 +15,168 @@ on:
1415
- major
1516
default: patch
1617

18+
concurrency:
19+
group: ${{ github.workflow }}
20+
cancel-in-progress: false
21+
22+
permissions:
23+
contents: write
24+
pull-requests: write
25+
1726
jobs:
1827
draft-release:
19-
name: Draft Node.js Release (placeholder)
28+
name: Draft mailtrap-nodejs Release
2029
runs-on: ubuntu-latest
2130
steps:
22-
- name: Echo inputs
31+
- uses: actions/checkout@v4
32+
with:
33+
fetch-depth: 0
34+
ref: main
35+
36+
- uses: actions/setup-node@v4
37+
with:
38+
node-version-file: .tool-versions
39+
40+
- name: Compute next version
41+
id: bump
42+
uses: actions/github-script@v7
43+
with:
44+
script: |
45+
const fs = require('fs');
46+
const current = JSON.parse(fs.readFileSync('package.json', 'utf8')).version;
47+
const [major, minor, patch] = current.split('.').map(Number);
48+
const next = {
49+
major: `${major + 1}.0.0`,
50+
minor: `${major}.${minor + 1}.0`,
51+
patch: `${major}.${minor}.${patch + 1}`,
52+
}[context.payload.inputs.bump_type];
53+
const tag = `v${next}`;
54+
55+
core.info(`Bumping ${current} -> ${next}`);
56+
core.setOutput('current', current);
57+
core.setOutput('next', next);
58+
core.setOutput('tag', tag);
59+
core.setOutput('branch', `release-${tag}`);
60+
61+
- name: Abort if tag already exists
62+
uses: actions/github-script@v7
63+
with:
64+
script: |
65+
const tag = '${{ steps.bump.outputs.tag }}';
66+
try {
67+
await github.rest.git.getRef({
68+
owner: context.repo.owner,
69+
repo: context.repo.repo,
70+
ref: `tags/${tag}`,
71+
});
72+
core.setFailed(`Tag ${tag} already exists`);
73+
} catch (error) {
74+
if (error.status !== 404) throw error;
75+
}
76+
77+
- name: Generate release notes, abort if nothing to release
78+
id: notes
79+
uses: actions/github-script@v7
80+
with:
81+
script: |
82+
const tag = '${{ steps.bump.outputs.tag }}';
83+
84+
// Detect the previous release tag so notes only cover PRs since then.
85+
const latest = await github.rest.repos.getLatestRelease({
86+
owner: context.repo.owner,
87+
repo: context.repo.repo,
88+
}).catch((error) => {
89+
if (error.status === 404) return null;
90+
throw error;
91+
});
92+
const previousTag = latest?.data.tag_name;
93+
94+
// Skip the rest of the release if nothing changed since the previous release.
95+
if (previousTag) {
96+
const { data: comparison } = await github.rest.repos.compareCommitsWithBasehead({
97+
owner: context.repo.owner,
98+
repo: context.repo.repo,
99+
basehead: `${previousTag}...main`,
100+
});
101+
if (comparison.total_commits === 0) {
102+
core.info(`No commits since ${previousTag}; nothing to release. Skipping.`);
103+
return;
104+
}
105+
}
106+
107+
const { data } = await github.rest.repos.generateReleaseNotes({
108+
owner: context.repo.owner,
109+
repo: context.repo.repo,
110+
tag_name: tag,
111+
...(previousTag ? { previous_tag_name: previousTag } : {}),
112+
});
113+
114+
core.setOutput('release_notes', data.body);
115+
116+
- name: Update version in package.json
117+
if: steps.notes.outputs.release_notes != ''
118+
run: yarn version --new-version "${{ steps.bump.outputs.next }}" --no-git-tag-version
119+
120+
- name: Prepend new section to CHANGELOG.md
121+
if: steps.notes.outputs.release_notes != ''
122+
uses: actions/github-script@v7
23123
env:
124+
NEXT: ${{ steps.bump.outputs.next }}
125+
RELEASE_NOTES: ${{ steps.notes.outputs.release_notes }}
126+
with:
127+
script: |
128+
const fs = require('fs');
129+
const version = process.env.NEXT;
130+
const date = new Date().toISOString().slice(0, 10);
131+
const releaseNotes = process.env.RELEASE_NOTES.trim();
132+
const entry = `## [${version}] - ${date}\n\n${releaseNotes}\n\n`;
133+
const original = fs.readFileSync('CHANGELOG.md', 'utf8');
134+
fs.writeFileSync('CHANGELOG.md', entry + original);
135+
136+
- name: Commit, push, open PR
137+
if: steps.notes.outputs.release_notes != ''
138+
env:
139+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24140
BUMP_TYPE: ${{ inputs.bump_type }}
141+
CURRENT: ${{ steps.bump.outputs.current }}
142+
NEXT: ${{ steps.bump.outputs.next }}
143+
TAG: ${{ steps.bump.outputs.tag }}
144+
BRANCH: ${{ steps.bump.outputs.branch }}
145+
RELEASE_NOTES: ${{ steps.notes.outputs.release_notes }}
25146
run: |
26-
echo "Placeholder draft-release workflow"
27-
echo "bump_type=$BUMP_TYPE"
28-
echo "No-op: implementation to follow."
147+
git config user.name "github-actions[bot]"
148+
git config user.email "github-actions[bot]@users.noreply.github.com"
149+
git checkout -b "$BRANCH"
150+
git add package.json CHANGELOG.md
151+
git commit -m "Release $TAG"
152+
git push -u origin "$BRANCH"
153+
body_file="$(mktemp)"
154+
{
155+
printf '## Automated `%s` bump from v%s to v%s\n\n' "$BUMP_TYPE" "$CURRENT" "$NEXT"
156+
printf '%s\n' "$RELEASE_NOTES"
157+
} > "$body_file"
158+
gh pr create \
159+
--base main \
160+
--head "$BRANCH" \
161+
--title "Release $TAG" \
162+
--body-file "$body_file"
163+
164+
- name: Create draft GitHub release
165+
if: steps.notes.outputs.release_notes != ''
166+
uses: actions/github-script@v7
167+
env:
168+
RELEASE_NOTES: ${{ steps.notes.outputs.release_notes }}
169+
with:
170+
script: |
171+
const tag = '${{ steps.bump.outputs.tag }}';
172+
const { data } = await github.rest.repos.createRelease({
173+
owner: context.repo.owner,
174+
repo: context.repo.repo,
175+
tag_name: tag,
176+
target_commitish: 'main',
177+
name: tag,
178+
body: process.env.RELEASE_NOTES,
179+
draft: true,
180+
prerelease: false,
181+
});
182+
core.info(`Draft release created: ${data.html_url}`);

0 commit comments

Comments
 (0)