Repository navigation
133 lines (123 loc) · 5.67 KB
/
Copy pathrelease.yml
File metadata and controls
133 lines (123 loc) · 5.67 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
name: Publish image
on:
push:
branches: [main]
tags: ["v*"]
# release-please creates its tags with a PAT (not GITHUB_TOKEN), so that tag
# push isn't subject to GitHub's anti-recursion trigger suppression and the
# `tags:` rule above fires on its own — no dispatch needed. Kept for anyone
# who wants to manually rebuild pinned images from an existing tag.
workflow_dispatch:
inputs:
tag:
description: "Existing vX.Y.Z tag to build pinned images for"
required: true
type: string
# One publish run per ref at a time, but let a superseded run finish rather
# than cancelling it (cancel-in-progress: false). release-please's auto-merge
# means the release-bump commit almost always lands on `main` seconds behind
# the feature commit it bundles — two pushes to the same ref within the same
# concurrency window. With cancel-in-progress: true, whichever of those two
# runs happened to be mid-flight when the other queued got killed, and if
# that was the *later* (release-bump) commit's run, `:latest` stayed built
# from the pre-bump commit until some unrelated future push rebuilt it —
# confirmed live: v1.6.0's `:latest` build was cancelled this way, leaving
# GHCR's `:latest` several commits stale (right features, wrong version
# label) with no error surfaced anywhere. Queueing instead of cancelling
# costs a redundant build occasionally; it never leaves `:latest` wrong.
concurrency:
group: publish-${{ inputs.tag || github.ref }}
cancel-in-progress: false
jobs:
# Gate the image publish on the same tests CI runs, so a red test never
# ships an image.
verify:
name: Tests
runs-on: ubuntu-latest
steps:
# On dispatch, test the tag being built (empty ref = event's own ref).
- uses: actions/checkout@v7
with:
ref: ${{ inputs.tag || '' }}
- uses: actions/setup-node@v7
with:
node-version: "22"
- name: Server tests
working-directory: server
run: npm ci && npm test
- name: Client build
working-directory: client
run: npm ci && npm run build
publish:
name: Build and push to GHCR
needs: verify
runs-on: ubuntu-latest
# A normal build takes ~4 minutes. Fail loudly on a stall instead of
# hanging for GitHub's 6-hour default with `:latest` silently stale.
timeout-minutes: 25
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v7
with:
ref: ${{ inputs.tag || '' }}
# GHCR image names must be lowercase; the owner may not be.
- name: Compute lowercase image name
id: img
run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
- name: Docker metadata (tags/labels)
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ steps.img.outputs.name }}
flavor: latest=false
# Use explicit ref checks rather than {{is_default_branch}} — the
# repo's default branch isn't necessarily `main`, which would leave
# a push to main with zero tags (and buildx refuses to push then).
# The `value=` semver entries cover dispatch (github.ref is main
# there, so the ref-derived semver entries produce nothing).
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{version}},value=${{ inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
type=semver,pattern={{major}}.{{minor}},value=${{ inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }}
# Pin a recent QEMU build. The default binfmt image lagged the toolchain
# in node:22-alpine (gcc 15 / musl 1.2.6), and emulating those newer
# arm64 binaries crashed with "uncaught target signal 4 (Illegal
# instruction)", which core-dumped and left the build hung. A current
# QEMU emulates the new userspace correctly.
- uses: docker/setup-qemu-action@v4
with:
image: tonistiigi/binfmt:qemu-v10.2.3
- uses: docker/setup-buildx-action@v4
- name: Log in to GHCR
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v7
with:
context: .
file: server/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# Separate cache scopes for branch and tag builds. A release pushes
# `main` and the `vX.Y.Z` tag seconds apart, so both builds run at
# once (different refs, different concurrency groups); writing the
# same gha cache scope concurrently stalled the `:latest` build for
# 12+ minutes on v1.8.0. Tag builds still READ main's cache, so they
# stay fast, but each writes only its own scope.
cache-from: |
type=gha,scope=main
type=gha,scope=${{ github.ref_type == 'tag' && 'release' || 'main' }}
cache-to: type=gha,mode=max,scope=${{ github.ref_type == 'tag' && 'release' || 'main' }}
# NOTE: the GitHub Release itself is created by release-please.yml (with a
# changelog grouped by feat/fix/deps), not here — it creates the git tag as a
# side effect of publishing the Release, which is what triggers this workflow's
# tag push above. See release-please-config.json / release-please.yml.