@subhamxcod/apiguard 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +191 -61
  2. package/package.json +20 -1
package/README.md CHANGED
@@ -1,48 +1,140 @@
1
1
  # API Guard
2
2
 
3
- > Detect breaking changes between OpenAPI specifications (3.0 & 3.1) so API contracts never
4
- > silently break downstream consumers.
3
+ > Production-grade OpenAPI breaking-change detection for CI/CD.
5
4
 
6
5
  [![Test](https://github.com/subhamxaura/apiguard/actions/workflows/test.yml/badge.svg)](./.github/workflows/test.yml)
7
- [![Lint](https://github.com/subhamxaura/apiguard/actions/workflows/lint.yml/badge.svg)](./.github/workflows/lint.yml)
6
+ [![Release](https://github.com/subhamxaura/apiguard/actions/workflows/release.yml/badge.svg)](./.github/workflows/release.yml)
7
+ [![npm version](https://img.shields.io/npm/v/@subhamxcod/apiguard.svg)](https://www.npmjs.com/package/@subhamxcod/apiguard)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)
8
9
 
9
- **API Guard** diffs two OpenAPI specs — a _baseline_ and a _current_ — and classifies every
10
- difference as **breaking** (an existing conformant client will fail), **non-breaking but
10
+ **API Guard** diffs two OpenAPI specifications — a _baseline_ and a _current_ — and classifies
11
+ every difference as **breaking** (an existing conformant client will fail), **non-breaking but
11
12
  notable**, or **informational**. It ships as a CLI, a TypeScript library, and a GitHub Action.
12
13
 
14
+ ## Why API Guard?
15
+
16
+ APIs evolve, and small spec changes can silently break the consumers that depend on them:
17
+ frontend applications, generated SDKs, partner integrations, and downstream services. A removed
18
+ response field or a changed property type doesn't fail your build — it fails your users, in
19
+ production, at runtime.
20
+
21
+ API Guard moves that failure left. It compares your OpenAPI documents **semantically** — not
22
+ textually — and reports every change that would break an existing client, before the code ships:
23
+
24
+ ```
25
+ old spec + new spec → API Guard → breaking changes (or a clean pass)
26
+ ```
27
+
13
28
  ## Features
14
29
 
15
30
  - **81 detection rules** across operations, parameters, request/response bodies, headers,
16
31
  schemas, security, callbacks, and webhooks — see the [generated rule reference](./docs/rules.md)
17
- - **Semantic, not textual**: YAML formatting, key order, enum order, and `$ref` aliasing are
18
- never false positives; `nullable: true` (3.0) equals `type: ["string","null"]` (3.1)
19
- - **Deterministic**: same inputs → byte-identical output, on any machine, any locale
20
- - **Configurable**: per-rule severities, per-context overrides (`ruleId@context`), path/
21
- operation/schema/rule ignore lists in `apiguard.yaml`
22
- - **Semver advice**: suggests major/minor/patch from the detected change kinds
23
- - **Three output formats**: terminal (color), JSON (machine contract), markdown (PR-friendly)
24
- - **GitHub Action**: merge-base baseline by default, PR summary, `fail-on` policy
32
+ - **OpenAPI 3.0 and 3.1** — unified normalization: `nullable: true` (3.0) equals
33
+ `type: ["string", "null"]` (3.1)
34
+ - **Semantic, not textual** — YAML formatting, key order, enum order, and `$ref` aliasing are
35
+ never false positives
36
+ - **Deterministic** — same inputs produce byte-identical output, on any machine, any locale
37
+ - **Configurable** — per-rule severities, per-context overrides (`ruleId@context`), and
38
+ path/operation/schema/rule ignore lists in `apiguard.yaml`
39
+ - **Semver advice** — suggests major/minor/patch from the detected change kinds
40
+ - **Three output formats** — terminal (color), JSON (machine contract), markdown (PR-friendly)
41
+ - **GitHub Action** — merge-base baseline by default, PR job summary, `fail-on` policy
42
+ - **TypeScript library** — typed, pure API for embedding in your own tooling
25
43
 
26
44
  ## Quick start
27
45
 
28
46
  ```bash
29
- pnpm add -D @subhamxcod/apiguard
30
- apiguard init # scaffold apiguard.yaml
31
- apiguard diff openapi.v1.yaml openapi.yaml
47
+ npm install --save-dev @subhamxcod/apiguard
48
+ # or: pnpm add -D @subhamxcod/apiguard
49
+ # or: yarn add -D @subhamxcod/apiguard
50
+
51
+ apiguard init . --examples # scaffold apiguard.yaml + two sample specs
52
+ apiguard diff examples/baseline.yaml examples/current.yaml
53
+ ```
54
+
55
+ Or run it directly without installing:
56
+
57
+ ```bash
58
+ npx @subhamxcod/apiguard diff old.yaml new.yaml
32
59
  ```
33
60
 
34
- Example output:
61
+ ## What gets detected
62
+
63
+ Remove a required response property between two releases:
35
64
 
65
+ ```yaml
66
+ # before # after
67
+ responses: responses:
68
+ '200': '200':
69
+ content: content:
70
+ application/json: application/json:
71
+ schema: schema:
72
+ type: object type: object
73
+ required: [id, name, email] required: [id, name]
74
+ properties: properties:
75
+ id: { type: string } id: { type: string }
76
+ name: { type: string } name: { type: string }
77
+ email: { type: string }
36
78
  ```
37
- ERROR /users/{id} removed-method
38
- operation DELETE was removed; existing clients calling it will get 404s
39
- → keep the operation and mark it deprecated instead
40
79
 
41
- 2 breaking, 3 non-breaking, 12 info — suggest major
42
- verdict: FAIL (exit 1)
80
+ Running `apiguard diff old.yaml new.yaml` exits non-zero and prints:
81
+
82
+ ```text
83
+ GET /users/{id}
84
+ ERROR Response property `email` was removed
85
+ Suggestion: stop relying on `email`; it will no longer be returned
86
+ ❌ Breaking API changes found
87
+
88
+ Suggested version bump: MAJOR
43
89
  ```
44
90
 
45
- ## The GitHub Action (recommended)
91
+ The `property-removed` rule fired for the response body; removals are resolved per the §10.5
92
+ context table (request/response/parameter/component), so the same rule can downgrade to a
93
+ warning where the context allows it. The full catalog of 81 rules with severities and trigger
94
+ conditions lives in the [rule reference](./docs/rules.md).
95
+
96
+ ## CLI
97
+
98
+ ### `apiguard diff <old-spec> <new-spec>`
99
+
100
+ Diff two OpenAPI specs and report breaking changes. This is the core command.
101
+
102
+ | Flag | Effect |
103
+ | ------------------------ | -------------------------------------------------------- |
104
+ | `--json`, `--markdown` | Output format (default: colorized terminal) |
105
+ | `--fail-on <level>` | `error` (default) \| `warning` \| `never` |
106
+ | `--strict` | Shorthand for `--fail-on warning --include-non-breaking` |
107
+ | `--include-non-breaking` | Also render warning + info changes |
108
+ | `--show-ignored` | Show changes suppressed by ignore rules, tagged |
109
+ | `--suggest-version` | Print the suggested semver bump after the report |
110
+ | `--config <path>` | Explicit config file (missing file: exit 2) |
111
+ | `--output <path>` | Also write the full report to a file |
112
+ | `--quiet` | Print only the one-line verdict |
113
+ | `--no-color` | Disable ANSI color (auto-detected for non-TTY) |
114
+ | `--verbose` | Log parse/config steps to stderr |
115
+
116
+ **Exit codes:** `0` pass · `1` changes met the fail-on policy · `2` usage/config error ·
117
+ `3` invalid spec · `4` internal error (please report).
118
+
119
+ Machine contract: `--json` emits a versioned report (`schemaVersion: "1.0"`) with
120
+ `summary.bySeverity`, per-change `ruleId`/`severity`/`kind`, and stable change ids.
121
+
122
+ ### `apiguard validate <spec>`
123
+
124
+ Load and semantically validate one spec. `--strict` exits 1 when semantic warnings exist.
125
+
126
+ ### `apiguard init [dir]`
127
+
128
+ Scaffold an `apiguard.yaml` config template. `--examples` also writes two sample specs for a
129
+ first diff run; `--force` overwrites existing files.
130
+
131
+ Full reference: [CLI guide](./docs/cli.md).
132
+
133
+ ## GitHub Action
134
+
135
+ The Action wraps the same engine for pull requests: it resolves the baseline automatically from
136
+ the merge-base with your default branch, posts the report as a job summary, and fails the step
137
+ when breaking changes exist.
46
138
 
47
139
  ```yaml
48
140
  # .github/workflows/api-guard.yml
@@ -54,33 +146,18 @@ jobs:
54
146
  steps:
55
147
  - uses: actions/checkout@v4
56
148
  with: { fetch-depth: 0 } # merge-base needs history
57
- - uses: apiguard/action@v1
149
+ - uses: subhamxaura/apiguard@v1 # tracks the latest v1.x release
58
150
  with:
59
- current: openapi.yaml # default; the PR's spec
151
+ current: openapi.yaml # the PR's spec
60
152
  # baseline: omitted → the spec at the merge-base with origin/main
61
153
  ```
62
154
 
63
- The Action posts a job summary table, emits `breaking` / `warnings` / `verdict` /
64
- `suggested-version` outputs, and fails the step when breaking changes exist
65
- (`fail-on: never` to only report). Full input/output reference:
155
+ The Action emits `breaking`, `warnings`, `verdict`, and `suggested-version` outputs, and writes
156
+ a deterministic job summary. To report without blocking (e.g. during a migration), set
157
+ `fail-on: never` and branch on the outputs. Full input/output reference:
66
158
  [GitHub Action guide](./docs/github-action.md).
67
159
 
68
- ## CLI
69
-
70
- ```
71
- apiguard diff <old-spec> <new-spec> # the core command
72
- apiguard validate <spec> # load + semantic validation
73
- apiguard init [dir] # scaffold a config
74
- ```
75
-
76
- Useful `diff` flags: `--json`, `--markdown`, `--fail-on warning|never`, `--strict`,
77
- `--include-non-breaking`, `--show-ignored`, `--suggest-version`, `--output <file>`,
78
- `--quiet`, `--no-color`. Full flag semantics: [CLI guide](./docs/cli.md).
79
-
80
- **Exit codes:** `0` pass · `1` changes met the fail-on policy · `2` usage/config error ·
81
- `3` invalid spec · `4` internal error (please report).
82
-
83
- ## Library
160
+ ## TypeScript library
84
161
 
85
162
  ```ts
86
163
  import { analyze } from '@subhamxcod/apiguard';
@@ -91,11 +168,15 @@ if (report.summary.bySeverity.error > 0) {
91
168
  }
92
169
  ```
93
170
 
94
- The engine is pure: no stdout, no process.exit, no timestamps in report bodies.
95
- [Library guide](./docs/library.md).
171
+ The library also exports `suggestVersion` for standalone semver advice and `validateSpec` for
172
+ load + semantic validation. The engine is pure: no stdout, no `process.exit`, no timestamps in
173
+ report bodies. [Library guide](./docs/library.md).
96
174
 
97
175
  ## Configuration
98
176
 
177
+ API Guard reads `apiguard.yaml` (discovered from the working directory, or passed with
178
+ `--config` / the `APIGUARD_CONFIG` environment variable):
179
+
99
180
  ```yaml
100
181
  # apiguard.yaml
101
182
  schemaVersion: 1
@@ -111,31 +192,80 @@ ignore:
111
192
 
112
193
  [Configuration guide](./docs/configuration.md) · [Ignoring changes](./docs/ignoring.md)
113
194
 
114
- ## How it works
195
+ ## OpenAPI support
196
+
197
+ | Version | Status |
198
+ | ------- | --------------- |
199
+ | 3.0.x | ✅ Full support |
200
+ | 3.1.x | ✅ Full support |
201
+
202
+ Documents are normalized into one internal model before diffing, so 3.0 and 3.1 idioms compare
203
+ correctly: type arrays vs `nullable`, exclusive bounds, and `$ref` aliasing. Remote `$ref`
204
+ fetching is **denied by default** (pass an explicit loader option to allow it) so untrusted
205
+ specs cannot trigger network access. Swagger 2.0 documents are not supported.
206
+
207
+ ## CI/CD
208
+
209
+ The intended workflow — run API Guard on every pull request that touches your API:
210
+
211
+ ```text
212
+ OpenAPI change → API Guard (merge-base diff) → breaking changes?
213
+ ├─ none → CI passes, merge with confidence
214
+ └─ breaking → step fails, changes are blocked before release
215
+ ```
115
216
 
116
- 1. **Load** — `@apidevtools/swagger-parser` behind a swappable `SpecLoader`; remote `$ref`s
117
- denied by default
118
- 2. **Normalize** — 3.0/3.1 unified: type arrays, nullable, exclusive bounds (§11.2)
119
- 3. **Index** — paths/methods/parameters/components, sorted
120
- 4. **Diff** — ref-aware pairwise schema comparison with cycle protection (10k-deep chains OK)
121
- 5. **Classify** — rules map facts → severities with per-context doctrine
122
- 6. **Report** — dedupe by stable id, deterministic sort, render
217
+ 1. Add the [GitHub Action](#github-action) to your PR workflow, **or** run the CLI in any CI:
218
+ `apiguard diff "$BASELINE" "$CURRENT" --fail-on error`
219
+ 2. If you ship an SDK, gate version bumps on `--suggest-version` output
220
+ 3. Encode your team's policy in `apiguard.yaml` (downgrades, ignores) and commit it
123
221
 
124
222
  ## Docs
125
223
 
126
224
  - [Getting started](./docs/getting-started.md)
127
- - [Configuration](./docs/configuration.md)
128
225
  - [CLI reference](./docs/cli.md)
129
- - [GitHub Action](./docs/github-action.md)
226
+ - [Configuration](./docs/configuration.md)
130
227
  - [Ignoring changes](./docs/ignoring.md)
131
- - [Rule reference (generated)](./docs/rules.md)
228
+ - [Rule reference (81 rules, generated)](./docs/rules.md)
229
+ - [GitHub Action](./docs/github-action.md)
230
+ - [Library API](./docs/library.md)
231
+ - [Changelog](./CHANGELOG.md)
232
+
233
+ ## Development
234
+
235
+ Requires Node ≥ 20. pnpm is pinned via the `packageManager` field (use corepack).
236
+
237
+ ```bash
238
+ pnpm install # install dependencies
239
+ pnpm test # unit tests (vitest)
240
+ pnpm test:e2e # CLI + Action bundle e2e tests
241
+ pnpm coverage # coverage with 90/90/90 lines, 85 branches thresholds
242
+ pnpm lint # eslint
243
+ pnpm format:check # prettier
244
+ pnpm typecheck # tsc --noEmit
245
+ pnpm build # tsup (ESM + CJS + dts)
246
+ pnpm build:action # esbuild bundle for the GitHub Action
247
+ pnpm docs:rules # regenerate docs/rules.md from the rule registry
248
+ ```
249
+
250
+ Rule parity is enforced: if you add a rule and forget `pnpm docs:rules`, CI fails.
132
251
 
133
252
  ## Contributing
134
253
 
135
- See [CONTRIBUTING.md](./CONTRIBUTING.md). Milestone-by-milestone build decisions live in
136
- [DECISIONS.md](./DECISIONS.md). Security issues: [SECURITY.md](./SECURITY.md) — do not open
137
- public issues.
254
+ Contributions are welcome — see [CONTRIBUTING.md](./CONTRIBUTING.md) for the workflow, the
255
+ milestone gate (`lint && typecheck && test`), and how to add a rule. Design decisions and their
256
+ reasons live in [DECISIONS.md](./DECISIONS.md). Please note the
257
+ [Code of Conduct](./CODE_OF_CONDUCT.md).
258
+
259
+ ## Security
260
+
261
+ API Guard processes untrusted spec files; its threat model is documented in
262
+ [SECURITY.md](./SECURITY.md). Please report vulnerabilities through GitHub's private
263
+ vulnerability reporting — do not open public issues.
138
264
 
139
265
  ## License
140
266
 
141
- [MIT](./LICENSE)
267
+ [MIT](./LICENSE) © API Guard contributors
268
+
269
+ ---
270
+
271
+ **Catch breaking OpenAPI changes before they reach production.**
package/package.json CHANGED
@@ -1,9 +1,28 @@
1
1
  {
2
2
  "name": "@subhamxcod/apiguard",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Detect breaking changes between OpenAPI specifications (3.0 & 3.1) so API contracts never silently break downstream consumers.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
+ "author": {
8
+ "name": "SUBHAM GHOSH"
9
+ },
10
+ "homepage": "https://github.com/subhamxaura/apiguard#readme",
11
+ "bugs": "https://github.com/subhamxaura/apiguard/issues",
12
+ "keywords": [
13
+ "openapi",
14
+ "openapi3",
15
+ "openapi31",
16
+ "api",
17
+ "breaking-change",
18
+ "api-breaking-change",
19
+ "openapi-diff",
20
+ "api-diff",
21
+ "ci",
22
+ "cd",
23
+ "typescript",
24
+ "developer-tools"
25
+ ],
7
26
  "engines": {
8
27
  "node": ">=20"
9
28
  },