-
Notifications
You must be signed in to change notification settings - Fork 4
chore(release): add CI release pipeline with npm provenance #52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| name: Release | ||
|
|
||
| # Publishing is driven by a version tag, not by a commit pushed from CI. | ||
| # `main` requires pull request reviews with `enforce_admins` enabled, so nothing — | ||
| # including github-actions[bot] — can push a release commit to it. The version | ||
| # bump therefore lands through a normal reviewed pull request, and pushing the | ||
| # matching tag afterwards triggers the publish. | ||
| on: | ||
| push: | ||
| tags: | ||
| - 'v[0-9]+.[0-9]+.[0-9]+' | ||
| - 'v[0-9]+.[0-9]+.[0-9]+-*' | ||
| # Rehearsal against the current branch. Always a dry run: it validates the | ||
| # version, changelog and full gate without tagging or publishing anything. | ||
| workflow_dispatch: | ||
|
|
||
| concurrency: | ||
| group: release-${{ github.ref }} | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| release: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| # Create the GitHub Release for the pushed tag. | ||
| contents: write | ||
| # Mint the OIDC token npm exchanges for a short-lived publish token, and | ||
| # sign the provenance attestation. | ||
| id-token: write | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||
| with: | ||
| # Full history so conventional-changelog can build the release notes | ||
| # from every commit since the previous tag. | ||
| fetch-depth: 0 | ||
| persist-credentials: false | ||
|
|
||
| - name: Setup Bun | ||
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | ||
| with: | ||
| bun-version: latest | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | ||
| with: | ||
| # Node 24 ships npm >= 11.5.1, required for npm trusted publishing. | ||
| # `registry-url` is deliberately omitted: it writes an `_authToken` | ||
| # entry into .npmrc, which makes npm assume classic token auth and skip | ||
| # the OIDC flow. The registry is already pinned by publishConfig. | ||
| node-version: 24 | ||
|
|
||
| - name: Verify npm supports trusted publishing | ||
| run: | | ||
| npm_version="$(npm --version)" | ||
| required=11.5.1 | ||
| echo "npm $npm_version (need >= $required)" | ||
| if [ "$(printf '%s\n%s\n' "$required" "$npm_version" | sort -V | head -n1)" != "$required" ]; then | ||
| echo "::error::npm $npm_version is too old for trusted publishing (need >= $required)." | ||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Install dependencies | ||
| run: bun install --frozen-lockfile | ||
|
|
||
| - name: Resolve version | ||
| id: version | ||
| run: | | ||
| version="$(node -p "require('./package/package.json').version")" | ||
| echo "package.json version: $version" | ||
| echo "version=$version" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Verify tag matches package.json | ||
| if: github.event_name == 'push' | ||
| env: | ||
| VERSION: ${{ steps.version.outputs.version }} | ||
| TAG: ${{ github.ref_name }} | ||
| run: | | ||
| if [ "$TAG" != "v$VERSION" ]; then | ||
| echo "::error::Tag $TAG does not match package.json version $VERSION. Bump the version on main first." | ||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Verify CHANGELOG entry exists | ||
| env: | ||
| VERSION: ${{ steps.version.outputs.version }} | ||
| run: | | ||
| if ! grep -qE "^## +${VERSION//./\\.}( |$)" CHANGELOG.md; then | ||
| echo "::error::CHANGELOG.md has no '## $VERSION' section. Add the release notes before publishing." | ||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Verify version is not already published | ||
| env: | ||
| VERSION: ${{ steps.version.outputs.version }} | ||
| run: | | ||
| if npm view "react-native-better-maps@$VERSION" version >/dev/null 2>&1; then | ||
| echo "::error::react-native-better-maps@$VERSION is already on npm." | ||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Codegen | ||
| run: bun run nitrogen | ||
|
|
||
| - name: Lint | ||
| run: bun run lint | ||
|
|
||
| - name: Typecheck | ||
| run: bun run typecheck | ||
|
|
||
| - name: Build | ||
| run: bun run build | ||
|
|
||
| - name: Test | ||
| run: bun run --filter react-native-better-maps test | ||
|
|
||
| - name: Publish | ||
| working-directory: package | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| # --no-increment: the version already lives in package.json, reviewed | ||
| # and merged through a pull request. | ||
| # --no-git: the tag is pushed by the maintainer; CI never writes to git. | ||
| args=(--ci --no-increment --no-git) | ||
| if [ "${{ github.event_name }}" != 'push' ]; then | ||
| args+=(--dry-run) | ||
| echo 'Rehearsal run — nothing will be tagged or published.' | ||
| fi | ||
| bunx release-it "${args[@]}" | ||
|
Comment on lines
+117
to
+130
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- release-it npm publish configuration ---'
sed -n '1,240p' package/.release-it.json
printf '%s\n' '--- configured npm tag arguments ---'
rg -n -C 3 --glob 'package.json' --glob '.release-it.json' \
'publishArgs|npm[.]tag|"tag"|--tag|prerelease' packageRepository: gmi-software/react-native-better-maps Length of output: 1017 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- workflow context ---'
sed -n '1,145p' .github/workflows/release.yml
printf '%s\n' '--- package metadata ---'
sed -n '1,180p' package/package.json
printf '%s\n' '--- deterministic prerelease/tag check ---'
python3 - <<'PY'
import json
from pathlib import Path
release = json.loads(Path("package/.release-it.json").read_text())
package = json.loads(Path("package/package.json").read_text())
publish_args = release.get("npm", {}).get("publishArgs", [])
version = package.get("version", "")
print("version:", version)
print("is_prerelease:", "-" in version.split("+", 1)[0])
print("publishArgs:", publish_args)
print("has_tag_option:", any(a == "--tag" or a.startswith("--tag=") for a in publish_args))
print("has_non_latest_tag:", any(
(a.startswith("--tag=") and a.split("=", 1)[1] != "latest")
for a in publish_args
))
PYRepository: gmi-software/react-native-better-maps Length of output: 8556 🌐 Web query:
💡 Result: By default, the npm publish command automatically assigns the latest dist-tag to the version being published unless you explicitly specify a different tag using the --tag option [1][2][3]. This behavior applies even to pre-release versions (e.g., 1.0.0-beta.1), which can cause them to become the default version installed by users if they run npm install [4][5]. To avoid this, you should use the --tag flag to specify a non-latest tag (such as beta or next) when publishing pre-release versions [1][3]. Regarding release-it, if you are using it to automate your publishing process, you can configure the distribution tag and pass extra arguments to the underlying npm publish command via the configuration file (e.g.,.release-it.json or in package.json) [6][7][8]. To control the tag, use the npm.tag option [6][9]: { "npm": { "tag": "beta" } } To pass extra arguments to the publish command, use the npm.publishArgs option [6][9]: { "npm": { "publishArgs": ["--tag", "beta"] } } Note that npm.tag is the dedicated setting for managing the dist-tag, while npm.publishArgs provides a way to pass any additional arguments directly to the npm publish command [6][7]. Citations:
Set a non- The workflow accepts prerelease tags, but 🤖 Prompt for AI Agents |
||
|
|
||
| - name: Summary | ||
| if: always() | ||
| env: | ||
| VERSION: ${{ steps.version.outputs.version }} | ||
| run: | | ||
| { | ||
| echo "### Release v$VERSION" | ||
| echo | ||
| if [ "${{ github.event_name }}" != 'push' ]; then | ||
| echo "Rehearsal only — nothing was published." | ||
| else | ||
| echo "- [npm](https://www.npmjs.com/package/react-native-better-maps/v/$VERSION)" | ||
| echo "- [GitHub Release](${{ github.server_url }}/${{ github.repository }}/releases/tag/v$VERSION)" | ||
| fi | ||
| } >> "$GITHUB_STEP_SUMMARY" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| # Changelog | ||
|
|
||
| All notable changes to this project are documented in this file. | ||
|
|
||
| The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), | ||
| and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||
|
|
||
| ## 1.1.0 | ||
|
|
||
| ### Behavior changes | ||
|
|
||
| Two changes alter runtime behavior without changing any type signatures, so your | ||
| code keeps compiling but may behave differently after upgrading. | ||
|
|
||
| **`onRegionChange` and `onRegionChangeComplete` now fire once per gesture** | ||
|
|
||
| In 1.0.0 these fired repeatedly while the map was moving, and also fired for | ||
| programmatic camera updates. Now: | ||
|
|
||
| - `onRegionChange` fires **once** when a user-initiated region change **begins** | ||
| - `onRegionChangeComplete` fires **once** when the user gesture **ends** | ||
| - Programmatic updates (`setCamera`, `animateCamera`, `fitToCoordinates`) no | ||
| longer emit either callback | ||
|
|
||
| If you relied on a continuous stream of region updates — a live coordinate | ||
| readout, or a "search this area" button that re-renders while panning — move that | ||
| work to `onRegionChangeComplete`, which now marks the end of the gesture: | ||
|
|
||
| ```tsx | ||
| // Before: fired continuously during the gesture | ||
| <MapView onRegionChange={(region) => setSearchArea(region)} /> | ||
|
|
||
| // After: fires once when the user stops moving the map | ||
| <MapView onRegionChangeComplete={(region) => setSearchArea(region)} /> | ||
| ``` | ||
|
|
||
| **`MapViewRef` camera methods now return `Promise<void>`** | ||
|
|
||
| `setCamera`, `animateCamera`, and `fitToCoordinates` previously returned `void`. | ||
| Existing call sites still compile, but linters configured with | ||
| `@typescript-eslint/no-floating-promises` will now flag them, and any custom | ||
| implementation or test mock of `MapViewRef` must be updated to match. | ||
|
|
||
| ```tsx | ||
| // Await the call, or explicitly ignore the promise | ||
| await mapRef.current?.animateCamera(camera, 300); | ||
| ``` | ||
|
|
||
| ### Features | ||
|
|
||
| - Add native POI press events with provider-specific payloads | ||
| (`onPoiPress`, `PoiPressEvent`, `ApplePoiPressEvent`, `GooglePoiPressEvent`) | ||
| ([#36](https://github.com/gmi-software/react-native-better-maps/pull/36)) | ||
| - Add Expo SDK 57 support | ||
| ([#49](https://github.com/gmi-software/react-native-better-maps/pull/49)) | ||
| - Rework map region change handling and camera update logic; programmatic | ||
| updates now skip no-op native calls | ||
| ([#48](https://github.com/gmi-software/react-native-better-maps/pull/48)) | ||
|
|
||
| ### Bug Fixes | ||
|
|
||
| - **ios:** Remove `main.sync` from `HybridMapView` and make camera APIs async, | ||
| fixing main-thread deadlocks | ||
| ([#45](https://github.com/gmi-software/react-native-better-maps/pull/45)) | ||
| - **ios:** Fix threading issues in map view ownership | ||
| ([#43](https://github.com/gmi-software/react-native-better-maps/pull/43)) | ||
| - **android:** Align SDK versions with the nitro-modules prefab | ||
| ([#41](https://github.com/gmi-software/react-native-better-maps/pull/41)) | ||
| - Fix failure on first-time build | ||
| ([#39](https://github.com/gmi-software/react-native-better-maps/pull/39)) | ||
|
|
||
| ## 1.0.0 | ||
|
|
||
| Initial public release: high-performance maps for React Native built on Nitro | ||
| Modules and the New Architecture, with Apple Maps and Google Maps providers on | ||
| iOS and Android. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| # Releasing | ||
|
|
||
| Releases are published to npm from CI with | ||
| [provenance](https://docs.npmjs.com/generating-provenance-statements) by the | ||
| [Release workflow](.github/workflows/release.yml). Nothing is published from a | ||
| developer machine, and no long-lived npm token exists anywhere. | ||
|
|
||
| Publishing is triggered by pushing a version tag. CI never writes to git: `main` | ||
| requires pull request reviews with `enforce_admins` enabled, so nothing — not | ||
| even `github-actions[bot]` — can push a release commit to it. The version bump | ||
| goes through a normal reviewed pull request instead, which has the useful side | ||
| effect of putting the changelog in front of a reviewer. | ||
|
|
||
| ## Cutting a release | ||
|
|
||
| **1. Open a release pull request.** Bump the version and write the notes: | ||
|
|
||
| ```bash | ||
| cd package | ||
| npm version 1.1.0 --no-git-tag-version | ||
| ``` | ||
|
|
||
| Then add a matching `## 1.1.0` section to `CHANGELOG.md`, and open a pull request | ||
| with both changes. Review and merge it as usual. | ||
|
|
||
| **2. Rehearse (optional).** Run the **Release** workflow manually from the | ||
| Actions tab. A manual run is always a dry run: it validates the version, the | ||
| changelog and the full gate without publishing. | ||
|
|
||
| **3. Push the tag.** | ||
|
|
||
| ```bash | ||
| git checkout main && git pull | ||
| git tag -a v1.1.0 -m 'v1.1.0' | ||
| git push origin v1.1.0 | ||
| ``` | ||
|
|
||
| The workflow then verifies the tag matches `package/package.json`, that | ||
| `CHANGELOG.md` has a section for it, and that the version is not already on npm; | ||
| runs the full gate; publishes to npm with provenance; and creates the GitHub | ||
| Release with notes generated from the conventional commits since the last tag. | ||
|
|
||
| The iOS podspec reads its version from `package.json`, so there is no second | ||
| version to keep in sync. | ||
|
|
||
| ## Versioning | ||
|
|
||
| The bump is a judgement call, made when you open the release pull request. The | ||
| usual rules apply — `fix:` is a patch, `feat:` a minor, an incompatible API | ||
| change a major — but two cases are easy to get wrong: | ||
|
|
||
| - A commit that is not conventional (for example `Fix threading issues on ios`) | ||
| is invisible to the generated release notes. Add it to the changelog by hand. | ||
| - A change in runtime behavior that keeps the same types — such as a callback | ||
| that starts firing once per gesture instead of continuously — breaks consumers | ||
| even though their code still compiles. Either take the major, or ship it as a | ||
| minor with a prominent **Behavior changes** section, as 1.1.0 did. | ||
|
|
||
| ## Changelog | ||
|
|
||
| `CHANGELOG.md` is written by hand, not generated. This is deliberate: the parts | ||
| of a release that matter most — behavior changes, migration snippets, the reason | ||
| a fix exists — cannot be derived from commit subjects. The workflow **fails** if | ||
| there is no `## <version>` section, so the notes cannot be forgotten. | ||
|
|
||
| Notes generated from conventional commits still go into the GitHub Release body, | ||
| so commit-level detail is not lost. | ||
|
|
||
| ## One-time setup: npm trusted publishing | ||
|
|
||
| Publishing uses OIDC, so it only works once npm knows which workflow may publish | ||
| this package. On npmjs.com, open the `react-native-better-maps` package settings | ||
| and add a **GitHub Actions** trusted publisher pointing at: | ||
|
|
||
| - repository: `gmi-software/react-native-better-maps` | ||
| - workflow: `release.yml` | ||
|
|
||
| Until this is configured the publish step fails with an authentication error. If | ||
| an `NPM_TOKEN` secret still exists in the repository, delete it once trusted | ||
| publishing works — it is no longer used. | ||
|
|
||
| ## Notes on the configuration | ||
|
|
||
| A few settings exist for non-obvious reasons: | ||
|
|
||
| - `npm.skipChecks: true` in `package/.release-it.json` — release-it otherwise | ||
| runs `npm whoami` at startup, which fails under trusted publishing because the | ||
| token is only minted at publish time. | ||
| - The workflow does **not** set `registry-url` on `actions/setup-node` — doing so | ||
| writes an `_authToken` entry into `.npmrc`, which makes npm assume classic | ||
| token auth and skip the OIDC flow. The registry is pinned by `publishConfig`. | ||
| - `release-it` runs with `--no-increment --no-git`: the version is already | ||
| committed and the tag already pushed, so CI only publishes and creates the | ||
| release. | ||
|
|
||
| ## Inspecting a release locally | ||
|
|
||
| A local publish would produce a package without provenance, so it is not | ||
| supported. To see what a release would do: | ||
|
|
||
| ```bash | ||
| cd package | ||
| bunx release-it --no-increment --no-git --dry-run | ||
| ``` |
Uh oh!
There was an error while loading. Please reload this page.