@subhamxcod/apiguard 1.0.3 → 1.1.0
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.
- package/README.md +207 -61
- package/dist/{chunk-RQVNHPHC.js → chunk-EJQ5XOY6.js} +1012 -16
- package/dist/cli/main.cjs +1127 -26
- package/dist/cli/main.js +118 -9
- package/dist/index.cjs +171 -168
- package/dist/index.d.cts +54 -2
- package/dist/index.d.ts +54 -2
- package/dist/index.js +9 -1
- package/package.json +20 -1
package/README.md
CHANGED
|
@@ -1,48 +1,156 @@
|
|
|
1
1
|
# API Guard
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
> silently break downstream consumers.
|
|
3
|
+
> Production-grade OpenAPI breaking-change detection for CI/CD.
|
|
5
4
|
|
|
6
5
|
[](./.github/workflows/test.yml)
|
|
7
|
-
[](./.github/workflows/release.yml)
|
|
7
|
+
[](https://www.npmjs.com/package/@subhamxcod/apiguard)
|
|
8
|
+
[](./LICENSE)
|
|
8
9
|
|
|
9
|
-
**API Guard** diffs two OpenAPI
|
|
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
|
-
- **
|
|
18
|
-
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
21
|
-
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
24
|
-
- **
|
|
32
|
+
- **OpenAPI 3.0 and 3.1** — unified normalization: `nullable: true` (3.0) equals
|
|
33
|
+
`type: ["string", "null"]` (3.1)
|
|
34
|
+
- **AST source mapping** — pinpoint exact source file line and column numbers for every change
|
|
35
|
+
- **Multiple output formats** — terminal, JSON (machine contract), markdown, interactive standalone HTML dashboard, and OASIS SARIF 2.1.0 (for GitHub Code Scanning)
|
|
36
|
+
- **Git & remote spec support** — diff directly against git branches (`--git-base main`, `git://rev:file`) or remote URLs (`https://...`) with built-in SSRF protection
|
|
37
|
+
- **Client impact personas** — evaluate breaking impact against `--profile strict-sdk` or `--profile loose-web`
|
|
38
|
+
- **Changelog & shim generators** — automated Keep-a-Changelog release notes and backward-compatibility gateway shims (Express, Cloudflare Worker, Envoy Lua)
|
|
39
|
+
- **Semantic, not textual** — YAML formatting, key order, enum order, and `$ref` aliasing are
|
|
40
|
+
never false positives
|
|
41
|
+
- **Deterministic** — same inputs produce byte-identical output, on any machine, any locale
|
|
42
|
+
- **Configurable** — per-rule severities, per-context overrides (`ruleId@context`), and
|
|
43
|
+
path/operation/schema/rule ignore lists in `apiguard.yaml`
|
|
44
|
+
- **Semver advice** — suggests major/minor/patch from the detected change kinds
|
|
45
|
+
- **GitHub Action** — merge-base baseline, PR job summary, idempotent sticky PR comments, and inline diff annotations
|
|
46
|
+
- **TypeScript library** — typed, pure API for embedding in your own tooling
|
|
25
47
|
|
|
26
48
|
## Quick start
|
|
27
49
|
|
|
28
50
|
```bash
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
51
|
+
npm install --save-dev @subhamxcod/apiguard
|
|
52
|
+
# or: pnpm add -D @subhamxcod/apiguard
|
|
53
|
+
# or: yarn add -D @subhamxcod/apiguard
|
|
54
|
+
|
|
55
|
+
apiguard init . --examples # scaffold apiguard.yaml + two sample specs
|
|
56
|
+
apiguard diff examples/baseline.yaml examples/current.yaml
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Or run it directly without installing:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npx @subhamxcod/apiguard diff old.yaml new.yaml
|
|
32
63
|
```
|
|
33
64
|
|
|
34
|
-
|
|
65
|
+
## What gets detected
|
|
66
|
+
|
|
67
|
+
Remove a required response property between two releases:
|
|
35
68
|
|
|
69
|
+
```yaml
|
|
70
|
+
# before # after
|
|
71
|
+
responses: responses:
|
|
72
|
+
'200': '200':
|
|
73
|
+
content: content:
|
|
74
|
+
application/json: application/json:
|
|
75
|
+
schema: schema:
|
|
76
|
+
type: object type: object
|
|
77
|
+
required: [id, name, email] required: [id, name]
|
|
78
|
+
properties: properties:
|
|
79
|
+
id: { type: string } id: { type: string }
|
|
80
|
+
name: { type: string } name: { type: string }
|
|
81
|
+
email: { type: string }
|
|
36
82
|
```
|
|
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
83
|
|
|
41
|
-
|
|
42
|
-
|
|
84
|
+
Running `apiguard diff old.yaml new.yaml` exits non-zero and prints:
|
|
85
|
+
|
|
86
|
+
```text
|
|
87
|
+
GET /users/{id}
|
|
88
|
+
ERROR Response property `email` was removed
|
|
89
|
+
Suggestion: stop relying on `email`; it will no longer be returned
|
|
90
|
+
❌ Breaking API changes found
|
|
91
|
+
|
|
92
|
+
Suggested version bump: MAJOR
|
|
43
93
|
```
|
|
44
94
|
|
|
45
|
-
|
|
95
|
+
The `property-removed` rule fired for the response body; removals are resolved per the §10.5
|
|
96
|
+
context table (request/response/parameter/component), so the same rule can downgrade to a
|
|
97
|
+
warning where the context allows it. The full catalog of 81 rules with severities and trigger
|
|
98
|
+
conditions lives in the [rule reference](./docs/rules.md).
|
|
99
|
+
|
|
100
|
+
## CLI
|
|
101
|
+
|
|
102
|
+
### `apiguard diff <old-spec> <new-spec>`
|
|
103
|
+
|
|
104
|
+
Diff two OpenAPI specs and report breaking changes. This is the core command.
|
|
105
|
+
|
|
106
|
+
| Flag | Effect |
|
|
107
|
+
| ------------------------ | -------------------------------------------------------- |
|
|
108
|
+
| `--json`, `--markdown` | Output format shorthands |
|
|
109
|
+
| `--html`, `--sarif` | Interactive standalone HTML dashboard or SARIF 2.1.0 |
|
|
110
|
+
| `--open` | Open interactive HTML report in browser automatically |
|
|
111
|
+
| `--git-base <ref>` | Diff against baseline from git revision or branch |
|
|
112
|
+
| `--profile <p>` | `default`, `strict-sdk`, or `loose-web` |
|
|
113
|
+
| `--fail-on <level>` | `error` (default) \| `warning` \| `never` |
|
|
114
|
+
| `--strict` | Shorthand for `--fail-on warning --include-non-breaking` |
|
|
115
|
+
| `--include-non-breaking` | Also render warning + info changes |
|
|
116
|
+
| `--show-ignored` | Show changes suppressed by ignore rules, tagged |
|
|
117
|
+
| `--suggest-version` | Print the suggested semver bump after the report |
|
|
118
|
+
| `--config <path>` | Explicit config file (missing file: exit 2) |
|
|
119
|
+
| `--output <path>` | Also write the full report to a file |
|
|
120
|
+
| `--quiet` | Print only the one-line verdict |
|
|
121
|
+
| `--no-color` | Disable ANSI color (auto-detected for non-TTY) |
|
|
122
|
+
| `--verbose` | Log parse/config steps to stderr |
|
|
123
|
+
|
|
124
|
+
**Exit codes:** `0` pass · `1` changes met the fail-on policy · `2` usage/config error ·
|
|
125
|
+
`3` invalid spec · `4` internal error (please report).
|
|
126
|
+
|
|
127
|
+
Machine contract: `--json` emits a versioned report (`schemaVersion: "1.0"`) with
|
|
128
|
+
`summary.bySeverity`, per-change `ruleId`/`severity`/`kind`, and stable change ids.
|
|
129
|
+
|
|
130
|
+
### `apiguard changelog <old-spec> <new-spec>`
|
|
131
|
+
|
|
132
|
+
Generate Keep-a-Changelog formatted release notes grouped by breaking changes, additions, and updates.
|
|
133
|
+
|
|
134
|
+
### `apiguard codegen-shim <old-spec> <new-spec>`
|
|
135
|
+
|
|
136
|
+
Synthesize backward-compatibility gateway middleware (`express`, `cloudflare-worker`, `envoy-lua`) for breaking changes.
|
|
137
|
+
|
|
138
|
+
### `apiguard validate <spec>`
|
|
139
|
+
|
|
140
|
+
Load and semantically validate one spec. `--strict` exits 1 when semantic warnings exist.
|
|
141
|
+
|
|
142
|
+
### `apiguard init [dir]`
|
|
143
|
+
|
|
144
|
+
Scaffold an `apiguard.yaml` config template. `--examples` also writes two sample specs for a
|
|
145
|
+
first diff run; `--force` overwrites existing files.
|
|
146
|
+
|
|
147
|
+
Full reference: [CLI guide](./docs/cli.md).
|
|
148
|
+
|
|
149
|
+
## GitHub Action
|
|
150
|
+
|
|
151
|
+
The Action wraps the same engine for pull requests: it resolves the baseline automatically from
|
|
152
|
+
the merge-base with your default branch, posts the report as a job summary, and fails the step
|
|
153
|
+
when breaking changes exist.
|
|
46
154
|
|
|
47
155
|
```yaml
|
|
48
156
|
# .github/workflows/api-guard.yml
|
|
@@ -54,33 +162,18 @@ jobs:
|
|
|
54
162
|
steps:
|
|
55
163
|
- uses: actions/checkout@v4
|
|
56
164
|
with: { fetch-depth: 0 } # merge-base needs history
|
|
57
|
-
- uses: apiguard
|
|
165
|
+
- uses: subhamxaura/apiguard@v1 # tracks the latest v1.x release
|
|
58
166
|
with:
|
|
59
|
-
current: openapi.yaml #
|
|
167
|
+
current: openapi.yaml # the PR's spec
|
|
60
168
|
# baseline: omitted → the spec at the merge-base with origin/main
|
|
61
169
|
```
|
|
62
170
|
|
|
63
|
-
The Action
|
|
64
|
-
|
|
65
|
-
|
|
171
|
+
The Action emits `breaking`, `warnings`, `verdict`, and `suggested-version` outputs, and writes
|
|
172
|
+
a deterministic job summary. To report without blocking (e.g. during a migration), set
|
|
173
|
+
`fail-on: never` and branch on the outputs. Full input/output reference:
|
|
66
174
|
[GitHub Action guide](./docs/github-action.md).
|
|
67
175
|
|
|
68
|
-
##
|
|
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
|
|
176
|
+
## TypeScript library
|
|
84
177
|
|
|
85
178
|
```ts
|
|
86
179
|
import { analyze } from '@subhamxcod/apiguard';
|
|
@@ -91,11 +184,15 @@ if (report.summary.bySeverity.error > 0) {
|
|
|
91
184
|
}
|
|
92
185
|
```
|
|
93
186
|
|
|
94
|
-
The
|
|
95
|
-
|
|
187
|
+
The library also exports `suggestVersion` for standalone semver advice and `validateSpec` for
|
|
188
|
+
load + semantic validation. The engine is pure: no stdout, no `process.exit`, no timestamps in
|
|
189
|
+
report bodies. [Library guide](./docs/library.md).
|
|
96
190
|
|
|
97
191
|
## Configuration
|
|
98
192
|
|
|
193
|
+
API Guard reads `apiguard.yaml` (discovered from the working directory, or passed with
|
|
194
|
+
`--config` / the `APIGUARD_CONFIG` environment variable):
|
|
195
|
+
|
|
99
196
|
```yaml
|
|
100
197
|
# apiguard.yaml
|
|
101
198
|
schemaVersion: 1
|
|
@@ -111,31 +208,80 @@ ignore:
|
|
|
111
208
|
|
|
112
209
|
[Configuration guide](./docs/configuration.md) · [Ignoring changes](./docs/ignoring.md)
|
|
113
210
|
|
|
114
|
-
##
|
|
211
|
+
## OpenAPI support
|
|
212
|
+
|
|
213
|
+
| Version | Status |
|
|
214
|
+
| ------- | --------------- |
|
|
215
|
+
| 3.0.x | ✅ Full support |
|
|
216
|
+
| 3.1.x | ✅ Full support |
|
|
115
217
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
218
|
+
Documents are normalized into one internal model before diffing, so 3.0 and 3.1 idioms compare
|
|
219
|
+
correctly: type arrays vs `nullable`, exclusive bounds, and `$ref` aliasing. Remote `$ref`
|
|
220
|
+
fetching is **denied by default** (pass an explicit loader option to allow it) so untrusted
|
|
221
|
+
specs cannot trigger network access. Swagger 2.0 documents are not supported.
|
|
222
|
+
|
|
223
|
+
## CI/CD
|
|
224
|
+
|
|
225
|
+
The intended workflow — run API Guard on every pull request that touches your API:
|
|
226
|
+
|
|
227
|
+
```text
|
|
228
|
+
OpenAPI change → API Guard (merge-base diff) → breaking changes?
|
|
229
|
+
├─ none → CI passes, merge with confidence
|
|
230
|
+
└─ breaking → step fails, changes are blocked before release
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
1. Add the [GitHub Action](#github-action) to your PR workflow, **or** run the CLI in any CI:
|
|
234
|
+
`apiguard diff "$BASELINE" "$CURRENT" --fail-on error`
|
|
235
|
+
2. If you ship an SDK, gate version bumps on `--suggest-version` output
|
|
236
|
+
3. Encode your team's policy in `apiguard.yaml` (downgrades, ignores) and commit it
|
|
123
237
|
|
|
124
238
|
## Docs
|
|
125
239
|
|
|
126
240
|
- [Getting started](./docs/getting-started.md)
|
|
127
|
-
- [Configuration](./docs/configuration.md)
|
|
128
241
|
- [CLI reference](./docs/cli.md)
|
|
129
|
-
- [
|
|
242
|
+
- [Configuration](./docs/configuration.md)
|
|
130
243
|
- [Ignoring changes](./docs/ignoring.md)
|
|
131
|
-
- [Rule reference (generated)](./docs/rules.md)
|
|
244
|
+
- [Rule reference (81 rules, generated)](./docs/rules.md)
|
|
245
|
+
- [GitHub Action](./docs/github-action.md)
|
|
246
|
+
- [Library API](./docs/library.md)
|
|
247
|
+
- [Changelog](./CHANGELOG.md)
|
|
248
|
+
|
|
249
|
+
## Development
|
|
250
|
+
|
|
251
|
+
Requires Node ≥ 20. pnpm is pinned via the `packageManager` field (use corepack).
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
pnpm install # install dependencies
|
|
255
|
+
pnpm test # unit tests (vitest)
|
|
256
|
+
pnpm test:e2e # CLI + Action bundle e2e tests
|
|
257
|
+
pnpm coverage # coverage with 90/90/90 lines, 85 branches thresholds
|
|
258
|
+
pnpm lint # eslint
|
|
259
|
+
pnpm format:check # prettier
|
|
260
|
+
pnpm typecheck # tsc --noEmit
|
|
261
|
+
pnpm build # tsup (ESM + CJS + dts)
|
|
262
|
+
pnpm build:action # esbuild bundle for the GitHub Action
|
|
263
|
+
pnpm docs:rules # regenerate docs/rules.md from the rule registry
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Rule parity is enforced: if you add a rule and forget `pnpm docs:rules`, CI fails.
|
|
132
267
|
|
|
133
268
|
## Contributing
|
|
134
269
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
270
|
+
Contributions are welcome — see [CONTRIBUTING.md](./CONTRIBUTING.md) for the workflow, the
|
|
271
|
+
milestone gate (`lint && typecheck && test`), and how to add a rule. Design decisions and their
|
|
272
|
+
reasons live in [DECISIONS.md](./DECISIONS.md). Please note the
|
|
273
|
+
[Code of Conduct](./CODE_OF_CONDUCT.md).
|
|
274
|
+
|
|
275
|
+
## Security
|
|
276
|
+
|
|
277
|
+
API Guard processes untrusted spec files; its threat model is documented in
|
|
278
|
+
[SECURITY.md](./SECURITY.md). Please report vulnerabilities through GitHub's private
|
|
279
|
+
vulnerability reporting — do not open public issues.
|
|
138
280
|
|
|
139
281
|
## License
|
|
140
282
|
|
|
141
|
-
[MIT](./LICENSE)
|
|
283
|
+
[MIT](./LICENSE) © API Guard contributors
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
**Catch breaking OpenAPI changes before they reach production.**
|