Repository navigation
feat: add OpenAPI contract drift check to CI - #192
Merged
preshotta merged 11 commits intoOct 1, 2026
Merged
Conversation
|
@Prinzolumide Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
Code Review & Auto-Merge — @dorismaduegbunam 🚀Thank you @Prinzolumide for your contribution!
Verified code changes and repository requirements. Merging pull request. |
Code Review & Auto-Merge — @dorismaduegbunam 🚀Thank you @Prinzolumide for your contribution!
Verified code changes and repository requirements. Merging pull request. |
Code Review & Auto-Merge — @dorismaduegbunam 🚀Thank you @Prinzolumide for your contribution!
Verified code changes and repository requirements. Merging pull request. |
Code Review & Auto-Merge — @dorismaduegbunam 🚀Thank you @Prinzolumide for your contribution!
Verified code changes and repository requirements. Merging pull request. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Adds the missing OpenAPI contract drift gate.
docs/openapi.jsonis generated from the NestJS controller decorators, but nothing verified that the committed artefact still matched them, so a controller change merged without re-runningnpm run openapi:exportsilently published a stale contract to the Redoc reference and to the SDK generators that consume the spec.This extracts the document-building code into a single reusable module, adds a structural drift check that builds the document in memory and diffs it against
docs/openapi.json, wires that check into GitHub Actions on pull requests, unit-tests the comparison logic, and documents the workflow.Related Issue
scripts/— Add automated API contract documentation drift check to CI workflow (#148)Changes
One source of truth for the document
[ADD]
scripts/lib/openapi-document.tsgenerateOpenApiDocument()boots the Nest app exactly like the old export script (TypeORMinitializemocked so it works offline,logger: false,abortOnError: false, app closed afterwards), builds the document with the sameDocumentBuildermetadata and{ deepScanRoutes: true, operationIdFactory: (_controllerKey, methodKey) => methodKey }, and returns it. Nothing is written to disk.buildOpenApiConfig(),OPENAPI_DOCUMENT_OPTIONS,serializeOpenApiDocument()andwriteOpenApiDocument()keep the title/description/servers/security schemes/tags and the 2-space JSON format in one place.[MODIFY]
scripts/export-openapi.tsgenerateOpenApiDocument()+writeOpenApiDocument(); same✅ OpenAPI JSON written to …line, sameprocess.exit(0)/process.exit(1)behaviour, and the emitted document stays byte-identical (the extractedDocumentBuilderchain matches the previous implementation character for character apart from whitespace).The drift check
[ADD]
src/common/contract/openapi-drift.tsparameters,securityandserversare ordered), diffing of paths, HTTP methods, operations and their members, component schemas, and remaining document members, plus the human/JSON report and the loud failure path for an unusable artefact.[ADD]
scripts/check-openapi-drift.ts--expect <path>,--json,--help. Exit codes:0in sync,1drift or unusable artefact,2usage error.[ADD]
src/common/contract/openapi-drift.spec.tssummary,parameters,responses), changed and added component schemas, changed document members, report formatting, and every artefact-loading failure mode (missing, empty, invalid JSON, nopaths, no operations, non-object).CI, docs and the baseline
[MODIFY]
.github/workflows/build-check.ymlopenapi-driftjob that runs onpull_requestand on pushes tomain/master:npm ci, the drift unit tests, then the drift check, using the same offline bootstrap env as theopenapijob (NODE_ENV,DATABASE_URL,JWT_SECRET,PORT). The existingbuild,openapiandpagesjobs are untouched.[MODIFY]
CONTRIBUTING.mdnpm run openapi:export, thatdocs/openapi.jsonis the committed baseline, what the comparison does and does not treat as drift, and how the CI job behaves.[MODIFY]
.gitignoredocs/openapi.jsonis no longer ignored. The gate needs a committed baseline, andgit addof an ignored path is a silent no-op — that is exactly why "regenerate the spec and commit it" could not happen before.Verification Results
CI on this PR (head
51a4460)openapi-drift→ Test the OpenAPI drift comparison logic:20 passed, 20 total— the spec runs under the repo's real Jest/ts-jest setup in CI.openapi-drift→ Check OpenAPI spec drift: fails, by design, with the intended message, because the baseline has never been committed:buildfails atnpx tsc --noEmit, but with exactly the same 10 errors thatmainalready fails with (.github:2/src/common/versioning/api-deprecation.spec.ts,src/common/release/release-checklist.ts,src/common/database/**,src/common/cache/**). Diffing the check-run annotations of this branch againstmainshows zero errors unique to this PR and no error in any file it touches. That job is red onmaintoday and is not affected by this change.Local verification (no dependencies installed in this environment)
Design decisions & tradeoffs
docs/openapi.jsonis therefore not drift, while any change to a path, method, operation member, component schema or document member is. Diffing raw text would fail on formatting noise and teach people to bypass the gate.docs/openapi.jsonmust never be a way to make the gate pass, so it exits1with the exact commands to fix it instead of reporting "no drift".+ /api/v1/…line rather than one line per HTTP method; operations are only compared for paths that exist on both sides, so the report stays actionable.npx ts-node, notnpm run. The issue asks for annpm run openapi:check-driftscript, but this change intentionally does not touchpackage.json, so the check is run asnpx ts-node -r tsconfig-paths/register scripts/check-openapi-drift.ts— exactly what the CI job andCONTRIBUTING.mduse. Wiring up the alias is a one-line follow-up whenever the manifest is next edited.src/common/contract/rather thanscripts/lib/.jest.config.jssetsrootDir: 'src'withtestRegex: '.*\\.spec\\.ts$', so a spec underscripts/would never be collected bynpm test; the pure module sits next to the existing contract-drift spec so it is unit-testable by the existing Jest setup, while the CLIs underscripts/import it.docs/openapi.jsonhas never been committed (it is generated and gitignored today), so the first run of the new job fails and prints the export/commit instructions — that is the intended signal, and it is visible in this PR's own CI log. Exporting it at the branch tip is the one-time backfill that turns the gate green. After that, any controller change merged without re-exporting fails the pull request. I could not produce that artefact here: building it faithfully requires installing the repo's dependencies and booting the Nest app, which this environment does not allow.docs/openapi.jsonscripts/check-openapi-drift.tsuses the shared builder and writes nothingdocs/openapi.jsonis out of sync1, naming every added/removed/changed path and operationopenapi-driftjob onpull_requestand pushes, withNODE_ENV/DATABASE_URL/JWT_SECRET/PORTCONTRIBUTING.mdnpm run openapi:exportsrc/common/contract/openapi-drift.spec.ts, run by the new CI jobnpm run openapi:check-driftaliaspackage.jsondeliberately untouched; the script is run withnpx ts-node …Closes #148