Keeping changelog.md in sync — process guide¶
The changelog.md is append-only and curated — sync-postman.sh produces a draft diff, but the changelog entry itself is hand-written so the rationale, breaking-change semantics, and migration notes survive.
When to update¶
Bump the changelog whenever any of these land on the prod branch of ebit-api:
- A new endpoint (
@Get,@Post,@Put,@Patch,@Deletedecorator added to a controller imported byapps/api/src/app.module.tsorapps/bo/src/app.module.ts). - An existing endpoint's request or response DTO changed in a non-backward-compatible way (field removed, field renamed, type narrowed, validation tightened).
- An endpoint moved tags or paths (consumers care about both).
- An endpoint guard tightened (e.g. became admin-only) — that's a breaking change for any client that was calling it.
- A response status code changed.
You do not need a changelog entry for:
- Internal refactors that don't touch the OpenAPI output (renaming a service class, moving DTOs between files, etc.).
- DTO field reordering (OpenAPI is order-insensitive).
- Comment-only changes to controllers.
End-to-end process¶
0. Pre-condition¶
Local stack is up and the prod-branch code is built and running:
cd ebit-api
npm run dockers:infra
npm i && npm run db:migrate:dev && npm run db:seed
npm run start:dev # api on :4000
npm run start:dev:bo # bo on :4003 (in another shell)
1. Run the refresh¶
From the repo root:
That script:
- Snapshots the previous OpenAPI JSON to
*.openapi.json.prev. - Re-pulls the live spec from
:4000/swagger.jsonand:4003/swagger.json. - Regenerates both Postman collections.
- Validates that the regenerated JSON parses.
- Writes a JSON diff to
docs/api/changelog-draft-YYYYMMDD.diff. - Prints an endpoint-summary table you can paste straight into the changelog.
2. Review the diff¶
The diff is OpenAPI JSON, so it's verbose. Useful filters:
# Just the path-level adds/removes
grep -E '^[+-]\s+"/' docs/api/changelog-draft-$(date +%Y%m%d).diff
# Just the schema changes
grep -E '^[+-].*"(type|format|enum|required)":' docs/api/changelog-draft-$(date +%Y%m%d).diff
If the JSON is reordered but semantically identical (this happens when Nest reflection re-walks decorators in a different order), you can normalise both sides before diffing:
python3 -c "import json,sys; print(json.dumps(json.load(open(sys.argv[1])), sort_keys=True, indent=2))" \
docs/api-reference/openapi/api.openapi.json.prev > /tmp/prev.json
python3 -c "import json,sys; print(json.dumps(json.load(open(sys.argv[1])), sort_keys=True, indent=2))" \
docs/api-reference/openapi/api.openapi.json > /tmp/curr.json
diff -u /tmp/prev.json /tmp/curr.json | less
3. Categorise each change¶
Walk every change in the diff and bucket it as:
| Category | Definition | Semver impact |
|---|---|---|
| Breaking | Existing client code stops working without modification. Examples: removed endpoint; required field added to request; field removed from response that wasn't documented as optional; status code change; auth requirement tightened. | Major bump (2.0.0) |
| Additive | A net-new endpoint, an optional new request field, an optional new response field, a relaxed auth check. | Minor bump (1.1.0) |
| Fix | Wording-only change to summary/description, fixed typo in tag, error-response example clarified, no behavioural change. | Patch bump (1.0.1) |
| Deprecation | Endpoint still works but should not be called by new code. | Minor bump + sunset date. |
Edge cases:
- A renamed field is a breaking change even if the new name is "obviously" the same — clients can't deserialize.
- An enum gaining a new value is additive if clients are doing
.includes(known)checks, breaking if they're doing exhaustiveswitch. Document it as additive but flag the risk. - An endpoint moving from one tag to another in the OpenAPI spec is non-breaking for clients (clients don't see tags) but is a navigation change — note it under "Modified endpoints" without bumping.
4. Write the changelog entry¶
Open changelog.md, copy the "Forward template" section near the bottom into a new dated entry, and fill it in. Conventions:
- Reference paths verbatim (
POST /user/me, notPOST /api/user/me). - For each breaking change include a
migration notesblock with a before/after curl or TypeScript snippet. - Cite the controller file (
apps/api/src/<module>/<file>.controller.ts) so readers can grep. - Keep new-endpoints entries to one line each — the detail is in the rendered Markdown reference.
5. Bump the version field¶
Edit info.version in the Nest buildSwagger call so the next regeneration shows the new version in the spec:
apps/api/src/main.ts(or whereverbuildSwagger(...)is called)apps/bo/src/main.ts
Then re-run sync-postman.sh once more so the JSON snapshots and Postman collections carry the new version string.
6. Re-render the Markdown reference¶
This regenerates docs/api-reference/api.md and bo.md. Spot-check that the changed endpoint shows up correctly there.
7. Commit¶
Commit all artefacts in one commit so a git revert rolls back the entire chain:
docs/api-reference/openapi/api.openapi.json
docs/api-reference/openapi/bo.openapi.json
docs/api-reference/api.md
docs/api-reference/bo.md
docs/api/postman/ebit-api.postman_collection.json
docs/api/postman/ebit-bo.postman_collection.json
docs/api/changelog.md
Delete the changelog-draft-YYYYMMDD.diff artefact — it was scratch.
Common pitfalls¶
- Forgot to start
bo. If:4003is down,sync-postman.shfails on step 2 withcurl: (7) Failed to connect. Startnpm run start:dev:boand retry. bj/speed-roulettestarted but no spec extracted. Expected — those apps don't register Swagger. Don't try to scrape them.info.versionnot bumped. The diff will look right but consumers can't tell the spec changed by inspecting the version field. Always bump.- Diff says everything changed. Usually means OpenAPI key ordering shifted (Nest re-walked metadata). Use the
sort_keys=Truenormaliser shown above before deciding what changed. - Captcha-guarded endpoint added. Document that the endpoint is callable from local with
x-captcha-token: passand rejected silently in prod without a real reCAPTCHA token — seedocs/audits/doppler-perf-audit.md. - OTel coverage regressed. If the new endpoint sits behind
BetService/DiceService, cross-link todocs/audits/perf-trace-coverage-audit.mdso the missing manual span is on someone's radar.
Audit cadence¶
- Every release — the engineer cutting the release runs
sync-postman.sh, writes the changelog entry, bumpsinfo.version. - Quarterly — re-run
sync-postman.sh --skip-pullagainst the committed JSON, confirm Postman collections still match. Catches drift introduced by manual edits to the JSON. - Per breaking change — Slack / release-note announcement linking to the changelog entry. Anything tagged "Breaking" must be paired with a migration runbook before the release ships.
Tooling notes¶
openapi-to-postmanv2@5emits someError while resolving allOf schemawarnings on stderr against this spec (some response DTOs useallOfwith conflictingdata.items.type). The conversion succeeds — the warnings affect example generation only and the request/response schemas in the Postman item are still correct.- The Postman collection name embeds the port (
ebit api (port 4000)) so it doesn't collide with thebocollection in the Postman UI. - The local environment's
captchaToken=passonly works because of theNODE_ENV=localbypass atapps/api/src/captcha/google/recaptcha.service.ts:28. The production stub correctly leaves it as{{TBD}}.