Skip to content

chore(release): add scripted version bump and store builds - #107

Open
i-kumar wants to merge 1 commit into
navigation-hubfrom
release-script
Open

chore(release): add scripted version bump and store builds#107
i-kumar wants to merge 1 commit into
navigation-hubfrom
release-script

Conversation

@i-kumar

@i-kumar i-kumar commented Aug 12, 2026

Copy link
Copy Markdown
Member

Summary

Cutting a MaizeBus release is currently a manual checklist. This PR replaces it with one command: ./scripts/release.sh patch.

Description

Releasing today means hand-editing four files in four different formats (pubspec.yaml, lib/constants.dart, ios/Runner.xcodeproj/project.pbxproj, android/local.properties) and remembering to point the backend URL back at production. Miss any one of those and nothing complains locally — you find out several minutes later when App Store Connect or Play Console rejects the artifact, or worse, when a build ships pointed at the dev backend.

scripts/release.sh does the version bump, runs pre-flight checks for the mistakes that fail silently, and builds both store artifacts. No app code changes; the script only touches those four files, and only when you run it.

Type of Change

  • New feature (feat)
  • Bug fix (fix)
  • Refactor / code improvement
  • Dependency / build update
  • Documentation
  • Other (explain)

Related Issues

None

Changes Made

Two new files. No existing files modified.

scripts/release.sh

Takes an explicit version (2.0.4) or a bump keyword (patch / minor / major). Build number defaults to current + 1.

Pre-flight checks, chosen because each one catches a failure that is otherwise invisible until upload time:

Check Why it exists
Working tree is clean Standard release hygiene. --allow-dirty overrides.
android/key.properties exists Without it, build.gradle.kts silently falls back to debug signing. The build succeeds and Play Console rejects the bundle minutes later.
Info.plist still uses $(FLUTTER_BUILD_NAME) / $(FLUTTER_BUILD_NUMBER) This is the assumption the whole script rests on (see note below). If it ever stops being true, the script would ship a stale version — so it fails loudly instead.
Exactly one active defaultValue: in constants.dart, pointing at production constants.dart keeps four commented-out backend URLs next to the live one. If the live one isn't production, the script fixes it. If it can't tell which is live, it refuses rather than guessing.

All checks run to completion and report together, so one run tells you everything to fix.

A lower version name warns and prompts instead of hard-failing — re-uploading a build of an already-released version is legitimate. A non-increasing build number is always refused.

Flags: --dry-run, -y, --build N, --skip-ios, --skip-android, --no-build, --allow-dirty, --allow-debug-signing.

.claude/commands/release.md — exposes the script as a /release slash command, so the process is identical whether run by hand or by Claude.

Backend (TypeScript) and Firebase/Shared: no changes.

Note on which version files actually matter. pubspec.yaml is the only load-bearing one. iOS reads it via the generated xcconfig through Info.plist; Android reads it via updateLocalProperties(), which rewrites local.properties on every build anyway. The project.pbxproj and local.properties edits are cosmetic — synced only so the Xcode GUI and a bare Gradle invocation don't display a stale version. This is documented at the top of the script so nobody has to re-derive it.

Testing Done

Flutter:

  • Tested on:
    • iOS Simulator
    • Android Emulator
    • Physical device
    • N/A — no app code changed.

All testing was manual, run against scratch copies of the repo. This branch's own version files were never modified.

What was tested and why

The risky half of this script is the file rewriting. It edits YAML, Dart source, an Xcode pbxproj, and a Java properties file using targeted text substitution rather than a real parser for each format. The failure mode isn't a crash — it's an edit that looks right and produces an artifact that gets rejected at upload, or ships under the wrong version. So every case below diffs the file before and after to confirm the script changed exactly what it meant to and nothing else.

  • project.pbxproj (the riskiest edit) — the same setting names appear in both the Runner and RunnerTests targets, so a naive find-replace would clobber the test target. Verified the patcher touches exactly the same 12 lines the Xcode GUI touches, that RunnerTests keeps its own MARKETING_VERSION = 1.0 / CURRENT_PROJECT_VERSION = 1, that plutil -lint still parses the result, and that two consecutive bumps stay correct.
  • pubspec.yaml, constants.dart, local.properties — exactly one line changed in each, no collateral damage to neighboring keys.
  • Backend URL detection — four variants: production active, dev URL active, two uncommented at once (refused, didn't guess), and indented // comments correctly skipped. The auto-fix rewrites only the live line and leaves all four commented alternates untouched.
  • Argument handling — no argument, 2.0, --build 10 (non-increasing), --build abc, unknown flag, version passed twice. Each gets a specific error message, not a stack trace or a silent default.
  • Downgrade guard — prompts rather than refusing: aborts on n, proceeds with a warning under -y.
  • Chained bumps — 2.0.3 → 2.0.4 → 2.0.5, confirming the script reads back what it wrote last run rather than only working from a pristine checkout.
  • bash 3.2.57 — macOS still ships bash 3.2 as /bin/bash, which is what /usr/bin/env bash resolves to here. Expanding an empty array under set -u errors on that version, and pre-flight results live in an array, so this was verified rather than assumed.
  • End-to-end — a full run in an isolated git repo with a stubbed flutter on PATH. Artifact paths were checked against flutter_tools source rather than against the stub (the stub's paths were hand-written and would otherwise just confirm themselves): build/ios/ipa/*.ipa and build/app/outputs/bundle/release/app-release.aab. This project declares no product flavors, so both hold.

Three bugs found and fixed along the way: the production URL's // terminated perl's s/// early and crashed the backend auto-fix; pre-flight exited on the first failure instead of collecting all of them; --help printed the wrong line range.

Known gaps before the first real run

  • No real build has run. flutter build ipa and flutter build appbundle are invoked verbatim, so the untested surface is the wrapper, not the builds.
  • Not atomic. Files are written in order (pubspec → constants → pbxproj → local.properties → builds). A failure partway leaves the earlier files bumped; recovery is git checkout --.
  • Build failure leaves a dirty tree. set -e exits with the version already bumped. Retrying needs --allow-dirty --build <same number> so the build number isn't consumed twice.
  • --skip-ios / --skip-android individually (only --no-build, which sets both) and the /release command itself are unexercised.

Suggest the first real run be the next actual release rather than a synthetic one. Heads up that it will trip the clean-tree check if a version bump is already sitting uncommitted.

Screenshots / Demo (if UI or notification change)

N/A — no UI change.

Checklist

  • Commit messages follow Conventional Commits
  • PR title follows [type](scope): short description
  • PR target branch is not main and is our current working update branch
  • No print() / debugPrint() / console.log() left in production code
  • Secrets / keys not committed — the diff is two files. android/key.properties is only checked for existence, never read, written, or logged.

@iswheeler iswheeler left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! Thanks for working on this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants