Repository navigation
228 lines (199 loc) · 9.37 KB
/
Copy pathrelease.yml
File metadata and controls
228 lines (199 loc) · 9.37 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
# Fully CI-driven release flow, no local commands. Two human confirmations, both without
# running anything on a maintainer's machine:
#
# 1. release-please watches pushes to `main` and keeps a "release PR" open, whose title/body
# and CHANGELOG.md entry are generated from Conventional Commit messages since the last
# release (feat -> minor, fix -> patch, `!`/`BREAKING CHANGE:` -> major). Nothing is
# published yet. A maintainer reviews and merges that PR: that merge is confirmation #1.
# 2. Merging the release PR makes release-please tag the release commit and create a draft
# GitHub Release, which triggers this same workflow again. This time `release_created` is
# `true`: the `release-assets` job attaches the tarball, its signed provenance and the SBOM to
# the draft and publishes it, and the `publish-npm` job builds, validates and STAGES the
# package on npm (`npm stage publish --provenance`). A staged version is not installable until a
# maintainer approves it with 2FA, on npmjs.com (package -> Staged versions) or with
# `npm stage approve <stage-id>`. That approval is confirmation #2 (npm's
# "proof-of-presence"), and the trusted publisher is configured for staged publishing only,
# so no workflow can ever publish directly.
#
# Dependabot and dataset-update PRs are included automatically: any conventional commit merged
# to `main` (via any PR) feeds the next release PR, no extra step needed.
#
# One-time npm Trusted Publishing setup (done by a package maintainer, not by CI):
# 1. Sign in at npmjs.com and open this package.
# 2. Settings -> Trusted Publishing -> Add a trusted publisher -> GitHub Actions.
# 3. Organization/user: brazilian-utils | Repository: javascript
# Workflow filename: release.yml | Environment: npm
# Allowed actions: leave "publish directly" UNCHECKED (staged publishing only).
# 4. Save. No NPM_TOKEN secret is needed: npm exchanges the workflow's OIDC token for a
# short-lived token automatically.
#
# One-time JSR setup (done by a package maintainer, not by CI):
# 1. Sign in at jsr.io with GitHub and create the scope `brazilian-utils` and the package
# `brazilian-utils` in it.
# 2. Package Settings -> GitHub Actions -> link `brazilian-utils/javascript`. JSR then trusts
# this repository's OIDC token; no JSR token is stored anywhere.
#
# One-time GitHub Environment setup (done by a package maintainer, not by CI):
# Repository Settings -> Environments -> New environment -> name it `npm`. No protection rules
# are needed: the environment only exists so the npm trusted publisher can be bound to it.
name: Release
on:
push:
branches: [main]
workflow_dispatch:
concurrency:
group: release
permissions:
contents: read
jobs:
release-please:
name: Release Please
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
pull-requests: write
outputs:
release_created: ${{ steps.release.outputs.release_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
version: ${{ steps.release.outputs.version }}
steps:
- name: Run release-please
id: release
uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
publish-npm:
name: Publish to npm
needs: release-please
if: ${{ needs.release-please.outputs.release_created == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 15
environment: npm
permissions:
contents: read
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.release-please.outputs.tag_name }}
persist-credentials: false
# setup-node runs in the workflow itself, not in the composite action, with the npm registry
# URL npm's trusted publishing guide uses: OpenSSF Scorecard (Packaging) only recognizes an
# npm publishing job by this step next to the publish command.
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
registry-url: https://registry.npmjs.org
- name: Setup
uses: ./.github/actions/setup
with:
setup-node: "false"
- name: Install dependencies
run: npm ci
- name: Run build
run: npm run build
- name: Compare tree-shaking against the published version
continue-on-error: true
run: |
mkdir -p /tmp/prev
npm pack @brazilian-utils/brazilian-utils@latest --pack-destination /tmp/prev
tar -xzf /tmp/prev/*.tgz -C /tmp/prev
node scripts/tree-shaking.ts --dist /tmp/prev/package --json /tmp/prev/published.json
node scripts/tree-shaking.ts --compare /tmp/prev/published.json --markdown tree-shaking.md
cat tree-shaking.md >> "$GITHUB_STEP_SUMMARY"
- name: Ensure npm supports staged publishing and OIDC (npm >= 11.15)
# Node.js 24.18 and later bundle npm 11.16+, so the npm that actions/setup-node ships is
# enough; upgrading it with `npm install -g` is an unpinned install for OpenSSF Scorecard
# (Pinned-Dependencies), so the step only asserts the version and never installs anything.
run: |
npm_version="$(npm --version)"
echo "npm ${npm_version}"
echo "${npm_version}" | awk -F. '{ exit !($1 > 11 || ($1 == 11 && $2 >= 15)) }'
- name: Generate the CycloneDX SBOM shipped inside the package
# `files` in package.json lists brazilian-utils.cdx.json, so the tarball carries it;
# --package-lock-only reads the lockfile instead of installing anything.
run: npm sbom --sbom-format cyclonedx --omit dev --package-lock-only > brazilian-utils.cdx.json
- name: Stage on npm
run: npm stage publish --provenance --access public
publish-jsr:
name: Publish to JSR
needs: release-please
# JSR publishes through OIDC as well: no token is stored. It needs the one-time JSR setup
# described at the top of this file; until that is done this job fails, and the npm job, which
# does not depend on it, still publishes.
if: ${{ needs.release-please.outputs.release_created == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write
steps:
- name: Checkout the release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.release-please.outputs.tag_name }}
persist-credentials: false
- name: Setup
uses: ./.github/actions/setup
- name: Setup Deno
uses: denoland/setup-deno@22d081ff2d3a40755e97629de92e3bcbfa7cf2ed # v2.0.5
with:
deno-version: v2.x
- name: Publish to JSR
run: deno publish
release-assets:
name: Attach the signed package to the release
needs: release-please
if: ${{ needs.release-please.outputs.release_created == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
id-token: write
attestations: write
env:
TAG: ${{ needs.release-please.outputs.tag_name }}
steps:
- name: Checkout the release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.release-please.outputs.tag_name }}
persist-credentials: false
- name: Setup
uses: ./.github/actions/setup
with:
node-version: 24
- name: Install dependencies
run: npm ci
- name: Run build
run: npm run build
- name: Pack the package as npm publishes it
id: pack
# The SBOM goes in first because `files` in package.json ships it inside the tarball.
run: |
npm sbom --sbom-format cyclonedx --omit dev --package-lock-only > brazilian-utils.cdx.json
echo "tarball=$(npm pack --silent)" >> "$GITHUB_OUTPUT"
- name: Attest the build provenance of the tarball
id: attest
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: ${{ steps.pack.outputs.tarball }}
# release-please creates the release as a draft (release-please-config.json) because releases
# are immutable in this repository: assets can only be added before the release is published.
# The Sigstore bundle can be checked with `gh attestation verify`; the .intoto.jsonl file holds
# the same signed SLSA provenance as a DSSE envelope, the form OpenSSF Scorecard
# (Signed-Releases) looks for.
- name: Attach the tarball, its provenance and the SBOM, then publish the release
env:
GH_TOKEN: ${{ github.token }}
TARBALL: ${{ steps.pack.outputs.tarball }}
BUNDLE: ${{ steps.attest.outputs.bundle-path }}
run: |
head -n 1 "$BUNDLE" | jq . > "$TARBALL.sigstore.json"
jq -c .dsseEnvelope "$BUNDLE" > "$TARBALL.intoto.jsonl"
gh release upload "$TAG" "$TARBALL" "$TARBALL.sigstore.json" "$TARBALL.intoto.jsonl" brazilian-utils.cdx.json --repo "$GITHUB_REPOSITORY"
gh release edit "$TAG" --draft=false --latest --repo "$GITHUB_REPOSITORY"