Release workflow
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:
| Surface | app.config.ts key | Example |
|---|---|---|
| Version name | top-level version | "1.2.4" |
| Android versionCode | android.versionCode | 8 |
| iOS build number | ios.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 changelogdraft 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 releaseWithout 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
| Flag | Effect |
|---|---|
| (none) | Interactive prompt with the patch proposal as default |
--patch / --minor / --major | Bump the corresponding segment; code +1 |
--code-only | Keep the version name, raise only the code |
--version x.y.z | Explicit version name (code defaults to current + 1) |
--code N | Override the version code |
--yes | Accept the patch proposal without prompting |
--date YYYY-MM-DD | Override the changelog date (default: today) |
--allow-empty | Release with an empty [Unreleased] (code-only re-release) |
--no-commit / --no-tag / --no-sync | Opt out of the automatic commit / tag / bowlerShip push |
--dir <path> | Run against another project directory |
What gets written
| File | Change |
|---|---|
mobile-app/app.config.ts | Top-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.json | The 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 buildFor the surrounding checklist (signing, store listing, assets), see Play Store Assets & Publishing Checklist. For the rest of the CLI, see CLI Reference.
