BowlerKitBowlerKit
ExpoGuides

Release workflow

9/18/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 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 changelog add         # interactive: pick a section + one-line summary
bowler changelog draft       # propose entries from commits since the last v* tag
bowler 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 changelog release.

Run the release

From the generated project root:

bowler 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 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), 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 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.