1111 # pull_request_target event is required for autolabeler to support PRs from forks
1212 # pull_request_target:
1313 # types: [opened, reopened, synchronize]
14+ workflow_dispatch :
15+ schedule :
16+ - cron : ' 0 9 1,15 * *' # 1st and 15th of each month (~every 2 weeks)
1417
1518permissions :
1619 contents : read
@@ -25,12 +28,68 @@ jobs:
2528 pull-requests : write
2629 runs-on : ubuntu-latest
2730 steps :
28- # Updates draft release notes when PRs are opened/updated against main
29- # Publishes the release on push to main (including merged PRs)
31+ # On push to main, check whether the merged PR carries a significant label.
32+ # Chore-only merges (e.g. Renovate dependency bumps) update the draft but
33+ # do not trigger an immediate release.
34+ - name : Check merged PR significance
35+ id : pr-check
36+ if : github.event_name == 'push'
37+ uses : actions/github-script@v7
38+ with :
39+ script : |
40+ const prs = await github.rest.repos.listPullRequestsAssociatedWithCommit({
41+ owner: context.repo.owner,
42+ repo: context.repo.repo,
43+ commit_sha: context.sha,
44+ });
45+ if (prs.data.length === 0) {
46+ core.setOutput('publish', 'false');
47+ return;
48+ }
49+ const labels = prs.data[0].labels.map(l => l.name);
50+ const significant = ['feature', 'enhancement', 'fix', 'bugfix', 'bug', 'security', 'breaking', 'breaking-change'];
51+ core.setOutput('publish', labels.some(l => significant.includes(l)) ? 'true' : 'false');
52+
53+ # On the scheduled run, skip publishing if nothing has merged since the last release.
54+ # This avoids creating an empty or chore-only release when there has been no activity.
55+ # workflow_dispatch bypasses this check intentionally — a manual trigger always publishes.
56+ - name : Check for unreleased changes (schedule only)
57+ id : schedule-check
58+ if : github.event_name == 'schedule'
59+ uses : actions/github-script@v7
60+ with :
61+ script : |
62+ const releases = await github.rest.repos.listReleases({
63+ owner: context.repo.owner,
64+ repo: context.repo.repo,
65+ per_page: 10,
66+ });
67+ const latest = releases.data.find(r => !r.draft && !r.prerelease);
68+ const since = latest ? latest.published_at : null;
69+
70+ const prs = await github.rest.pulls.list({
71+ owner: context.repo.owner,
72+ repo: context.repo.repo,
73+ state: 'closed',
74+ base: 'main',
75+ sort: 'updated',
76+ direction: 'desc',
77+ per_page: 100,
78+ });
79+ const merged = prs.data.filter(pr =>
80+ pr.merged_at && (!since || new Date(pr.merged_at) > new Date(since))
81+ );
82+ core.setOutput('has_changes', merged.length > 0 ? 'true' : 'false');
83+
84+ # Updates draft release notes when PRs are opened/updated against main.
85+ # Publishes when:
86+ # - triggered manually (workflow_dispatch)
87+ # - the biweekly schedule fires AND there are unreleased merged PRs
88+ # - a significant (non-chore) PR was merged to main
3089 - uses : release-drafter/release-drafter@5de93583980a40bd78603b6dfdcda5b4df377b32 # v7
3190 with :
3291 commitish : main
33- publish : ${{ github.event_name == 'push ' }}
92+ publish : ${{ github.event_name == 'workflow_dispatch' || steps.pr-check.outputs.publish == 'true' || steps.schedule-check.outputs.has_changes == 'true ' }}
3493 prerelease : false
3594 env :
3695 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
0 commit comments