Skip to content

Commit f376d67

Browse files
authored
ci: automate releases with Release Please (#432)
1 parent 9090047 commit f376d67

22 files changed

Lines changed: 1930 additions & 1040 deletions

‎.github/workflows/release.yml‎

Lines changed: 178 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,26 @@ on:
44
workflow_dispatch:
55
inputs:
66
version:
7-
description: "Exact prepared version, such as 10.0.1 or 9.8.3"
7+
description: "patch, minor, major, or an exact stable version"
88
required: true
9+
default: "patch"
910
type: string
11+
announcement:
12+
description: "Optional announcement, saved before the generated changelog"
13+
required: false
14+
type: string
15+
release_tag:
16+
description: "Existing release tag to resume publication without another version bump"
17+
required: false
18+
type: string
19+
20+
permissions: {}
1021

11-
permissions:
12-
contents: write
13-
# Both npm packages must trust margelo/react-native-nitro-sqlite's release.yml.
14-
id-token: write
22+
env:
23+
GH_REPO: ${{ github.repository }}
1524

1625
concurrency:
26+
# Serialize releases across branches so maintenance releases cannot race latest.
1727
group: release
1828
cancel-in-progress: false
1929

@@ -26,112 +36,191 @@ jobs:
2636
with:
2737
full: true
2838

29-
release:
30-
name: Publish packages and GitHub release
39+
prepare:
40+
name: Prepare release PR and lockfiles
3141
needs: preflight
32-
runs-on: ubuntu-24.04
42+
if: inputs.release_tag == ''
43+
runs-on: macos-26
44+
timeout-minutes: 40
45+
permissions:
46+
contents: write
47+
pull-requests: write
48+
issues: write
49+
outputs:
50+
pr: ${{ steps.prepare.outputs.pr }}
51+
version: ${{ steps.prepare.outputs.version }}
52+
env:
53+
# A separate token makes GitHub run the usual PR checks on automated commits.
54+
GH_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.RELEASE_IT_GITHUB_TOKEN }}
55+
RELEASE_INCREMENT: ${{ inputs.version }}
3356
steps:
57+
- name: Check release credentials
58+
run: |
59+
if [ -z "$GH_TOKEN" ]; then
60+
echo "Configure RELEASE_PLEASE_TOKEN with contents, pull requests, and issues write access." >&2
61+
exit 1
62+
fi
3463
- uses: actions/checkout@v7
3564
with:
3665
fetch-depth: 0
3766
ref: ${{ github.sha }}
38-
39-
- name: Validate release version
67+
persist-credentials: false
68+
- uses: actions/setup-node@v7
69+
with:
70+
node-version: 24
71+
package-manager-cache: false
72+
- uses: oven-sh/setup-bun@v2
73+
with:
74+
bun-version: 1.3.1
75+
- name: Install release tooling
76+
run: bun install --frozen-lockfile
77+
- name: Test release automation
78+
run: |
79+
bun run test:release
80+
bun run typecheck:release
81+
- name: Create or refresh release PR
82+
id: prepare
83+
run: node scripts/prepare-release.ts
84+
- name: Check out release PR
85+
uses: actions/checkout@v7
86+
with:
87+
ref: ${{ steps.prepare.outputs.branch }}
88+
token: ${{ env.GH_TOKEN }}
89+
fetch-depth: 0
90+
persist-credentials: false
91+
- name: Synchronize workspace lockfile
92+
run: |
93+
bun install --lockfile-only --ignore-scripts
94+
bun run sync:lockfile
95+
bun run check:lockfile
96+
bun install --frozen-lockfile
97+
- uses: ruby/setup-ruby@v1
98+
with:
99+
ruby-version: 3.3.0
100+
bundler-cache: true
101+
working-directory: example
102+
- name: Select Xcode
103+
run: sudo xcode-select -s "/Applications/Xcode_26.5.app/Contents/Developer"
104+
- name: Update iOS and macOS lockfiles
105+
run: |
106+
bun example pods
107+
bun example pods:macos
108+
- name: Commit lockfiles and optional announcement
109+
run: node scripts/commit-release-lockfiles.ts
40110
env:
41-
RELEASE_VERSION: ${{ inputs.version }}
111+
RELEASE_BRANCH: ${{ steps.prepare.outputs.branch }}
112+
RELEASE_ANNOUNCEMENT: ${{ inputs.announcement }}
113+
114+
merge:
115+
name: Wait for checks, merge and tag release
116+
needs: prepare
117+
runs-on: ubuntu-24.04
118+
timeout-minutes: 70
119+
permissions:
120+
contents: write
121+
pull-requests: write
122+
issues: write
123+
outputs:
124+
tag: ${{ steps.release.outputs.tag }}
125+
env:
126+
GH_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.RELEASE_IT_GITHUB_TOKEN }}
127+
RELEASE_PR: ${{ needs.prepare.outputs.pr }}
128+
RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
129+
steps:
130+
- uses: actions/checkout@v7
131+
with:
132+
ref: ${{ github.sha }}
133+
persist-credentials: false
134+
- uses: actions/setup-node@v7
135+
with:
136+
node-version: 24
137+
package-manager-cache: false
138+
- uses: oven-sh/setup-bun@v2
139+
with:
140+
bun-version: 1.3.1
141+
- run: bun install --frozen-lockfile
142+
- name: Merge when GitHub permits, then create a tagged draft
143+
id: release
144+
run: node scripts/merge-release.ts
145+
146+
publish:
147+
name: Publish both packages from the release tag
148+
needs: [preflight, merge]
149+
if: ${{ always() && needs.preflight.result == 'success' && (needs.merge.result == 'success' || inputs.release_tag != '') }}
150+
runs-on: ubuntu-24.04
151+
timeout-minutes: 30
152+
permissions:
153+
contents: write
154+
id-token: write
155+
outputs:
156+
tag: ${{ steps.validate.outputs.tag }}
157+
latest: ${{ steps.validate.outputs.latest }}
158+
env:
159+
GH_TOKEN: ${{ github.token }}
160+
RELEASE_TAG: ${{ needs.merge.outputs.tag || inputs.release_tag }}
161+
NPM_CONFIG_PROVENANCE: "true"
162+
steps:
163+
- name: Validate tag input
42164
run: |
43-
if [[ ! "$RELEASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
44-
echo "An exact stable release version is required." >&2
165+
if [[ ! "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
166+
echo "An existing stable release tag is required." >&2
45167
exit 1
46168
fi
47-
169+
- uses: actions/checkout@v7
170+
with:
171+
ref: ${{ env.RELEASE_TAG }}
172+
fetch-depth: 0
173+
persist-credentials: false
48174
- uses: actions/setup-node@v7
49175
with:
50176
node-version: 24
51177
package-manager-cache: false
52-
53178
- uses: oven-sh/setup-bun@v2
54179
with:
55180
bun-version: 1.3.1
56-
57-
- name: Install npm dependencies (bun)
58-
run: bun install
59-
181+
- run: bun install --frozen-lockfile
182+
- name: Validate release and publication channel
183+
id: validate
184+
run: node scripts/validate-release.ts
60185
- name: Test release preflight
61-
run: bun run test:release
62-
63-
- name: Check trusted publishing for both packages
64186
run: |
65-
node --input-type=module <<'NODE'
66-
const requestUrl = process.env.ACTIONS_ID_TOKEN_REQUEST_URL
67-
const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN
68-
if (!requestUrl || !requestToken) {
69-
throw new Error('GitHub OIDC is unavailable to the release job')
70-
}
71-
72-
const oidcUrl = new URL(requestUrl)
73-
oidcUrl.searchParams.set('audience', 'npm:registry.npmjs.org')
74-
for (const packageName of [
75-
'react-native-nitro-sqlite',
76-
'react-native-nitro-sqlite-vec',
77-
]) {
78-
const oidcResponse = await fetch(oidcUrl, {
79-
headers: { Authorization: `Bearer ${requestToken}` },
80-
})
81-
if (!oidcResponse.ok) {
82-
throw new Error(`GitHub OIDC request failed: ${oidcResponse.status}`)
83-
}
84-
const { value: idToken } = await oidcResponse.json()
85-
if (!idToken) {
86-
throw new Error('GitHub OIDC response did not contain a token')
87-
}
88-
89-
const exchangeUrl = new URL(
90-
`/-/npm/v1/oidc/token/exchange/package/${encodeURIComponent(packageName)}`,
91-
'https://registry.npmjs.org',
92-
)
93-
const response = await fetch(exchangeUrl, {
94-
method: 'POST',
95-
headers: { Authorization: `Bearer ${idToken}` },
96-
})
97-
if (!response.ok) {
98-
throw new Error(`${packageName}: npm OIDC exchange failed (${response.status})`)
99-
}
100-
const { token } = await response.json()
101-
if (!token) {
102-
throw new Error(`${packageName}: npm OIDC exchange returned no token`)
103-
}
104-
console.log(`${packageName}: npm trusted publishing is available`)
105-
}
106-
NODE
107-
108-
- name: Generate release notes
109-
run: node scripts/generate-release-notes.ts "$RELEASE_VERSION" "$RUNNER_TEMP/release-notes.md"
110-
env:
111-
GH_TOKEN: ${{ github.token }}
112-
RELEASE_VERSION: ${{ inputs.version }}
113-
114-
- name: Publish prepared release
115-
run: ./scripts/release.sh --publish-prepared "$RELEASE_VERSION"
187+
bun run test:release
188+
bun run typecheck:release
189+
- name: Assemble announcement, changelog and contributors
190+
run: node scripts/generate-release-notes.ts "${RELEASE_TAG#v}" "$RUNNER_TEMP/release-notes.md"
191+
- name: Publish packages with trusted publishing
192+
# Both packages keep their existing trusted publisher for release.yml.
193+
run: node scripts/publish-release.ts "${RELEASE_TAG#v}"
116194
env:
117-
RELEASE_VERSION: ${{ inputs.version }}
118195
NPM_CONFIG_CACHE: ${{ runner.temp }}/npm-cache
119-
NPM_CONFIG_PROVENANCE: "true"
120-
121-
- name: Create GitHub release
122-
run: |
123-
version="$RELEASE_VERSION"
124-
latest=true
125-
if [[ "${version%%.*}" == 9 ]]; then
126-
latest=false
127-
fi
128-
gh release create "v${version}" --target "$(git rev-parse HEAD)" --title "NitroSQLite ${version}" --latest="$latest" --notes-file "$RUNNER_TEMP/release-notes.md"
129-
env:
130-
GH_TOKEN: ${{ github.token }}
131-
RELEASE_VERSION: ${{ inputs.version }}
196+
- uses: actions/upload-artifact@v7
197+
with:
198+
name: release-notes
199+
path: ${{ runner.temp }}/release-notes.md
132200

133201
build-android:
134-
needs: release
202+
needs: publish
203+
permissions:
204+
contents: write
135205
uses: ./.github/workflows/build-android-release.yml
136206
with:
137-
tag: v${{ inputs.version }}
207+
tag: ${{ needs.publish.outputs.tag }}
208+
209+
announce:
210+
name: Publish GitHub release with notes and APK
211+
needs: [publish, build-android]
212+
runs-on: ubuntu-24.04
213+
permissions:
214+
contents: write
215+
steps:
216+
- uses: actions/download-artifact@v8
217+
with:
218+
name: release-notes
219+
- name: Publish draft release
220+
env:
221+
GH_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.RELEASE_IT_GITHUB_TOKEN }}
222+
GH_REPO: ${{ github.repository }}
223+
RELEASE_TAG: ${{ needs.publish.outputs.tag }}
224+
RELEASE_LATEST: ${{ needs.publish.outputs.latest }}
225+
run: |
226+
gh release edit "$RELEASE_TAG" --draft=false --title "NitroSQLite ${RELEASE_TAG#v}" --latest="$RELEASE_LATEST" --notes-file release-notes.md

‎.github/workflows/test-release.yml‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Test release automation
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- "scripts/**"
7+
- "*release*.json"
8+
- "package.json"
9+
- "bun.lock"
10+
- ".github/workflows/release.yml"
11+
- ".github/workflows/test-release.yml"
12+
workflow_dispatch:
13+
14+
permissions:
15+
contents: read
16+
17+
jobs:
18+
test:
19+
runs-on: ubuntu-24.04
20+
steps:
21+
- uses: actions/checkout@v7
22+
- uses: actions/setup-node@v7
23+
with:
24+
node-version: 24
25+
package-manager-cache: false
26+
- uses: oven-sh/setup-bun@v2
27+
with:
28+
bun-version: 1.3.1
29+
- run: bun install --frozen-lockfile
30+
- run: bun run test:release
31+
- run: bun run typecheck:release

‎.release-please-manifest.json‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
".": "10.1.0"
3+
}

0 commit comments

Comments
 (0)