Developing & Committing
How a change goes from an idea to a merged commit on master. What happens after the merge is in Building & Deploying; undoing a release is in Rollback.
The repo uses trunk-based development: master is the only long-lived branch, and every change reaches it through a short-lived branch and a pull request.
| Status | |
|---|---|
| Branches, commits, local checks, PRs | Today |
| PR checks run by GitHub Actions (CI) | CI/CD Phase 1 |
Merge to master deploys to staging | CI/CD Phase 4 |
CI runs the same checks on every PR, but GitHub Free can't make them required. Run them locally before pushing, and don't merge a PR with a red check.
1. Branch
git switch master && git pull
git switch -c feat/payout-csv-export| Prefix | For |
|---|---|
feat/ | New behaviour |
fix/ | A bug |
chore/ | Tooling, dependencies, infra, config |
docs/ | Docs only |
test/ | Tests only |
Keep branches short-lived: a few days, not weeks. A large feature lands as several PRs, each safe to deploy on its own (see Shipping in slices).
2. Develop
- Run the stack locally: Getting Started.
- Schema change? See Migrations before you write it.
- Changed
packages/database,packages/typesorpackages/socket-client? Rebuild it (pnpm --filter <package> build). Every app reads these packages fromdist/, not from source. - Update the docs in
apps/docs/in the same PR. This site is the source of truth; a change that makes a page wrong without fixing it is incomplete.
3. Check locally
TURBO_SCM_BASE=master pnpm turbo run lint check-types test --affected # what CI runs, for what you changed
# or everything:
pnpm check-types
pnpm lint
cd apps/api && pnpm test && pnpm test:db && cd ../.. # test:db needs the test database, see Testing
cd apps/anansi && pnpm test && cd ../..
cd apps/ogun && pnpm test && cd ../..
cd apps/docs && pnpm build && cd ../.. # if you touched docsRun the suites for the apps you changed, plus the API's when you changed packages/database. Details are in Testing.
4. Commit
The history uses Conventional Commits with a scope:
type(scope): what changed, in the imperative
Why it changed, if the subject doesn't say. What breaks without it, what
you considered, anything a reviewer or a future bisect needs.| Types | feat, fix, test, chore, docs, refactor |
| Scopes | the app or package: api, admin, mobile, anansi, ogun, esu, web, db, infra, docs, or a feature area (auth, media, finance) |
Real examples from the log:
fix(anansi): compile src to dist/ so the image's dist/server.js exists
feat(mobile): RevenueCat key per platform
test(finance): end-to-end financial suite, and the payout bugs it foundRules:
- One logical change per commit. A schema change and the code that uses it can share a commit; an unrelated refactor can't.
- Never commit secrets.
.env*,terraform.tfvars, Terraform state and plan files, anddev-notes/are gitignored for a reason. Secrets for the cluster go in a SealedSecret. - Commit the generated migration directory together with the schema change.
- Commit
pnpm-lock.yamlwheneverpackage.jsonchanges.
5. Open a pull request
git push -u origin feat/payout-csv-export
gh pr create --base master --fillPR checklist
- [ ] CI is green (types, lint, tests, migration check, Docker builds)
- [ ] Docs updated in
apps/docs/ - [ ] Migrations are additive, or the PR is labelled
contract-migration(see below) - [ ] API and socket changes are backward compatible with the mobile app version in the stores
- [ ] New environment variables are added to
.env.example, the k8s manifest and, if secret, a SealedSecret, and are documented in Environment Variables - [ ] A change to a shared package lists every service that must be redeployed
- [ ] The PR says how to roll it back if that's anything other than "previous version"
Merge with squash and merge, so master has one commit per PR and every commit on it is a deployable, revertable unit.
Migrations
Rolling back the code is quick; rolling back the database isn't. Prisma has no down-migrations, so every migration must leave the previous release working. That is what keeps code rollback safe.
Allowed in a normal PR (expand):
- add a table, or a nullable column or one with a default
- add an index (
CREATE INDEX CONCURRENTLYfor large tables) - add an enum value
- backfill data
Only in a PR labelled contract-migration (contract), and only one release after the code stopped using the old shape:
- drop a table or column
- rename anything (do it as add → copy → switch reads → drop)
- change a column's type, or make it
NOT NULL - remove an enum value
From CI/CD Phase 1, a check fails any PR whose new migration.sql contains DROP, RENAME or ALTER … TYPE unless it has that label. Worked examples are in Migrations → Rollback-safe migrations.
Shipping in slices
A feature that needs a schema change, an API change and a mobile release ships in this order, each its own deploy:
- Expand the schema (additive migration).
- Backend that writes the new shape and still accepts the old one.
- Mobile release (store review takes days) or OTA update.
- Contract: remove the old shape, once no supported app version uses it.
Each step is safe to roll back on its own, because the step before it is still compatible.
Compatibility rules
- Mobile clients lag. Installed apps run older code for weeks. API responses only gain fields; an endpoint isn't removed or changed in meaning until no supported app version calls it.
- api and esu share a socket contract. Change it backward-compatibly, or deploy both together and roll both back together.
- Shared packages are compiled into each image. A change to
@playpals/dbor@playpals/typesmeans redeploying every service that imports it, from the same commit.
