-
Notifications
You must be signed in to change notification settings - Fork 0
163 lines (144 loc) · 6.1 KB
/
release.yml
File metadata and controls
163 lines (144 loc) · 6.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
name: Release
on:
pull_request:
types: [closed]
branches: [main]
permissions:
contents: write # gh release create (creates tag + release)
# + listTags via API. That's all GITHUB_TOKEN needs;
# labels are read from the event payload, not the API.
# Serialize release runs so two PRs merging back-to-back don't both
# compute the same next version and race on `gh release create`.
#
# GitHub Actions concurrency guarantees at most ONE running + ONE pending
# run per group. If a third run is queued while one is running and one is
# pending, the third cancels the pending one. For jc-cli's merge cadence
# (single-digit PRs/day, ~3-5min release runtime) this is a theoretical
# edge case — if it ever bites, the cancelled run shows up in the Actions
# tab and can be re-triggered manually. If it becomes a real problem,
# swap this block for a proper queueing action.
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
jobs:
# =================================================================
# == Parse the PR's release label
# == Expects exactly one of: major, minor, patch, no version
# == Reads directly from the webhook event payload (no API call),
# == same pattern as internal common-github-actions/create_release.
# =================================================================
Check-PR-Labels:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
outputs:
RELEASE_TYPE: ${{ steps.labels.outputs.RELEASE_TYPE }}
steps:
- name: Parse PR labels
id: labels
uses: actions/github-script@v9
with:
script: |
const valid = ['major', 'minor', 'patch', 'no version'];
const labels = context.payload.pull_request.labels.map(l => l.name);
core.info(`Labels on PR: ${labels.join(', ') || '(none)'}`);
const matches = labels.filter(name => valid.includes(name));
if (matches.length !== 1) {
core.setFailed(
`Expected exactly one of: ${valid.map(v => `'${v}'`).join(', ')}. Found ${matches.length}.`
);
return;
}
// Normalize "no version" -> "none" so the downstream
// job-level `if` expression is shell-safe (no space).
const type = matches[0] === 'no version' ? 'none' : matches[0];
core.info(`Release type: ${type}`);
core.setOutput('RELEASE_TYPE', type);
# =================================================================
# == Compute the next semver from the latest tag + the label
# == Uses the semver npm package (same as internal
# == common-github-actions/create_release) to avoid fragile bash
# == arithmetic on version strings.
# =================================================================
Compute-Version:
needs: Check-PR-Labels
if: needs.Check-PR-Labels.outputs.RELEASE_TYPE != 'none'
runs-on: ubuntu-latest
outputs:
VERSION: ${{ steps.bump.outputs.VERSION }}
steps:
- name: Install semver
run: npm install semver
- name: Compute next version
id: bump
uses: actions/github-script@v9
env:
RELEASE_TYPE: ${{ needs.Check-PR-Labels.outputs.RELEASE_TYPE }}
with:
script: |
const semver = require('semver');
const releaseType = process.env.RELEASE_TYPE;
// Pull all tags, keep only valid semver ones.
// semver.valid() rejects things like v1.2.3.4 or bare branches
// that `git tag --list 'v*.*.*'` would otherwise match.
const tags = await github.paginate(
github.rest.repos.listTags,
{ owner: context.repo.owner, repo: context.repo.repo }
);
const semverTags = tags
.map(t => t.name)
.filter(name => semver.valid(name))
.map(name => semver.parse(semver.clean(name)))
.sort(semver.rcompare);
const latest = semverTags.length > 0
? semverTags[0]
: semver.parse('0.0.0');
const next = semver.inc(latest, releaseType);
if (!next) {
core.setFailed(
`Unable to compute next version (latest=${latest}, releaseType=${releaseType})`
);
return;
}
// No `v` prefix on the tag: the TheJumpCloud org has a
// ruleset "Version Tag Protections" that forbids creating
// refs matching refs/tags/v* (and refs/tags/proto/v*),
// which the GITHUB_TOKEN can't bypass. Existing release
// tags (1.15.0, 1.16.0) already use the no-prefix
// convention, so we match it.
const tag = String(next);
core.info(`Latest semver tag: ${latest}`);
core.info(`Next version: ${tag}`);
core.setOutput('VERSION', tag);
# =================================================================
# == Build, test, and publish the release
# =================================================================
Release:
needs: [Check-PR-Labels, Compute-Version]
if: needs.Check-PR-Labels.outputs.RELEASE_TYPE != 'none'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.merge_commit_sha }}
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Run tests
run: make test
- name: Build release archives
run: make release VERSION=${{ needs.Compute-Version.outputs.VERSION }}
- name: Create tag + GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ needs.Compute-Version.outputs.VERSION }}"
SHA="${{ github.event.pull_request.merge_commit_sha }}"
gh release create "$VERSION" \
--target "$SHA" \
--title "$VERSION" \
--generate-notes \
dist/*.tar.gz \
dist/*.zip \
dist/checksums.txt