Repository navigation
196 lines (177 loc) · 6.54 KB
/
Copy pathrelease.yml
File metadata and controls
196 lines (177 loc) · 6.54 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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
name: Release
run-name: "Release: ${{ inputs.package }} (${{ inputs.bump }})${{ inputs.dry-run && ' — dry run' || '' }}"
# Tags one action at a time. Tags are `<action>/vMAJOR.MINOR.PATCH`, so actions
# version independently and a bump to one doesn't churn the others.
#
# Adding a new action or reusable workflow means adding it to the `package`
# options below. CI fails if a top-level directory with a README.md is missing
# from that list.
on:
workflow_dispatch:
inputs:
package:
description: 'Package to release'
required: true
type: choice
options:
- approve-and-set-automerge
- cla-check
- scan-community-package
bump:
description: 'Version bump (first release of an action is always v1.0.0)'
required: true
type: choice
default: patch
options:
- patch
- minor
- major
dry-run:
description: 'Dry-Run: Resolve the version and render the notes without tagging'
type: boolean
default: false
permissions:
contents: read
concurrency:
# Serialize per action: two concurrent releases would resolve the same "next"
# version off the same latest tag. Never cancel — a half-run release is worse
# than a queued one.
group: release-${{ inputs.package }}
cancel-in-progress: false
jobs:
release:
name: Release ${{ inputs.package }}
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- name: Guard branch
if: github.ref != 'refs/heads/main'
run: |
echo "::error::Releases must be dispatched from main, got ${{ github.ref }}"
exit 1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# Full history + tags: the next version is derived from existing tags,
# and the changelog diffs against the previous one.
fetch-depth: 0
persist-credentials: false
- uses: pnpm/setup@84cb39b217b10273981911c288cd62326dc7c6d2 #v2.0.2
with:
install: true
version: 11.22.0
runtime: node@26
# Gate the tag on a green tree — a tag is immutable in consumers' pins.
- run: pnpm typecheck
- name: Resolve version
id: version
env:
PACKAGE: ${{ inputs.package }}
BUMP: ${{ inputs.bump }}
run: |
set -euo pipefail
# An action lives in <package>/action.yml. A reusable workflow lives
# in .github/workflows/<package>.yml, with its docs and helper actions
# under <package>/.
if [ -f "$PACKAGE/action.yml" ]; then
ref_path="$PACKAGE"
paths="$PACKAGE"
elif [ -f ".github/workflows/$PACKAGE.yml" ]; then
ref_path=".github/workflows/$PACKAGE.yml"
paths=".github/workflows/$PACKAGE.yml $PACKAGE"
else
echo "::error::neither $PACKAGE/action.yml nor .github/workflows/$PACKAGE.yml found"
exit 1
fi
prev_version="$(git tag --list "$PACKAGE/v*" \
| sed "s|^$PACKAGE/v||" \
| grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' \
| sort -V | tail -n1 || true)"
if [ -z "$prev_version" ]; then
next="1.0.0"
echo "No existing $PACKAGE tag — first release is v$next (bump input ignored)."
else
IFS=. read -r major minor patch <<<"$prev_version"
case "$BUMP" in
major) major=$((major + 1)); minor=0; patch=0 ;;
minor) minor=$((minor + 1)); patch=0 ;;
patch) patch=$((patch + 1)) ;;
*) echo "::error::unknown bump '$BUMP'"; exit 1 ;;
esac
next="$major.$minor.$patch"
fi
tag="$PACKAGE/v$next"
if git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then
echo "::error::$tag already exists"
exit 1
fi
{
echo "tag=$tag"
echo "version=$next"
echo "prev_tag=${prev_version:+$PACKAGE/v$prev_version}"
echo "ref_path=$ref_path"
echo "paths=$paths"
} >> "$GITHUB_OUTPUT"
- name: Render release notes
id: notes
env:
PACKAGE: ${{ inputs.package }}
VERSION: ${{ steps.version.outputs.version }}
PREV_TAG: ${{ steps.version.outputs.prev_tag }}
REF_PATH: ${{ steps.version.outputs.ref_path }}
PATHS: ${{ steps.version.outputs.paths }}
SHA: ${{ github.sha }}
run: |
set -euo pipefail
# PATHS is a space-separated list of pathspecs. Globbing is turned off
# so the shell only splits the words and git matches any glob against
# every tree in the range, including files renamed or removed since.
set -f
# shellcheck disable=SC2086
{
echo '```yaml'
echo "- uses: ${{ github.repository }}/$REF_PATH@$SHA # v$VERSION"
echo '```'
echo
echo "### Changes"
echo
if [ -n "$PREV_TAG" ]; then
git log "$PREV_TAG..HEAD" --no-merges --pretty='- %s (%h)' -- $PATHS > /tmp/commits
else
git log --no-merges --pretty='- %s (%h)' -- $PATHS > /tmp/commits
fi
if [ -s /tmp/commits ]; then
cat /tmp/commits
else
echo "- No commits touched \`$PACKAGE\` since ${PREV_TAG:-the start of the repo}."
fi
if [ -n "$PREV_TAG" ]; then
echo
echo "[Full diff](${{ github.server_url }}/${{ github.repository }}/compare/$PREV_TAG...$SHA)"
fi
} > /tmp/notes.md
cat /tmp/notes.md >> "$GITHUB_STEP_SUMMARY"
- name: Create tag and release
if: '!inputs.dry-run'
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.version.outputs.tag }}
PACKAGE: ${{ inputs.package }}
VERSION: ${{ steps.version.outputs.version }}
SHA: ${{ github.sha }}
run: |
set -euo pipefail
gh release create "$TAG" \
--target "$SHA" \
--title "$PACKAGE v$VERSION" \
--notes-file /tmp/notes.md
- name: Summarize
env:
TAG: ${{ steps.version.outputs.tag }}
run: |
if [ "${{ inputs.dry-run }}" = "true" ]; then
echo "::notice::Dry run — would have released $TAG"
else
echo "::notice::Released $TAG"
fi