Skip to content

Keeping changelog.md in sync — process guide

The changelog.md is append-only and curatedsync-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, @Delete decorator added to a controller imported by apps/api/src/app.module.ts or apps/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:

./docs/api/sync-postman.sh

That script:

  1. Snapshots the previous OpenAPI JSON to *.openapi.json.prev.
  2. Re-pulls the live spec from :4000/swagger.json and :4003/swagger.json.
  3. Regenerates both Postman collections.
  4. Validates that the regenerated JSON parses.
  5. Writes a JSON diff to docs/api/changelog-draft-YYYYMMDD.diff.
  6. Prints an endpoint-summary table you can paste straight into the changelog.

2. Review the diff

less docs/api/changelog-draft-$(date +%Y%m%d).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 exhaustive switch. 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, not POST /api/user/me).
  • For each breaking change include a migration notes block 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 wherever buildSwagger(...) 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

python3 docs/api-reference/_gen.py

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

  1. Forgot to start bo. If :4003 is down, sync-postman.sh fails on step 2 with curl: (7) Failed to connect. Start npm run start:dev:bo and retry.
  2. bj/speed-roulette started but no spec extracted. Expected — those apps don't register Swagger. Don't try to scrape them.
  3. info.version not bumped. The diff will look right but consumers can't tell the spec changed by inspecting the version field. Always bump.
  4. Diff says everything changed. Usually means OpenAPI key ordering shifted (Nest re-walked metadata). Use the sort_keys=True normaliser shown above before deciding what changed.
  5. Captcha-guarded endpoint added. Document that the endpoint is callable from local with x-captcha-token: pass and rejected silently in prod without a real reCAPTCHA token — see docs/audits/doppler-perf-audit.md.
  6. OTel coverage regressed. If the new endpoint sits behind BetService / DiceService, cross-link to docs/audits/perf-trace-coverage-audit.md so 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, bumps info.version.
  • Quarterly — re-run sync-postman.sh --skip-pull against 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@5 emits some Error while resolving allOf schema warnings on stderr against this spec (some response DTOs use allOf with conflicting data.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 the bo collection in the Postman UI.
  • The local environment's captchaToken=pass only works because of the NODE_ENV=local bypass at apps/api/src/captcha/google/recaptcha.service.ts:28. The production stub correctly leaves it as {{TBD}}.