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 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 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 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 changelog release.
Run the release
From the generated project root:
bowler 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 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 buildFor the surrounding checklist (signing, store listing, assets), see Play Store Assets & Publishing Checklist. For the rest of the CLI, see CLI Reference.
