specshift 1.0.0__tar.gz → 1.2.0__tar.gz
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.
- {specshift-1.0.0/specshift.egg-info → specshift-1.2.0}/PKG-INFO +124 -18
- {specshift-1.0.0 → specshift-1.2.0}/README.md +121 -15
- {specshift-1.0.0 → specshift-1.2.0}/pyproject.toml +6 -2
- {specshift-1.0.0 → specshift-1.2.0}/specshift/__init__.py +7 -1
- {specshift-1.0.0 → specshift-1.2.0}/specshift/cli.py +64 -8
- {specshift-1.0.0 → specshift-1.2.0}/specshift/differ.py +283 -0
- specshift-1.2.0/specshift/graphql_differ.py +527 -0
- specshift-1.2.0/specshift/protobuf_differ.py +622 -0
- specshift-1.2.0/specshift/reporter.py +369 -0
- specshift-1.2.0/specshift/webapp.py +335 -0
- {specshift-1.0.0 → specshift-1.2.0/specshift.egg-info}/PKG-INFO +124 -18
- {specshift-1.0.0 → specshift-1.2.0}/specshift.egg-info/SOURCES.txt +7 -1
- {specshift-1.0.0 → specshift-1.2.0}/tests/test_differ.py +145 -0
- specshift-1.2.0/tests/test_graphql_differ.py +119 -0
- specshift-1.2.0/tests/test_protobuf_differ.py +168 -0
- {specshift-1.0.0 → specshift-1.2.0}/tests/test_reporter.py +19 -0
- specshift-1.2.0/tests/test_webapp.py +62 -0
- specshift-1.0.0/specshift/reporter.py +0 -167
- {specshift-1.0.0 → specshift-1.2.0}/LICENSE +0 -0
- {specshift-1.0.0 → specshift-1.2.0}/setup.cfg +0 -0
- {specshift-1.0.0 → specshift-1.2.0}/specshift/ai_summary.py +0 -0
- {specshift-1.0.0 → specshift-1.2.0}/specshift/config.py +0 -0
- {specshift-1.0.0 → specshift-1.2.0}/specshift/git_utils.py +0 -0
- {specshift-1.0.0 → specshift-1.2.0}/specshift/models.py +0 -0
- {specshift-1.0.0 → specshift-1.2.0}/specshift/notifier.py +0 -0
- {specshift-1.0.0 → specshift-1.2.0}/specshift/spec_loader.py +0 -0
- {specshift-1.0.0 → specshift-1.2.0}/specshift.egg-info/dependency_links.txt +0 -0
- {specshift-1.0.0 → specshift-1.2.0}/specshift.egg-info/entry_points.txt +0 -0
- {specshift-1.0.0 → specshift-1.2.0}/specshift.egg-info/requires.txt +0 -0
- {specshift-1.0.0 → specshift-1.2.0}/specshift.egg-info/top_level.txt +0 -0
- {specshift-1.0.0 → specshift-1.2.0}/tests/test_cli.py +0 -0
- {specshift-1.0.0 → specshift-1.2.0}/tests/test_spec_loader.py +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: specshift
|
|
3
|
-
Version: 1.
|
|
4
|
-
Summary: Detects, classifies, and optionally summarizes breaking changes in OpenAPI/Swagger contracts using AI
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: Detects, classifies, and optionally summarizes breaking changes in OpenAPI/Swagger, GraphQL, and Protobuf/gRPC contracts using AI
|
|
5
5
|
Author: Lethe044
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/Lethe044/specshift
|
|
8
8
|
Project-URL: Repository, https://github.com/Lethe044/specshift
|
|
9
9
|
Project-URL: Issues, https://github.com/Lethe044/specshift/issues
|
|
10
10
|
Project-URL: Changelog, https://github.com/Lethe044/specshift/blob/main/CHANGELOG.md
|
|
11
|
-
Keywords: openapi,swagger,api,breaking-changes,contract-testing,api-diff,devtools,cli,ci-cd
|
|
11
|
+
Keywords: openapi,swagger,graphql,grpc,protobuf,api,breaking-changes,contract-testing,api-diff,devtools,cli,ci-cd,github-actions
|
|
12
12
|
Classifier: Development Status :: 5 - Production/Stable
|
|
13
13
|
Classifier: Environment :: Console
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
@@ -93,11 +93,21 @@ requirement.
|
|
|
93
93
|
|
|
94
94
|
- **Comprehensive structural diff**: deep comparison at the path, HTTP
|
|
95
95
|
method, parameter, request body, response, and schema level.
|
|
96
|
+
- **GraphQL support**: diff GraphQL SDL schemas with `specshift diff-graphql`,
|
|
97
|
+
covering types, fields, arguments, enum values, interfaces, unions, and
|
|
98
|
+
deprecations, no external GraphQL library required.
|
|
99
|
+
- **Protobuf/gRPC support**: diff `.proto` files with `specshift diff-proto`,
|
|
100
|
+
using protobuf's own documented wire-compatibility rules (field numbers,
|
|
101
|
+
wire type categories, zigzag encoding) rather than generic JSON-shape
|
|
102
|
+
heuristics, so severity reflects what will actually break on the wire.
|
|
96
103
|
- **Context-aware classification**: the same change is weighted
|
|
97
|
-
differently depending on whether it occurs in a request or a response
|
|
104
|
+
differently depending on whether it occurs in a request or a response
|
|
105
|
+
(or, for GraphQL, an input type versus an object type).
|
|
98
106
|
- **`$ref` resolution and `allOf` merging**: correctly follows the
|
|
99
107
|
reference and composition patterns common in real-world specifications.
|
|
100
|
-
- **Detects enum, format, nullable,
|
|
108
|
+
- **Detects enum, format, nullable, security scheme, content-type,
|
|
109
|
+
`additionalProperties`, `readOnly`/`writeOnly`, validation constraint,
|
|
110
|
+
default value, and `oneOf`/`anyOf` changes**.
|
|
101
111
|
- **Works entirely for free**: no API key or paid service is required.
|
|
102
112
|
- **Optional AI summary**: can generate a natural-language summary using
|
|
103
113
|
Groq, the Gemini free tier, or any OpenAI-compatible endpoint. If no key
|
|
@@ -105,12 +115,18 @@ requirement.
|
|
|
105
115
|
stops working.
|
|
106
116
|
- **CI/CD integration**: the `specshift check` command compares the
|
|
107
117
|
current specification against a branch and fails the build if a
|
|
108
|
-
breaking change is found.
|
|
118
|
+
breaking change is found. An official GitHub Action is also available
|
|
119
|
+
for one-line setup with automatic PR comments (see below).
|
|
109
120
|
- **Live monitoring**: the `specshift watch` command periodically checks
|
|
110
121
|
a remote API's specification and sends a Slack or Discord notification
|
|
111
122
|
when it changes.
|
|
112
|
-
- **
|
|
113
|
-
|
|
123
|
+
- **Local web dashboard**: `specshift serve` opens a browser-based tool
|
|
124
|
+
for comparing specs interactively (drag in files or paste text), with
|
|
125
|
+
zero extra dependencies since it runs entirely on Python's standard
|
|
126
|
+
library and stays on localhost.
|
|
127
|
+
- **Four output formats**: a colored console table, a Markdown report
|
|
128
|
+
(ideal for PR comments), JSON (for integrating with other tools), and a
|
|
129
|
+
standalone, filterable HTML report you can open in any browser.
|
|
114
130
|
|
|
115
131
|
## Installation
|
|
116
132
|
|
|
@@ -210,13 +226,50 @@ Useful options:
|
|
|
210
226
|
|
|
211
227
|
| Option | Description |
|
|
212
228
|
|---|---|
|
|
213
|
-
| `--format console\|markdown\|json` | Output format (default: console) |
|
|
229
|
+
| `--format console\|markdown\|json\|html` | Output format (default: console) |
|
|
214
230
|
| `--output <file>` | Writes the output to a file |
|
|
215
231
|
| `--ai` | Adds a natural-language summary |
|
|
216
232
|
| `--ai-provider groq\|gemini\|openai_compatible` | Chooses the AI provider |
|
|
217
233
|
| `--fail-on breaking\|warning\|none` | Determines at which level exit code 1 is returned |
|
|
218
234
|
| `--quiet` | Only prints the summary line |
|
|
219
235
|
|
|
236
|
+
### `specshift diff-graphql <old> <new>`
|
|
237
|
+
|
|
238
|
+
Compares two GraphQL SDL schemas. `<old>` and `<new>` can be a file path,
|
|
239
|
+
an http(s) URL, or raw SDL text. Supports the same `--format`, `--output`,
|
|
240
|
+
`--ai`, and `--fail-on` options as `diff`.
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
specshift diff-graphql old_schema.graphql new_schema.graphql
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### `specshift diff-proto <old> <new>`
|
|
247
|
+
|
|
248
|
+
Compares two Protobuf/gRPC `.proto` schemas using protobuf's own
|
|
249
|
+
wire-compatibility rules (field numbers, wire type categories) rather
|
|
250
|
+
than generic JSON-shape heuristics. Supports the same `--format`,
|
|
251
|
+
`--output`, `--ai`, and `--fail-on` options as `diff`.
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
specshift diff-proto old_service.proto new_service.proto
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### `specshift serve`
|
|
258
|
+
|
|
259
|
+
Starts a local, dependency-free web dashboard for comparing specs
|
|
260
|
+
interactively. Opens a browser tab where you can drag in or paste two
|
|
261
|
+
files (OpenAPI, GraphQL, or Protobuf, auto-detected), and view the same
|
|
262
|
+
styled HTML report the CLI produces, with a short history of past
|
|
263
|
+
comparisons in the current session.
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
specshift serve
|
|
267
|
+
# or: specshift serve --port 9000 --no-browser
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Everything runs in-process on your machine; nothing is uploaded anywhere,
|
|
271
|
+
and the server binds to `127.0.0.1` by default.
|
|
272
|
+
|
|
220
273
|
### `specshift check`
|
|
221
274
|
|
|
222
275
|
Designed for CI/CD. Compares the current specification file against a git
|
|
@@ -243,8 +296,48 @@ Creates a sample `.specshift.yml` file.
|
|
|
243
296
|
|
|
244
297
|
## Using it with GitHub Actions
|
|
245
298
|
|
|
246
|
-
|
|
247
|
-
|
|
299
|
+
### Option 1: the official SpecShift action (recommended)
|
|
300
|
+
|
|
301
|
+
SpecShift ships its own composite GitHub Action that runs the contract
|
|
302
|
+
check and automatically posts (and keeps updated) a Markdown report as a
|
|
303
|
+
pull request comment:
|
|
304
|
+
|
|
305
|
+
```yaml
|
|
306
|
+
name: API Contract Check
|
|
307
|
+
|
|
308
|
+
on:
|
|
309
|
+
pull_request:
|
|
310
|
+
paths:
|
|
311
|
+
- "openapi.yaml"
|
|
312
|
+
|
|
313
|
+
permissions:
|
|
314
|
+
pull-requests: write
|
|
315
|
+
|
|
316
|
+
jobs:
|
|
317
|
+
contract-check:
|
|
318
|
+
runs-on: ubuntu-latest
|
|
319
|
+
steps:
|
|
320
|
+
- uses: actions/checkout@v4
|
|
321
|
+
with:
|
|
322
|
+
fetch-depth: 0
|
|
323
|
+
|
|
324
|
+
- uses: Lethe044/SpecShift@v1
|
|
325
|
+
with:
|
|
326
|
+
spec-path: openapi.yaml
|
|
327
|
+
fail-on: breaking
|
|
328
|
+
# optional: ai-summary: 'true'
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Available inputs: `spec-path`, `base-ref` (defaults to the PR base branch),
|
|
332
|
+
`fail-on` (`breaking` | `warning` | `none`), `ai-summary` (`true`/`false`),
|
|
333
|
+
`ai-provider`, `comment-on-pr` (`true`/`false`), `github-token`, and
|
|
334
|
+
`specshift-version` to pin a specific release. Outputs: `has-breaking-changes`,
|
|
335
|
+
`breaking-count`, `warning-count`, `info-count`, and `report-path`.
|
|
336
|
+
|
|
337
|
+
### Option 2: calling the CLI directly
|
|
338
|
+
|
|
339
|
+
If you'd rather not use the action (or want to combine it with other
|
|
340
|
+
tooling), the CLI works just as well on its own:
|
|
248
341
|
|
|
249
342
|
```yaml
|
|
250
343
|
name: API Contract Check
|
|
@@ -307,26 +400,39 @@ common scenarios:
|
|
|
307
400
|
| Enum value removed | Breaking | Breaking |
|
|
308
401
|
| Endpoint or method removed | Breaking | Breaking |
|
|
309
402
|
|
|
403
|
+
The same table applies to GraphQL: `input` types behave like requests,
|
|
404
|
+
`type`/`interface` fields behave like responses.
|
|
405
|
+
|
|
406
|
+
Protobuf/gRPC follows a different model, based on the wire format rather
|
|
407
|
+
than request/response direction: adding fields or rpc methods is always
|
|
408
|
+
safe, changing a field's number is always breaking, and type changes are
|
|
409
|
+
judged by wire type category (see the `diff-proto` section above).
|
|
410
|
+
|
|
310
411
|
## Comparison with other tools
|
|
311
412
|
|
|
312
413
|
| | SpecShift | Raw JSON/YAML diff | oasdiff / openapi-diff style tools |
|
|
313
414
|
|---|---|---|---|
|
|
314
415
|
| Context-aware classification | Yes | No | Partially |
|
|
416
|
+
| GraphQL support | Yes | No | Rarely |
|
|
417
|
+
| Protobuf/gRPC support (wire-aware) | Yes | No | Rarely |
|
|
315
418
|
| Natural-language summary | Yes (optional) | No | No |
|
|
419
|
+
| Standalone HTML report | Yes | No | Rarely |
|
|
420
|
+
| Local interactive dashboard | Yes | No | Rarely |
|
|
316
421
|
| Free to use | Fully free | Free | Usually free |
|
|
317
|
-
| CI integration | Built-in (`check`) | Manual | Varies |
|
|
422
|
+
| CI integration | Built-in (`check` + official Action) | Manual | Varies |
|
|
318
423
|
| Live URL monitoring | Built-in (`watch`) | No | Rarely |
|
|
319
424
|
|
|
320
425
|
## Roadmap
|
|
321
426
|
|
|
322
427
|
This project is under active development. Some planned areas:
|
|
323
428
|
|
|
324
|
-
-
|
|
325
|
-
- GraphQL
|
|
326
|
-
|
|
327
|
-
-
|
|
328
|
-
|
|
329
|
-
|
|
429
|
+
- Path parameter pattern (regex) and discriminator-level rule refinements
|
|
430
|
+
- Deeper GraphQL support: directive-aware deprecation reasons, custom
|
|
431
|
+
scalar compatibility hints, and federation-aware schema composition
|
|
432
|
+
- Protobuf `reserved` statement awareness, so a properly reserved field
|
|
433
|
+
number isn't flagged the same way as an unreserved removal
|
|
434
|
+
- Optional shared/hosted history for the `serve` dashboard (currently
|
|
435
|
+
in-memory and per-session by design)
|
|
330
436
|
|
|
331
437
|
Feel free to open an issue if you have a feature request.
|
|
332
438
|
|
|
@@ -54,11 +54,21 @@ requirement.
|
|
|
54
54
|
|
|
55
55
|
- **Comprehensive structural diff**: deep comparison at the path, HTTP
|
|
56
56
|
method, parameter, request body, response, and schema level.
|
|
57
|
+
- **GraphQL support**: diff GraphQL SDL schemas with `specshift diff-graphql`,
|
|
58
|
+
covering types, fields, arguments, enum values, interfaces, unions, and
|
|
59
|
+
deprecations, no external GraphQL library required.
|
|
60
|
+
- **Protobuf/gRPC support**: diff `.proto` files with `specshift diff-proto`,
|
|
61
|
+
using protobuf's own documented wire-compatibility rules (field numbers,
|
|
62
|
+
wire type categories, zigzag encoding) rather than generic JSON-shape
|
|
63
|
+
heuristics, so severity reflects what will actually break on the wire.
|
|
57
64
|
- **Context-aware classification**: the same change is weighted
|
|
58
|
-
differently depending on whether it occurs in a request or a response
|
|
65
|
+
differently depending on whether it occurs in a request or a response
|
|
66
|
+
(or, for GraphQL, an input type versus an object type).
|
|
59
67
|
- **`$ref` resolution and `allOf` merging**: correctly follows the
|
|
60
68
|
reference and composition patterns common in real-world specifications.
|
|
61
|
-
- **Detects enum, format, nullable,
|
|
69
|
+
- **Detects enum, format, nullable, security scheme, content-type,
|
|
70
|
+
`additionalProperties`, `readOnly`/`writeOnly`, validation constraint,
|
|
71
|
+
default value, and `oneOf`/`anyOf` changes**.
|
|
62
72
|
- **Works entirely for free**: no API key or paid service is required.
|
|
63
73
|
- **Optional AI summary**: can generate a natural-language summary using
|
|
64
74
|
Groq, the Gemini free tier, or any OpenAI-compatible endpoint. If no key
|
|
@@ -66,12 +76,18 @@ requirement.
|
|
|
66
76
|
stops working.
|
|
67
77
|
- **CI/CD integration**: the `specshift check` command compares the
|
|
68
78
|
current specification against a branch and fails the build if a
|
|
69
|
-
breaking change is found.
|
|
79
|
+
breaking change is found. An official GitHub Action is also available
|
|
80
|
+
for one-line setup with automatic PR comments (see below).
|
|
70
81
|
- **Live monitoring**: the `specshift watch` command periodically checks
|
|
71
82
|
a remote API's specification and sends a Slack or Discord notification
|
|
72
83
|
when it changes.
|
|
73
|
-
- **
|
|
74
|
-
|
|
84
|
+
- **Local web dashboard**: `specshift serve` opens a browser-based tool
|
|
85
|
+
for comparing specs interactively (drag in files or paste text), with
|
|
86
|
+
zero extra dependencies since it runs entirely on Python's standard
|
|
87
|
+
library and stays on localhost.
|
|
88
|
+
- **Four output formats**: a colored console table, a Markdown report
|
|
89
|
+
(ideal for PR comments), JSON (for integrating with other tools), and a
|
|
90
|
+
standalone, filterable HTML report you can open in any browser.
|
|
75
91
|
|
|
76
92
|
## Installation
|
|
77
93
|
|
|
@@ -171,13 +187,50 @@ Useful options:
|
|
|
171
187
|
|
|
172
188
|
| Option | Description |
|
|
173
189
|
|---|---|
|
|
174
|
-
| `--format console\|markdown\|json` | Output format (default: console) |
|
|
190
|
+
| `--format console\|markdown\|json\|html` | Output format (default: console) |
|
|
175
191
|
| `--output <file>` | Writes the output to a file |
|
|
176
192
|
| `--ai` | Adds a natural-language summary |
|
|
177
193
|
| `--ai-provider groq\|gemini\|openai_compatible` | Chooses the AI provider |
|
|
178
194
|
| `--fail-on breaking\|warning\|none` | Determines at which level exit code 1 is returned |
|
|
179
195
|
| `--quiet` | Only prints the summary line |
|
|
180
196
|
|
|
197
|
+
### `specshift diff-graphql <old> <new>`
|
|
198
|
+
|
|
199
|
+
Compares two GraphQL SDL schemas. `<old>` and `<new>` can be a file path,
|
|
200
|
+
an http(s) URL, or raw SDL text. Supports the same `--format`, `--output`,
|
|
201
|
+
`--ai`, and `--fail-on` options as `diff`.
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
specshift diff-graphql old_schema.graphql new_schema.graphql
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### `specshift diff-proto <old> <new>`
|
|
208
|
+
|
|
209
|
+
Compares two Protobuf/gRPC `.proto` schemas using protobuf's own
|
|
210
|
+
wire-compatibility rules (field numbers, wire type categories) rather
|
|
211
|
+
than generic JSON-shape heuristics. Supports the same `--format`,
|
|
212
|
+
`--output`, `--ai`, and `--fail-on` options as `diff`.
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
specshift diff-proto old_service.proto new_service.proto
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### `specshift serve`
|
|
219
|
+
|
|
220
|
+
Starts a local, dependency-free web dashboard for comparing specs
|
|
221
|
+
interactively. Opens a browser tab where you can drag in or paste two
|
|
222
|
+
files (OpenAPI, GraphQL, or Protobuf, auto-detected), and view the same
|
|
223
|
+
styled HTML report the CLI produces, with a short history of past
|
|
224
|
+
comparisons in the current session.
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
specshift serve
|
|
228
|
+
# or: specshift serve --port 9000 --no-browser
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Everything runs in-process on your machine; nothing is uploaded anywhere,
|
|
232
|
+
and the server binds to `127.0.0.1` by default.
|
|
233
|
+
|
|
181
234
|
### `specshift check`
|
|
182
235
|
|
|
183
236
|
Designed for CI/CD. Compares the current specification file against a git
|
|
@@ -204,8 +257,48 @@ Creates a sample `.specshift.yml` file.
|
|
|
204
257
|
|
|
205
258
|
## Using it with GitHub Actions
|
|
206
259
|
|
|
207
|
-
|
|
208
|
-
|
|
260
|
+
### Option 1: the official SpecShift action (recommended)
|
|
261
|
+
|
|
262
|
+
SpecShift ships its own composite GitHub Action that runs the contract
|
|
263
|
+
check and automatically posts (and keeps updated) a Markdown report as a
|
|
264
|
+
pull request comment:
|
|
265
|
+
|
|
266
|
+
```yaml
|
|
267
|
+
name: API Contract Check
|
|
268
|
+
|
|
269
|
+
on:
|
|
270
|
+
pull_request:
|
|
271
|
+
paths:
|
|
272
|
+
- "openapi.yaml"
|
|
273
|
+
|
|
274
|
+
permissions:
|
|
275
|
+
pull-requests: write
|
|
276
|
+
|
|
277
|
+
jobs:
|
|
278
|
+
contract-check:
|
|
279
|
+
runs-on: ubuntu-latest
|
|
280
|
+
steps:
|
|
281
|
+
- uses: actions/checkout@v4
|
|
282
|
+
with:
|
|
283
|
+
fetch-depth: 0
|
|
284
|
+
|
|
285
|
+
- uses: Lethe044/SpecShift@v1
|
|
286
|
+
with:
|
|
287
|
+
spec-path: openapi.yaml
|
|
288
|
+
fail-on: breaking
|
|
289
|
+
# optional: ai-summary: 'true'
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Available inputs: `spec-path`, `base-ref` (defaults to the PR base branch),
|
|
293
|
+
`fail-on` (`breaking` | `warning` | `none`), `ai-summary` (`true`/`false`),
|
|
294
|
+
`ai-provider`, `comment-on-pr` (`true`/`false`), `github-token`, and
|
|
295
|
+
`specshift-version` to pin a specific release. Outputs: `has-breaking-changes`,
|
|
296
|
+
`breaking-count`, `warning-count`, `info-count`, and `report-path`.
|
|
297
|
+
|
|
298
|
+
### Option 2: calling the CLI directly
|
|
299
|
+
|
|
300
|
+
If you'd rather not use the action (or want to combine it with other
|
|
301
|
+
tooling), the CLI works just as well on its own:
|
|
209
302
|
|
|
210
303
|
```yaml
|
|
211
304
|
name: API Contract Check
|
|
@@ -268,26 +361,39 @@ common scenarios:
|
|
|
268
361
|
| Enum value removed | Breaking | Breaking |
|
|
269
362
|
| Endpoint or method removed | Breaking | Breaking |
|
|
270
363
|
|
|
364
|
+
The same table applies to GraphQL: `input` types behave like requests,
|
|
365
|
+
`type`/`interface` fields behave like responses.
|
|
366
|
+
|
|
367
|
+
Protobuf/gRPC follows a different model, based on the wire format rather
|
|
368
|
+
than request/response direction: adding fields or rpc methods is always
|
|
369
|
+
safe, changing a field's number is always breaking, and type changes are
|
|
370
|
+
judged by wire type category (see the `diff-proto` section above).
|
|
371
|
+
|
|
271
372
|
## Comparison with other tools
|
|
272
373
|
|
|
273
374
|
| | SpecShift | Raw JSON/YAML diff | oasdiff / openapi-diff style tools |
|
|
274
375
|
|---|---|---|---|
|
|
275
376
|
| Context-aware classification | Yes | No | Partially |
|
|
377
|
+
| GraphQL support | Yes | No | Rarely |
|
|
378
|
+
| Protobuf/gRPC support (wire-aware) | Yes | No | Rarely |
|
|
276
379
|
| Natural-language summary | Yes (optional) | No | No |
|
|
380
|
+
| Standalone HTML report | Yes | No | Rarely |
|
|
381
|
+
| Local interactive dashboard | Yes | No | Rarely |
|
|
277
382
|
| Free to use | Fully free | Free | Usually free |
|
|
278
|
-
| CI integration | Built-in (`check`) | Manual | Varies |
|
|
383
|
+
| CI integration | Built-in (`check` + official Action) | Manual | Varies |
|
|
279
384
|
| Live URL monitoring | Built-in (`watch`) | No | Rarely |
|
|
280
385
|
|
|
281
386
|
## Roadmap
|
|
282
387
|
|
|
283
388
|
This project is under active development. Some planned areas:
|
|
284
389
|
|
|
285
|
-
-
|
|
286
|
-
- GraphQL
|
|
287
|
-
|
|
288
|
-
-
|
|
289
|
-
|
|
290
|
-
|
|
390
|
+
- Path parameter pattern (regex) and discriminator-level rule refinements
|
|
391
|
+
- Deeper GraphQL support: directive-aware deprecation reasons, custom
|
|
392
|
+
scalar compatibility hints, and federation-aware schema composition
|
|
393
|
+
- Protobuf `reserved` statement awareness, so a properly reserved field
|
|
394
|
+
number isn't flagged the same way as an unreserved removal
|
|
395
|
+
- Optional shared/hosted history for the `serve` dashboard (currently
|
|
396
|
+
in-memory and per-session by design)
|
|
291
397
|
|
|
292
398
|
Feel free to open an issue if you have a feature request.
|
|
293
399
|
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specshift"
|
|
7
|
-
version = "1.
|
|
8
|
-
description = "Detects, classifies, and optionally summarizes breaking changes in OpenAPI/Swagger contracts using AI"
|
|
7
|
+
version = "1.2.0"
|
|
8
|
+
description = "Detects, classifies, and optionally summarizes breaking changes in OpenAPI/Swagger, GraphQL, and Protobuf/gRPC contracts using AI"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
11
11
|
license = { text = "MIT" }
|
|
@@ -13,6 +13,9 @@ authors = [{ name = "Lethe044" }]
|
|
|
13
13
|
keywords = [
|
|
14
14
|
"openapi",
|
|
15
15
|
"swagger",
|
|
16
|
+
"graphql",
|
|
17
|
+
"grpc",
|
|
18
|
+
"protobuf",
|
|
16
19
|
"api",
|
|
17
20
|
"breaking-changes",
|
|
18
21
|
"contract-testing",
|
|
@@ -20,6 +23,7 @@ keywords = [
|
|
|
20
23
|
"devtools",
|
|
21
24
|
"cli",
|
|
22
25
|
"ci-cd",
|
|
26
|
+
"github-actions",
|
|
23
27
|
]
|
|
24
28
|
classifiers = [
|
|
25
29
|
"Development Status :: 5 - Production/Stable",
|
|
@@ -4,14 +4,20 @@ summarizes changes in OpenAPI and Swagger contracts using AI.
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from specshift.differ import diff_specs
|
|
7
|
+
from specshift.graphql_differ import diff_graphql, load_sdl as load_graphql_sdl
|
|
7
8
|
from specshift.models import Change, Severity, ChangeType, DiffResult
|
|
9
|
+
from specshift.protobuf_differ import diff_protos, load_proto
|
|
8
10
|
from specshift.spec_loader import load_spec
|
|
9
11
|
|
|
10
|
-
__version__ = "1.
|
|
12
|
+
__version__ = "1.2.0"
|
|
11
13
|
|
|
12
14
|
__all__ = [
|
|
13
15
|
"diff_specs",
|
|
16
|
+
"diff_graphql",
|
|
17
|
+
"diff_protos",
|
|
14
18
|
"load_spec",
|
|
19
|
+
"load_graphql_sdl",
|
|
20
|
+
"load_proto",
|
|
15
21
|
"Change",
|
|
16
22
|
"Severity",
|
|
17
23
|
"ChangeType",
|
|
@@ -15,9 +15,16 @@ from specshift.ai_summary import generate_summary
|
|
|
15
15
|
from specshift.config import SpecShiftConfig, write_default_config
|
|
16
16
|
from specshift.differ import diff_specs
|
|
17
17
|
from specshift.git_utils import GitError, load_spec_from_git
|
|
18
|
+
from specshift.graphql_differ import GraphQLLoadError, diff_graphql, load_sdl
|
|
18
19
|
from specshift.models import DiffResult
|
|
19
20
|
from specshift.notifier import NotifyError, notify_discord, notify_slack
|
|
20
|
-
from specshift.
|
|
21
|
+
from specshift.protobuf_differ import ProtoLoadError, diff_protos, load_proto
|
|
22
|
+
from specshift.reporter import (
|
|
23
|
+
print_console_report,
|
|
24
|
+
render_html_report,
|
|
25
|
+
render_json_report,
|
|
26
|
+
render_markdown_report,
|
|
27
|
+
)
|
|
21
28
|
from specshift.spec_loader import SpecLoadError, load_spec
|
|
22
29
|
|
|
23
30
|
CACHE_DIR = Path(".specshift_cache")
|
|
@@ -26,17 +33,27 @@ CACHE_DIR = Path(".specshift_cache")
|
|
|
26
33
|
def build_parser() -> argparse.ArgumentParser:
|
|
27
34
|
parser = argparse.ArgumentParser(
|
|
28
35
|
prog="specshift",
|
|
29
|
-
description="Detects and classifies changes in OpenAPI/Swagger
|
|
36
|
+
description="Detects and classifies changes in OpenAPI/Swagger, GraphQL, and Protobuf/gRPC contracts.",
|
|
30
37
|
)
|
|
31
38
|
parser.add_argument("--version", action="version", version=f"specshift {__version__}")
|
|
32
39
|
|
|
33
40
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
34
41
|
|
|
35
|
-
diff_parser = subparsers.add_parser("diff", help="Compare two specifications")
|
|
42
|
+
diff_parser = subparsers.add_parser("diff", help="Compare two OpenAPI/Swagger specifications")
|
|
36
43
|
diff_parser.add_argument("old", help="Old specification: file path or URL")
|
|
37
44
|
diff_parser.add_argument("new", help="New specification: file path or URL")
|
|
38
45
|
_add_common_diff_args(diff_parser)
|
|
39
46
|
|
|
47
|
+
graphql_parser = subparsers.add_parser("diff-graphql", help="Compare two GraphQL SDL schemas")
|
|
48
|
+
graphql_parser.add_argument("old", help="Old GraphQL schema: file path or URL")
|
|
49
|
+
graphql_parser.add_argument("new", help="New GraphQL schema: file path or URL")
|
|
50
|
+
_add_common_diff_args(graphql_parser)
|
|
51
|
+
|
|
52
|
+
proto_parser = subparsers.add_parser("diff-proto", help="Compare two Protobuf/gRPC .proto schemas")
|
|
53
|
+
proto_parser.add_argument("old", help="Old .proto schema: file path or URL")
|
|
54
|
+
proto_parser.add_argument("new", help="New .proto schema: file path or URL")
|
|
55
|
+
_add_common_diff_args(proto_parser)
|
|
56
|
+
|
|
40
57
|
check_parser = subparsers.add_parser(
|
|
41
58
|
"check", help="For CI: compares the current specification against a git reference"
|
|
42
59
|
)
|
|
@@ -60,12 +77,19 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
60
77
|
init_parser.add_argument("--path", default=".specshift.yml", help="Path of the file to create")
|
|
61
78
|
init_parser.add_argument("--force", action="store_true", help="Overwrite an existing file")
|
|
62
79
|
|
|
80
|
+
serve_parser = subparsers.add_parser(
|
|
81
|
+
"serve", help="Starts a local web dashboard for comparing specs interactively in the browser"
|
|
82
|
+
)
|
|
83
|
+
serve_parser.add_argument("--host", default="127.0.0.1", help="Host to bind to (default: 127.0.0.1)")
|
|
84
|
+
serve_parser.add_argument("--port", type=int, default=8787, help="Port to listen on (default: 8787)")
|
|
85
|
+
serve_parser.add_argument("--no-browser", action="store_true", help="Don't automatically open a browser tab")
|
|
86
|
+
|
|
63
87
|
return parser
|
|
64
88
|
|
|
65
89
|
|
|
66
90
|
def _add_common_diff_args(parser: argparse.ArgumentParser) -> None:
|
|
67
91
|
parser.add_argument(
|
|
68
|
-
"--format", choices=["console", "markdown", "json"], default="console", help="Output format"
|
|
92
|
+
"--format", choices=["console", "markdown", "json", "html"], default="console", help="Output format"
|
|
69
93
|
)
|
|
70
94
|
parser.add_argument("--output", help="Write the output to a file (stdout if omitted)")
|
|
71
95
|
parser.add_argument("--ai", action="store_true", help="Generate a natural-language AI summary")
|
|
@@ -89,13 +113,19 @@ def main(argv: Optional[list[str]] = None) -> int:
|
|
|
89
113
|
try:
|
|
90
114
|
if args.command == "diff":
|
|
91
115
|
return _run_diff(args)
|
|
116
|
+
if args.command == "diff-graphql":
|
|
117
|
+
return _run_diff_graphql(args)
|
|
118
|
+
if args.command == "diff-proto":
|
|
119
|
+
return _run_diff_proto(args)
|
|
92
120
|
if args.command == "check":
|
|
93
121
|
return _run_check(args)
|
|
94
122
|
if args.command == "watch":
|
|
95
123
|
return _run_watch(args)
|
|
96
124
|
if args.command == "init":
|
|
97
125
|
return _run_init(args)
|
|
98
|
-
|
|
126
|
+
if args.command == "serve":
|
|
127
|
+
return _run_serve(args)
|
|
128
|
+
except (SpecLoadError, GitError, GraphQLLoadError, ProtoLoadError) as exc:
|
|
99
129
|
print(f"Error: {exc}", file=sys.stderr)
|
|
100
130
|
return 2
|
|
101
131
|
except KeyboardInterrupt:
|
|
@@ -113,6 +143,20 @@ def _run_diff(args: argparse.Namespace) -> int:
|
|
|
113
143
|
return _emit_result(result, args)
|
|
114
144
|
|
|
115
145
|
|
|
146
|
+
def _run_diff_graphql(args: argparse.Namespace) -> int:
|
|
147
|
+
old_sdl = load_sdl(args.old)
|
|
148
|
+
new_sdl = load_sdl(args.new)
|
|
149
|
+
result = diff_graphql(old_sdl, new_sdl)
|
|
150
|
+
return _emit_result(result, args)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _run_diff_proto(args: argparse.Namespace) -> int:
|
|
154
|
+
old_proto = load_proto(args.old)
|
|
155
|
+
new_proto = load_proto(args.new)
|
|
156
|
+
result = diff_protos(old_proto, new_proto)
|
|
157
|
+
return _emit_result(result, args)
|
|
158
|
+
|
|
159
|
+
|
|
116
160
|
def _run_check(args: argparse.Namespace) -> int:
|
|
117
161
|
config = SpecShiftConfig.load(args.config)
|
|
118
162
|
spec_path = args.spec or config.spec_path
|
|
@@ -151,6 +195,8 @@ def _emit_result(result: DiffResult, args: argparse.Namespace) -> int:
|
|
|
151
195
|
output_text = render_json_report(result, ai_summary=ai_summary)
|
|
152
196
|
elif args.format == "markdown":
|
|
153
197
|
output_text = render_markdown_report(result, ai_summary=ai_summary)
|
|
198
|
+
elif args.format == "html":
|
|
199
|
+
output_text = render_html_report(result, ai_summary=ai_summary)
|
|
154
200
|
else:
|
|
155
201
|
output_text = None # console format is printed directly
|
|
156
202
|
|
|
@@ -166,9 +212,12 @@ def _emit_result(result: DiffResult, args: argparse.Namespace) -> int:
|
|
|
166
212
|
if ai_summary:
|
|
167
213
|
print(f"\nAI Summary:\n{ai_summary}")
|
|
168
214
|
else:
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
215
|
+
output_path = args.output
|
|
216
|
+
if args.format == "html" and not output_path:
|
|
217
|
+
output_path = "specshift-report.html"
|
|
218
|
+
if output_path:
|
|
219
|
+
Path(output_path).write_text(output_text, encoding="utf-8")
|
|
220
|
+
print(f"Report written to: {output_path}")
|
|
172
221
|
else:
|
|
173
222
|
print(output_text)
|
|
174
223
|
|
|
@@ -194,6 +243,13 @@ def _run_init(args: argparse.Namespace) -> int:
|
|
|
194
243
|
return 0
|
|
195
244
|
|
|
196
245
|
|
|
246
|
+
def _run_serve(args: argparse.Namespace) -> int:
|
|
247
|
+
from specshift.webapp import run_server
|
|
248
|
+
|
|
249
|
+
run_server(host=args.host, port=args.port, open_browser=not args.no_browser)
|
|
250
|
+
return 0
|
|
251
|
+
|
|
252
|
+
|
|
197
253
|
def _run_watch(args: argparse.Namespace) -> int:
|
|
198
254
|
CACHE_DIR.mkdir(exist_ok=True)
|
|
199
255
|
cache_key = hashlib.sha256(args.url.encode("utf-8")).hexdigest()[:16]
|