-
Notifications
You must be signed in to change notification settings - Fork 0
197 lines (176 loc) · 8.7 KB
/
Copy pathrelease.yml
File metadata and controls
197 lines (176 loc) · 8.7 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
name: Release
# Builds the distributables, creates a GitHub release, and attaches each
# file with its checksum. Triggered by pushing a version tag:
#
# npm version minor
# git push --follow-tags
on:
push:
tags:
- 'v*'
# No permissions by default.
permissions: {}
jobs:
release:
name: Release
runs-on: ubuntu-latest
permissions:
contents: write # create release and upload assets
id-token: write # request OIDC token for attestation signing
attestations: write # write attestation to repo
steps:
- name: Check out source
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
# This job isn't safe to blindly re-run past this point: release
# creation fails outright on a second attempt against the same tag. If
# a re-run gets this far and the release already exists, the earlier
# run already built, tested, attested, and published it from this
# identical commit — so there's nothing left to redo. Every step below
# through "Create GitHub release" is skipped in that case, and the job
# goes straight to (re-)triggering the deploy, which is the one step
# actually worth retrying blindly.
- name: Check for an existing release
id: existing-release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "Release ${GITHUB_REF_NAME} already exists — skipping build, attestation, and release creation."
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi
- name: Set up Node
if: steps.existing-release.outputs.exists != 'true'
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: '22.23.0'
- name: Install dependencies
if: steps.existing-release.outputs.exists != 'true'
run: npm ci
- name: Lint and format (Biome)
if: steps.existing-release.outputs.exists != 'true'
run: npm run lint
- name: Type check
if: steps.existing-release.outputs.exists != 'true'
run: npm run typecheck
- name: Test
if: steps.existing-release.outputs.exists != 'true'
run: npm test
# Guard: refuse to release if the tag and package.json disagree.
- name: Verify tag matches package version
if: steps.existing-release.outputs.exists != 'true'
run: |
version=$(node -p "require('./package.json').version")
tag="${GITHUB_REF_NAME#v}"
if [ "$version" != "$tag" ]; then
echo "::error::Tag '${GITHUB_REF_NAME}' does not match package.json version '${version}'"
exit 1
fi
# The root build script builds argon2 and chacha20, then kdbx, then
# pages, in that dependency order, writing all intermediate output to
# build/ and the final distributables to dist/ — see ci.yml Step 7.
# Neither directory is committed, so this rebuild from source is the
# only place dist/*.html come from; there's nothing to reuse from CI.
# The inliner prints "sha256:<hex> <output path>" to stdout for each
# built file; the loop below re-runs it and keeps only the first
# whitespace-separated field (the checksum) for the summary. CNAME is
# copied verbatim; its checksum is computed separately since it isn't
# inlined.
#
# KEEPASS_WEB_COMMIT_DATE feeds every distributable's version footer
# (GITHUB_REF_TYPE/GITHUB_REF_NAME/GITHUB_SHA are already set by
# Actions) — see tools/build/inliner/src/version-label.ts and
# docs/REPRODUCING.md for reproducing this outside CI.
- name: Build distributables
if: steps.existing-release.outputs.exists != 'true'
run: |
export KEEPASS_WEB_COMMIT_DATE="$(git show -s --format=%cI HEAD)"
npm run build
cp pages/CNAME dist/CNAME
printf '### Distributable checksums\n| File | SHA-256 |\n|------|---------|\n' >> "$GITHUB_STEP_SUMMARY"
for page in index local 0x67 cloud-google-drive; do
checksum=$(node --experimental-strip-types tools/build/inliner/src/index.ts "pages/${page}/build.json" | awk '{print $1}')
printf '| `%s.html` | `%s` |\n' "${page}" "${checksum}" >> "$GITHUB_STEP_SUMMARY"
done
printf '| `CNAME` | `sha256:%s` |\n' "$(sha256sum dist/CNAME | awk '{print $1}')" >> "$GITHUB_STEP_SUMMARY"
# Real-browser coverage jsdom can't provide (no layout engine); gates
# attestation/release/deploy. Calls the e2e workspace's script directly —
# the root `npm run test:e2e` would rebuild dist/ without this job's
# KEEPASS_WEB_COMMIT_DATE, mismatching what's actually released.
- name: End-to-end tests
if: steps.existing-release.outputs.exists != 'true'
run: npm run test:e2e --workspace=e2e
# Independent from the release check above: attestation and release
# creation are separate GitHub subsystems, not one atomic operation.
# They're ordered attest-then-release in this job, so a *successful*
# prior run guarantees both exist — but a prior run that attested
# successfully and then failed on "Create GitHub release" (for a
# reason unrelated to attestation) would leave an attestation with no
# release, which the check above wouldn't see. Unlike a duplicate
# release, a duplicate attestation isn't an error — GitHub attestation
# verification is monotonic, so re-attesting an already-attested
# digest just adds a second valid record rather than failing — but
# it's still redundant, so check for it explicitly rather than relying
# on that being harmless.
- name: Check for an existing attestation
id: existing-attestation
if: steps.existing-release.outputs.exists != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if gh attestation verify dist/0x67.html --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1 && \
gh attestation verify dist/local.html --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1 && \
gh attestation verify dist/index.html --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1 && \
gh attestation verify dist/cloud-google-drive.html --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1 && \
gh attestation verify dist/CNAME --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "Distributables are already attested for this commit — skipping re-attestation."
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi
- name: Attest distributables
if: steps.existing-release.outputs.exists != 'true' && steps.existing-attestation.outputs.exists != 'true'
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
subject-path: |
dist/0x67.html
dist/local.html
dist/index.html
dist/cloud-google-drive.html
dist/CNAME
- name: Create GitHub release
if: steps.existing-release.outputs.exists != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
notes="### Distributable checksums"$'\n'
notes+="| File | SHA-256 |"$'\n'
notes+="|------|---------|"$'\n'
for file in dist/index.html dist/local.html dist/0x67.html dist/cloud-google-drive.html dist/CNAME; do
notes+="| \`$(basename "${file}")\` | \`sha256:$(sha256sum "${file}" | awk '{print $1}')\` |"$'\n'
done
gh release create "$GITHUB_REF_NAME" \
--title "$GITHUB_REF_NAME" \
--notes "${notes}" \
dist/0x67.html \
dist/local.html \
dist/index.html \
dist/cloud-google-drive.html \
dist/CNAME
# repositories: and --repo below must match the deploy repo's current
# name exactly — keep them in sync if that repo is ever renamed.
- name: Generate deploy token
id: deploy-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
with:
client-id: ${{ secrets.DEPLOY_BOT_APP_ID }}
private-key: ${{ secrets.DEPLOY_BOT_APP_PRIVATE_KEY }}
repositories: keepass-web.app
- name: Trigger deploy workflow
env:
GH_TOKEN: ${{ steps.deploy-token.outputs.token }}
run: |
gh workflow run deploy.yml \
--repo keepass-web/keepass-web.app \
--field release-tag="$GITHUB_REF_NAME"