-
Notifications
You must be signed in to change notification settings - Fork 0
304 lines (274 loc) · 11 KB
/
release.yml
File metadata and controls
304 lines (274 loc) · 11 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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
name: Release
on:
push:
branches:
- main
workflow_dispatch:
inputs:
bump:
description: "Version bump level"
required: true
default: minor
type: choice
options:
- patch
- minor
- major
permissions:
contents: write
concurrency:
group: release
cancel-in-progress: false
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Go
uses: actions/setup-go@v5
with:
# Pin to the major+minor declared in go.mod so `go mod vendor`
# and the release build succeed against the source tree.
go-version-file: go.mod
- name: Set up pnpm
# Pinned to commit SHA per supply-chain hygiene — third-party
# actions can be rewritten under a moving tag. Bump by re-running
# `gh api repos/pnpm/action-setup/git/refs/tags/v4`.
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
with:
# Pinned to ui/package.json packageManager so a bumped lockfile
# doesn't desync from the toolchain CI runs the build with.
version: 10.33.0
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
cache-dependency-path: ui/pnpm-lock.yaml
- name: Build UI bundle
# Populates internal/serve/dist/ from ui/dist/ via rsync so the
# `//go:embed all:dist` directive in internal/serve/assets.go has
# a non-empty match. Without this, both `go test ./...` and the
# cross-compiled binary builds below fail at compile time. The
# populated dist is naturally bundled into the air-gapped source
# tarball later (the tar excludes top-level ./dist staging only,
# not internal/serve/dist).
run: make ui
- name: Run tests
# sqlite_fts5: mattn/go-sqlite3 only compiles FTS5 in when this
# tag is set. internal/serve/store's schema uses
# `CREATE VIRTUAL TABLE … USING fts5(…)` — without the tag,
# OpenCostStore() fails at runtime with "no such module: fts5"
# and ~25 store/serve tests blow up.
# -race: Go's data-race detector. Gates the release on a clean
# dynamic-analysis pass per OpenSSF Best Practices
# `dynamic_analysis` / `dynamic_analysis_enable_assertions`.
run: go test -tags sqlite_fts5 -race ./...
- name: Determine bump level
id: level
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "level=${{ inputs.bump }}" >> "$GITHUB_OUTPUT"
else
echo "level=patch" >> "$GITHUB_OUTPUT"
fi
- name: Compute next version
id: version
run: |
git fetch --tags --force
LATEST=$(git tag -l 'v[0-9]*' --sort=-v:refname | head -n1)
if [ -z "$LATEST" ]; then
LATEST="v0.0.0"
fi
echo "latest=$LATEST" >> "$GITHUB_OUTPUT"
VERSION="${LATEST#v}"
MAJOR=$(echo "$VERSION" | cut -d. -f1)
MINOR=$(echo "$VERSION" | cut -d. -f2)
PATCH=$(echo "$VERSION" | cut -d. -f3)
case "${{ steps.level.outputs.level }}" in
major)
MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
minor)
MINOR=$((MINOR + 1)); PATCH=0 ;;
patch)
PATCH=$((PATCH + 1)) ;;
esac
NEXT="v${MAJOR}.${MINOR}.${PATCH}"
echo "next=$NEXT" >> "$GITHUB_OUTPUT"
echo "Next version: $NEXT (from $LATEST, bump=${{ steps.level.outputs.level }})"
- name: Generate release notes
id: notes
env:
VERSION: ${{ steps.version.outputs.next }}
PREV: ${{ steps.version.outputs.latest }}
run: |
# Build human-readable, categorised release notes by
# parsing Conventional Commit prefixes (feat:, fix:, etc.)
# rather than dumping raw `git log` output. This satisfies
# the OpenSSF Best Practices `release_notes` criterion,
# which requires a human-readable summary that is NOT the
# raw output of a version control log.
range_args=()
if [ "$PREV" != "v0.0.0" ]; then
range_args+=("$PREV"..HEAD)
fi
git log --no-merges --pretty=format:'%s|%h' "${range_args[@]}" > /tmp/commits.tsv
group_by_prefix() {
local heading="$1" prefix_re="$2"
local body
body=$(grep -E "^${prefix_re}(\\([^)]+\\))?(!)?:" /tmp/commits.tsv \
| sed -E 's/^[a-z]+(\([^)]+\))?(!)?:[[:space:]]*(.*)\|([0-9a-f]+)$/- \3 (\4)/' \
|| true)
if [ -n "$body" ]; then
echo "### ${heading}"
echo
echo "$body"
echo
fi
}
{
echo "## Install"
echo
echo '### Prebuilt binary (fastest)'
echo
echo 'Download the archive for your platform from the assets below, extract, and drop `ctm` into a directory on your `$PATH`. Archives include the binary plus `LICENSE` and `README.md`.'
echo
echo '| Platform | Asset |'
echo '|---|---|'
echo "| Linux x86_64 | \`ctm-${VERSION}-linux-amd64.tar.gz\` |"
echo "| Linux ARM64 | \`ctm-${VERSION}-linux-arm64.tar.gz\` |"
echo "| macOS (Intel) | \`ctm-${VERSION}-darwin-amd64.tar.gz\` |"
echo "| macOS (Apple Silicon) | \`ctm-${VERSION}-darwin-arm64.tar.gz\` |"
echo
echo 'Windows users: run the Linux binary under WSL — tmux has no native Windows support.'
echo
echo '### From source'
echo
echo '```bash'
echo "go install github.com/${{ github.repository }}@$VERSION"
echo '```'
echo
echo '### Air-gapped'
echo
echo "The \`ctm-${VERSION}-src.tar.gz\` archive is a vendored source tree (\`go mod vendor\` populated) that builds offline with \`go build .\`."
echo
echo "## What's changed"
echo
group_by_prefix "Features" "feat"
group_by_prefix "Bug fixes" "fix"
group_by_prefix "Performance" "perf"
group_by_prefix "Refactoring" "refactor"
group_by_prefix "Documentation" "docs"
group_by_prefix "Tests" "test"
group_by_prefix "CI / build" "(ci|build)"
group_by_prefix "Chores" "chore"
# Anything that doesn't follow Conventional Commits ends
# up here so nothing gets dropped silently.
other=$(grep -vE '^[a-z]+(\([^)]+\))?(!)?:' /tmp/commits.tsv \
| sed -E 's/^(.*)\|([0-9a-f]+)$/- \1 (\2)/' || true)
if [ -n "$other" ]; then
echo "### Other"
echo
echo "$other"
echo
fi
} > release-notes.md
echo "Generated notes:"
cat release-notes.md
- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Create tag
env:
VERSION: ${{ steps.version.outputs.next }}
run: |
git tag -a "$VERSION" -m "Release $VERSION"
git push origin "$VERSION"
- name: Build release artifacts (cross-platform binaries + source + checksums)
env:
VERSION: ${{ steps.version.outputs.next }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
mkdir -p dist
# --- Source tarball (vendored, for air-gapped builds) ---
#
# `go mod vendor` populates vendor/ so downstream `go build`
# works without public internet (rules/build.md).
go mod vendor
tar \
--transform "s,^\\.,ctm-${VERSION}," \
--exclude='./.git' \
--exclude='./dist' \
--exclude='*.DS_Store' \
-czf "dist/ctm-${VERSION}-src.tar.gz" \
.
# --- Cross-compiled binaries ---
#
# ctm is pure Go (no cgo), so Go's native cross-compile is
# enough — no gcc-mingw tooling required. Each binary is
# built with:
# -trimpath : strip workspace paths for reproducibility
# -s -w : drop debug info + symbol table (~30% smaller)
# -X ...Version= : inject the release tag so `ctm version` reports it
LDFLAGS="-s -w -X github.com/${REPO}/cmd.Version=${VERSION}"
# Windows is not a target: ctm depends on tmux (not natively
# available on Windows) and uses POSIX syscalls like
# syscall.Flock in internal/logrotate / internal/session.
# Windows users run the Linux binary under WSL.
for target in linux-amd64 linux-arm64 darwin-amd64 darwin-arm64; do
goos="${target%-*}"
goarch="${target#*-}"
stage="ctm-${VERSION}-${target}"
mkdir -p "dist/${stage}"
echo "→ building ${target}"
# -tags sqlite_fts5: see "Run tests" step. Released binaries
# without this tag panic at boot in OpenCostStore.
CGO_ENABLED=0 GOOS="$goos" GOARCH="$goarch" \
go build -trimpath -tags sqlite_fts5 -ldflags "$LDFLAGS" \
-o "dist/${stage}/ctm" \
./
cp LICENSE README.md "dist/${stage}/"
(cd dist && tar -czf "${stage}.tar.gz" "${stage}" && rm -rf "${stage}")
done
# --- SHA256SUMS over every artifact ---
#
# GNU coreutils format (`sha256sum -c SHA256SUMS` verifies).
(cd dist && sha256sum ctm-*.tar.gz > SHA256SUMS)
echo "Artifacts:"
ls -lh dist
echo "Checksums:"
cat dist/SHA256SUMS
- name: Append checksums to release notes
run: |
{
echo
echo "## Verification"
echo
echo 'Tarball SHA256 sums (also attached as `SHA256SUMS`):'
echo
echo '```'
cat dist/SHA256SUMS
echo '```'
echo
echo 'Verify:'
echo
echo '```bash'
echo 'sha256sum -c SHA256SUMS # inside the dir where both files live'
echo '```'
} >> release-notes.md
- name: Create GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.next }}
run: |
gh release create "$VERSION" \
--title "$VERSION" \
--notes-file release-notes.md \
dist/ctm-*.tar.gz \
dist/SHA256SUMS