BowlerKitBowlerKit
ExpoGuides

Release workflow

10/11/2026

Record changes, stamp the next version, tag the release, and produce Play Store release notes with one command.

Every production release follows the same loop: record what changed, pick the next version, stamp it into the project, tag it, and build. BowlerKit turns that loop into a single command — bowler ship changelog release — so the version in app.config.ts, the changelog, git history, and your bowlerShip dashboard never drift apart.

Version convention

BowlerKit uses the Flutter convention x.y.z+N: a semantic version name plus a monotonically increasing integer code. In an Expo project the release command keeps three surfaces of mobile-app/app.config.ts in sync:

Surfaceapp.config.ts keyExample
Version nametop-level version"1.2.4"
Android versionCodeandroid.versionCode8
iOS build numberios.buildNumber (kept equal to the versionCode)"8"

The full string 1.2.4+8 also labels the changelog block and the git tag (v1.2.4+8), so one identifier traces a release from source to store.

Google Play rejects an upload whose versionCode is not higher than the previous one. The release command enforces the store rules before touching any file: the code must rise on every release and the version name must never go down.

Record what changed

Changelog entries live in .bowlerkit/changelog.md (Keep a Changelog format, committed with the project):

bowler ship changelog add         # interactive: pick a section + one-line summary
bowler ship changelog draft       # propose entries from commits since the last v* tag
bowler ship changelog list        # show the changelog

draft maps conventional-commit prefixes to sections (feat → Added, fix → Fixed, remove/revert → Removed, anything else → Changed), skips merge commits, and records commit hashes for traceability. Re-running is safe: commits already recorded are skipped. Entries stay close to the commit subjects on purpose — release notes that read well for end users are produced on bowlerShip, which the changelog mirrors to on every bowler ship changelog release.

Run the release

From the generated project root:

bowler ship changelog release

Without flags the command is interactive — it proposes the next patch bump and asks you to confirm:

Versi baru? (sekarang 1.0.0+1)
  1.0.1+2   Patch (usulan)
  1.1.0+2   Minor
  2.0.0+2   Major
  1.0.0+2   Code only
  > Custom…

After you choose, the command stamps the version, releases the changelog, commits, tags, and prints the store notes:

✓ mobile-app/app.config.ts → 1.2.4+8
✓ .bowlerkit/project.json → 1.2.4+8
✓ .bowlerkit/changelog.md → ## [1.2.4+8] - 2026-09-12
✓ commit chore(release): v1.2.4+8
✓ tag v1.2.4+8

What's new untuk Play Console (versi 1.2.4+8):
- Crash saat refresh
- Mesin kalender Bali

✓ 42 karakter — aman (batas 500 per bahasa).

Flags

FlagEffect
(none)Interactive prompt with the patch proposal as default
--patch / --minor / --majorBump the corresponding segment; code +1
--code-onlyKeep the version name, raise only the code
--version x.y.zExplicit version name (code defaults to current + 1)
--code NOverride the version code
--yesAccept the patch proposal without prompting
--date YYYY-MM-DDOverride the changelog date (default: today)
--allow-emptyRelease with an empty [Unreleased] (code-only re-release)
--no-commit / --no-tag / --no-syncOpt out of the automatic commit / tag / bowlerShip push
--dir <path>Run against another project directory

What gets written

FileChange
mobile-app/app.config.tsTop-level version is rewritten; android.versionCode and ios.buildNumber are updated — keys missing from older templates are inserted into the first top-level android: / ios: blocks
.bowlerkit/project.jsonThe app.version block follows the new release
.bowlerkit/changelog.md[Unreleased] entries move into a ## [x.y.z+N] - date block

Projects generated from an older template may not define versionCode/buildNumber yet. The first release inserts them into the top-level android: and ios: blocks — review the diff once before building.

Git commit and tag

The command commits exactly the release-touched files — anything else you have modified or staged stays untouched — with the message chore(release): v<x.y.z+N>, then creates the matching git tag. The tag is the anchor the next bowler ship changelog draft counts commits from.

If a tag v<x.y.z+N> already exists, the release aborts before any file is modified — that exact version was already released, so pick another. Use --no-commit/--no-tag to manage git yourself; with --no-commit the tag (if any) points at HEAD, which does not yet contain the release.

Sync to bowlerShip

When the project is bound (bowler ship connect, then bowler ship add), the release pushes a snapshot automatically: the new version appears on the bowlerShip dashboard and the released changelog shows up on its Changelog page. Without a binding the command prints the manual follow-up (bowler ship sync). Skip the push with --no-sync.

Code-only re-release

Sometimes the store rejects an upload, or a build fix does not merit a changelog line. Re-release the same version name with a fresh code:

bowler ship changelog release --code-only --allow-empty

--allow-empty permits an empty [Unreleased]; the resulting version block carries no entries and stays invisible on the bowlerShip changelog page.

After the release

Build the production artifacts:

pnpm build:cloud                                    # EAS cloud build
eas build --platform android --profile production   # explicit target
pnpm build:local:prod                               # local production build

For the surrounding checklist (signing, store listing, assets), see Play Store Assets & Publishing Checklist. For the rest of the CLI, see CLI Reference.