-
Notifications
You must be signed in to change notification settings - Fork 17
259 lines (233 loc) · 10.8 KB
/
on-release.yml
File metadata and controls
259 lines (233 loc) · 10.8 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
name: Release
on:
release:
types: [published]
permissions:
contents: write # Required for GitHub publisher to upload release assets
checks: write # Required for unit tests workflow
pull-requests: write # Required for unit tests workflow
id-token: write # Required for AWS OIDC authentication
jobs:
validate-tag:
name: Validate Tag
runs-on: ubuntu-latest
steps:
- name: Validate tag format
shell: bash
run: |
TAG="${GITHUB_REF#refs/tags/}"
if [[ ! "$TAG" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$ ]]; then
echo "::error::Tag '$TAG' is not valid semver (e.g. v1.2.3, v1.2.3-rc.1)"
exit 1
fi
echo "Valid tag: $TAG"
validate-prerelease:
name: Validate Prerelease Tag
runs-on: ubuntu-latest
steps:
- name: Validate prerelease tag
if: ${{ github.event.release.prerelease == true }}
shell: bash
run: |
TAG="${GITHUB_REF#refs/tags/}"
if [[ ! "$TAG" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+-(alpha|beta|rc)(\.[0-9]+)?$ ]]; then
echo "::error::Invalid prerelease tag '$TAG'. Allowed identifiers: alpha, beta, rc"
exit 1
fi
echo "Valid prerelease tag: $TAG"
security:
name: Security Checks
needs: [validate-tag, validate-prerelease]
uses: ./.github/workflows/_security-checks.yml
static-checks:
name: Static Checks
needs: [validate-tag, validate-prerelease]
uses: ./.github/workflows/_static-checks.yml
secrets: inherit
unit-tests:
name: Unit Tests
needs: [validate-tag, validate-prerelease]
uses: ./.github/workflows/_unit-tests.yml
build-and-release:
name: Build and Release on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
needs: [security, static-checks, unit-tests]
# Activate the `artifact-signing` environment only for the Windows matrix
# row when the release is a prerelease, because that is the only row
# that signs with Azure Trusted Signing. The Azure OIDC federated
# credential is scoped to
# `repo:stacklok/toolhive-studio:environment:artifact-signing`, so the
# job must run in that environment for the Azure token exchange to
# succeed. Every other row (Linux and macOS on any release, and
# Windows on stable releases) stays outside the environment so it
# doesn't inherit the environment-scoped Azure signing secrets or
# trigger environment approvals. Windows stable releases continue
# signing via DigiCert; Linux/macOS don't do Windows signing at all.
#
# The AWS role assumed later in the job uses OIDC with the repo-wide
# trust policy `repo:stacklok/toolhive-studio:*`, which covers both
# `ref:refs/tags/*` (default subject) and
# `environment:artifact-signing`, so S3 sync and CloudFront
# invalidation keep working from the Windows prerelease job.
environment: ${{ github.event.release.prerelease == true && matrix.os == 'windows-latest' && 'artifact-signing' || '' }}
strategy:
matrix:
include:
- os: ubuntu-24.04-arm
arch: arm64
- os: ubuntu-latest
arch: x64
- os: windows-latest
arch: x64
- os: macos-15-intel
arch: x64
- os: macos-latest
arch: arm64
env:
NODE_OPTIONS: '--max_old_space_size=4096'
steps:
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup
uses: ./.github/actions/setup
- name: Update version from tag
shell: bash
run: |
# Extract version from tag (remove 'v' prefix if present)
VERSION=${GITHUB_REF#refs/tags/v}
VERSION=${VERSION#v}
echo "Setting version to: $VERSION"
# Update package.json version
npm version $VERSION --no-git-tag-version --allow-same-version
echo "Updated package.json version to $VERSION"
# Set SENTRY_RELEASE for build and publish step
echo "SENTRY_RELEASE=${VERSION}" >> $GITHUB_ENV
- name: Setup macOS code signing
uses: ./.github/actions/setup-macos-codesign
if: runner.os == 'macOS'
with:
apple-certificate: ${{ secrets.APPLE_CERTIFICATE }}
apple-certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
keychain-password: ${{ secrets.KEYCHAIN_PASSWORD }}
apple-api-key: ${{ secrets.APPLE_API_KEY }}
apple-issuer-id: ${{ secrets.APPLE_ISSUER_ID }}
apple-key-id: ${{ secrets.APPLE_KEY_ID }}
# Windows prerelease builds sign with Azure Trusted Signing (OIDC, no
# long-lived secrets). The calling job must be in the
# `artifact-signing` environment above so the federated credential
# subject `repo:.../environment:artifact-signing` is satisfied.
- name: Setup Windows code signing (Azure Trusted Signing — prerelease)
uses: ./.github/actions/setup-azure-trusted-signing
if: runner.os == 'Windows' && github.event.release.prerelease == true
with:
azure-client-id: ${{ secrets.AZURE_ARTIFACT_SIGNING_CLIENT_ID }}
azure-tenant-id: ${{ secrets.AZURE_ARTIFACT_SIGNING_TENANT_ID }}
azure-subscription-id: ${{ secrets.AZURE_ARTIFACT_SIGNING_SUBSCRIPTION_ID }}
azure-endpoint: ${{ secrets.AZURE_ARTIFACT_SIGNING_ENDPOINT }}
azure-account-name: ${{ secrets.AZURE_ARTIFACT_SIGNING_ACCOUNT_NAME }}
azure-certificate-profile-name: ${{ secrets.AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME }}
# Windows stable releases continue to sign with DigiCert KeyLocker
# until the Azure flow is validated end-to-end via prereleases. Remove
# this step (and the `setup-windows-codesign` action) once stable
# releases are also migrated to Azure Trusted Signing.
- name: Setup Windows code signing (DigiCert KeyLocker — stable)
uses: ./.github/actions/setup-windows-codesign
if: runner.os == 'Windows' && github.event.release.prerelease != true
with:
sm-host: ${{ secrets.SM_HOST }}
sm-api-key: ${{ secrets.SM_API_KEY }}
sm-client-cert-file-b64: ${{ secrets.SM_CLIENT_CERT_FILE_B64 }}
sm-client-cert-password: ${{ secrets.SM_CLIENT_CERT_PASSWORD }}
sm-code-signing-cert-sha1-hash: ${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }}
- name: Setup Flatpak (Linux only)
uses: ./.github/actions/setup-flatpak
if: runner.os == 'Linux'
- name: Configure AWS Credentials
if: vars.AWS_ROLE_ARN != ''
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6
with:
role-to-assume: ${{ vars.AWS_ROLE_ARN }}
aws-region: us-east-1
- name: Build and Publish
run: pnpm run publish
env:
# Ensure the correct Node.js version is used for native modules
npm_config_target_platform: ${{ runner.os == 'Linux' && 'linux' || (runner.os == 'macOS' && 'darwin' || 'win32') }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
VITE_SENTRY_THV_DSN: ${{ vars.VITE_SENTRY_THV_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }}
SENTRY_RELEASE: ${{ env.SENTRY_RELEASE }}
# Apple API Key method for notarization (set by composite action)
APPLE_API_KEY: ${{ env.APPLE_API_KEY_PATH }}
APPLE_ISSUER_ID: ${{ env.APPLE_ISSUER_ID }}
APPLE_KEY_ID: ${{ env.APPLE_KEY_ID }}
- name: Copy macOS update manifest to stable path
id: macos-manifest
if: runner.os == 'macOS' && vars.AWS_ROLE_ARN != ''
shell: bash
run: |
VERSION=${GITHUB_REF#refs/tags/v}
VERSION=${VERSION#v}
CHANNEL=${{ github.event.release.prerelease == true && 'pre-release' || 'stable' }}
ARCH=${{ matrix.arch }}
BUCKET="toolhive-studio-releases"
SRC="s3://${BUCKET}/${CHANNEL}/${VERSION}/darwin/${ARCH}/RELEASES.json"
DST="s3://${BUCKET}/${CHANNEL}/latest/darwin/${ARCH}/RELEASES.json"
if aws s3 cp "${SRC}" "${DST}"; then
echo "uploaded=true" >> "$GITHUB_OUTPUT"
else
echo "::warning::No RELEASES.json found at ${SRC} — skipping"
echo "uploaded=false" >> "$GITHUB_OUTPUT"
fi
- name: Promote Windows update manifests to stable path
id: windows-manifest
if: runner.os == 'Windows' && vars.AWS_ROLE_ARN != ''
shell: bash
run: |
VERSION=${GITHUB_REF#refs/tags/v}
VERSION=${VERSION#v}
CHANNEL=${{ github.event.release.prerelease == true && 'pre-release' || 'stable' }}
ARCH=${{ matrix.arch }}
BUCKET="toolhive-studio-releases"
SRC="s3://${BUCKET}/${CHANNEL}/${VERSION}/win32/${ARCH}"
DST="s3://${BUCKET}/${CHANNEL}/latest/win32/${ARCH}"
echo "Promoting Windows update files from ${SRC} to ${DST}"
# --delete ensures stale nupkg files from previous releases don't accumulate in latest/
# --copy-props none avoids s3:GetObjectTagging calls that the IAM role lacks
if aws s3 sync "${SRC}/" "${DST}/" \
--delete \
--copy-props none \
--exclude "*" \
--include "RELEASES" \
--include "*.nupkg"; then
echo "uploaded=true" >> "$GITHUB_OUTPUT"
else
echo "::warning::No Windows update files found — skipping"
echo "uploaded=false" >> "$GITHUB_OUTPUT"
fi
- name: Invalidate CloudFront cache for update manifests
if: |
vars.AWS_ROLE_ARN != '' &&
vars.CLOUDFRONT_DISTRIBUTION_ID != '' &&
(steps.macos-manifest.outputs.uploaded == 'true' || steps.windows-manifest.outputs.uploaded == 'true')
shell: bash
run: |
CHANNEL=${{ github.event.release.prerelease == true && 'pre-release' || 'stable' }}
PLATFORM=${{ runner.os == 'macOS' && 'darwin' || 'win32' }}
ARCH=${{ matrix.arch }}
DIST_ID="${{ vars.CLOUDFRONT_DISTRIBUTION_ID }}"
echo "Invalidating /${CHANNEL}/latest/${PLATFORM}/${ARCH}/*"
aws cloudfront create-invalidation \
--distribution-id "$DIST_ID" \
--paths "/${CHANNEL}/latest/${PLATFORM}/${ARCH}/*"
mirror-pages:
name: Mirror release to GitHub Pages
needs: [build-and-release]
if: ${{ always() && needs.build-and-release.result == 'success' }}
uses: ./.github/workflows/mirror-to-gh-pages.yml
with:
tag: ${{ github.ref_name }}
secrets: inherit