ADR-0002: release-please owns versioning and the release tag
- Status: Accepted
- Date: 2026-09-11
- Issue: #60 (D1 release mechanics; the grill outcome is the last comment on the issue)
Context
The store rung of the ladder (PLAN.md decisions 3, 12) already existed: a vX.Y.Z tag → the production GitHub Environment reviewer → the EAS Release workflow, which skips itself when the native fingerprint already has a store build. What was missing was who bumps version, writes a changelog and pushes the tag; until now that was a human editing app.config.ts and running git tag. Build numbers were never in question (EAS, appVersionSource: remote, autoIncrement). The forces: one PR per ticket squash-merged with a Conventional Commit title (so the history is already machine-readable), Renovate PRs landing on main on their own, and a native fingerprint that must not move on a JS-only release.
Decision
release-please, in manifest mode with the standard node release type, owns the version:
- Version meaning.
versiontracks every release-please release, OTA-only changes included. The existing fingerprint gate in.eas/workflows/release.ymlturns a tag with an unchanged fingerprint into a green no-op store step. - Two human steps per store release. Merge the release PR (cuts version + changelog + tag), then approve the
productionGitHub Environment (spends money, submits). Neither is automated. - Source of truth =
package.json.app.config.tsreads it. release-please touches onlypackage.jsonandCHANGELOG.md. Nobody hand-editsversion. - Build numbers stay on EAS (
appVersionSource: remote,autoIncrementon production). - Release triggers. Defaults:
feat→ minor,fix/perf/revert→ patch,!/BREAKING CHANGE→ major;chore/docs/ci/test/build/refactor/stylehidden and non-releasing (changelog-sectionsinrelease-please-config.jsonspells this out). Renovate useschore(deps)for every update (:semanticCommitTypeAll(chore)), so dependency bumps never release on their own. - Bootstrap.
.release-please-manifest.jsonseeded at1.0.0;last-release-shaset to themainhead at merge time (5ec4892b) so earlier history is not released retroactively.bun run initresets the manifest andpackage.jsonto1.0.0and dropslast-release-sha(--fresh-git) or points it at the generated project's current HEAD. - Token.
.github/workflows/release-please.ymlreads oneRELEASE_PLEASE_TOKENsecret (contents + pull-requests write). A GitHub App is recommended, a fine-grained PAT is the quick path.GITHUB_TOKENis not acceptable: its events do not trigger the required checks on the release PR or the tag-triggeredrelease.yml. The secret is on the human setup checklist next toEXPO_TOKEN. - Fingerprint finding (verified).
@expo/fingerprinthashesversionby default: bumping it moved the production iOS hashc7aa6066…→907d6e….fingerprint.config.jswithsourceSkips: SourceSkips.ExpoConfigVersionsmakes the bump hash-neutral —24e465debc…before and after the same bump. Every fingerprint path (bun run fingerprint, the EASfingerprintjob,eas build, the expo-updates runtime version) loads that file throughnormalizeOptionsAsync. - Staging deploy on the release commit runs as normal, so the tagged commit always exists as a staging update group and the promoted group's reported version matches the tag.
- Delivery. One PR: config + manifest + workflow,
fingerprint.config.js,app.config.tsreadingpackage.json, the Renovate commit type, theautorelease: pending/autorelease: taggedlabels inscripts/repo-settings.js, the init-script reset with its drift-guard tests, docs and this record.
Consequences
- Adding
fingerprint.config.jschanges the fingerprint once: the merge produces one round of staging builds. No store builds exist yet, so nothing else is invalidated. From then on a version bump is invisible to the fingerprint, andrelease.yml's unchanged-fingerprint skip keeps working for OTA-only releases. - The store version can skip numbers: every release-please release bumps
version, but only the ones with a native change produce a store build, so TestFlight / Play may go 1.2.0 → 1.5.0. That is intended (decision 1) — the version names what is onmain, not what was built. RELEASE_PLEASE_TOKENis a new owner-owed secret; until it exists the workflow fails at its first step with a pointer to the checklist, and no release PR appears. The two labels needbun run repo:settings:apply --only labelsonce.- Hand-edits to
versionand hand-pushed tags are now conventions violations (Conventions → Commits and PR titles); theversion_checkjob in the EAS release still refuses a tag that does not matchpackage.json. - The Renovate change means a dependency bump that users should notice needs a
fix/featcommit of its own to reach the changelog. bun run initgains aversioningstep; the drift guard inscripts/__tests__/init.test.tsfails if the manifest, config orpackage.jsonversionline move.