zuplo 7.13.3 → 7.13.5

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.
@@ -0,0 +1,211 @@
1
+ ---
2
+ title: Breaking Change Detection
3
+ sidebar_label: Breaking changes
4
+ description:
5
+ Use `zuplo lint` to compare your OpenAPI files to a Git branch and catch
6
+ removed endpoints, new required inputs, and changed types before they reach
7
+ consumers.
8
+ applies_to:
9
+ product: gateway
10
+ hosting: all
11
+ ---
12
+
13
+ Your OpenAPI files are the contract your API consumers build against. Remove an
14
+ endpoint, make a query parameter required, or change a field from a string to an
15
+ integer, and every client that relied on the old shape breaks — usually in
16
+ production, and usually after the change has shipped.
17
+
18
+ Breaking change detection catches these changes before you merge them.
19
+ `zuplo lint` compares each OpenAPI file in your project with the same file on a
20
+ Git branch, such as `main`, and reports every change that can break an existing
21
+ consumer:
22
+
23
+ ```txt
24
+ Breaking Changes
25
+
26
+ config/routes.oas.json
27
+ 0:0 error Removed "/todos/{id}" (was "/todos/{id}") breaking-change
28
+ 15:25 error Modified "required" from "false" to "true" breaking-change
29
+ 37:21 error Modified "type" from "string" to "integer" breaking-change
30
+
31
+ ✖ 3 breaking changes remaining
32
+ ```
33
+
34
+ ## Turn on detection
35
+
36
+ Breaking change detection is off by default. Turn it on in `zuplo.lint.jsonc` in
37
+ the root of your project:
38
+
39
+ ```jsonc title="zuplo.lint.jsonc"
40
+ {
41
+ "version": 2,
42
+ "breakingChanges": {
43
+ "enabled": true,
44
+ "baseline": "origin/main",
45
+ },
46
+ }
47
+ ```
48
+
49
+ Then run the linter as usual:
50
+
51
+ ```bash
52
+ git fetch origin main
53
+ npx zuplo lint
54
+ ```
55
+
56
+ Detection runs alongside the rest of the lint checks. It uses no extra flags, so
57
+ the same command works on your machine and in CI.
58
+
59
+ ## Settings
60
+
61
+ | Field | Description |
62
+ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------- |
63
+ | `enabled` | Turns detection on. Defaults to `false`. |
64
+ | `baseline` | The branch, tag, or commit SHA to compare against, such as `main`, `origin/main`, or `v1.2.0`. Defaults to `"main"`. |
65
+ | `severity` | `"error"` or `"warn"`. Defaults to `"error"`. Controls whether a breaking change fails the run — see [Block or warn](#block-or-warn). |
66
+
67
+ ## Choose a baseline
68
+
69
+ The baseline is the version of your API that consumers use today. Compare
70
+ against whatever represents that version:
71
+
72
+ - **The branch you deploy production from**, usually `main`. Each pull request
73
+ then reports what it would break once merged. This is the most common setup.
74
+ - **A release tag**, such as `v1.2.0`. Every change since the last release is
75
+ checked, which is useful when several merges ship together.
76
+
77
+ Use `origin/main` rather than `main` when you lint in CI. CI systems usually
78
+ check out the pull request's commit without creating a local `main` branch, so
79
+ only the remote-tracking branch `origin/main` exists. `origin/main` also works
80
+ on your machine after `git fetch`, so one configuration serves both.
81
+
82
+ The `baseline` value must be a branch, tag, or commit SHA. Expressions such as
83
+ `HEAD~1` or `main^` aren't accepted.
84
+
85
+ ## What counts as a breaking change
86
+
87
+ A change is breaking when a client written against the baseline can fail against
88
+ the new version. The linter reports these kinds of change, among others:
89
+
90
+ | Change | Example message |
91
+ | ----------------------------------------------------- | -------------------------------------------- |
92
+ | Removing a path or an operation | `Removed "/todos/{id}" (was "/todos/{id}")` |
93
+ | Adding a required parameter | `Added "parameters"` |
94
+ | Making an optional parameter required | `Modified "required" from "false" to "true"` |
95
+ | Making a request body property required | `Added property "required"` |
96
+ | Changing the type of a parameter, property, or schema | `Modified "type" from "string" to "integer"` |
97
+ | Removing a value from an enum | `Removed property "enum" (was "done")` |
98
+ | Removing a response status code | `Removed "codes" (was "201")` |
99
+
100
+ These changes are safe, so the linter doesn't report them:
101
+
102
+ - Adding a new path, operation, or response
103
+ - Adding an optional parameter or request body property
104
+ - Adding a property to a response
105
+ - Adding a value to an enum
106
+ - Changing descriptions, summaries, and examples
107
+
108
+ Detection compares the API contract — paths, operations, parameters, request
109
+ bodies, and responses. It doesn't compare the gateway configuration inside
110
+ `x-zuplo-route`, so removing a policy or changing a handler isn't reported as a
111
+ breaking change.
112
+
113
+ ## Read the results
114
+
115
+ Breaking changes appear in their own **Breaking Changes** section, after the
116
+ other lint results, and are counted separately in the summary. Every result uses
117
+ the ID `breaking-change`.
118
+
119
+ Each result points at the change in your current file. A removal has nothing
120
+ left to point at, so its location is `0:0`; the message names what was removed.
121
+
122
+ In `--format json` output, breaking changes are in the `breakingChanges` array
123
+ and counted in `summary.breakingChanges`. In `--format sarif` output, they're
124
+ included with the other results.
125
+
126
+ New files have nothing to compare against, so a file that doesn't exist on the
127
+ baseline never reports breaking changes. Detection checks the same files as the
128
+ rest of the linter: every `.oas.json` file in `config/`, or the files listed in
129
+ [`include`](./api-lint-configuration.mdx#include).
130
+ [`ignorePaths`](./api-lint-configuration.mdx#ignorepaths) doesn't apply to
131
+ breaking changes.
132
+
133
+ ## Block or warn
134
+
135
+ `severity` decides whether a breaking change fails the run:
136
+
137
+ | `severity` | `failOn: "error"` (default) | `failOn: "warn"` |
138
+ | ------------------- | --------------------------- | ---------------- |
139
+ | `"error"` (default) | Fails | Fails |
140
+ | `"warn"` | Passes, and reports | Fails |
141
+
142
+ Use `"error"` to block pull requests that break the contract. Use `"warn"` when
143
+ breaking changes are sometimes intentional — for example, while an API is still
144
+ in beta — and you want reviewers to see them without blocking the merge.
145
+
146
+ When you do mean to ship a breaking change, the usual approach is to version the
147
+ API instead: keep the old operation, add the new one under a new path such as
148
+ `/v2/todos`, and remove the old one later. See
149
+ [Versioning on Zuplo](./versioning-on-zuplo.mdx).
150
+
151
+ ## Run in CI
152
+
153
+ Detection reads the baseline from your Git history, so the CI checkout has to
154
+ include it. With GitHub Actions, set `fetch-depth: 0` on `actions/checkout`:
155
+
156
+ ```yaml title=".github/workflows/lint.yaml"
157
+ steps:
158
+ - uses: actions/checkout@v4
159
+ with:
160
+ fetch-depth: 0
161
+
162
+ - uses: actions/setup-node@v4
163
+ with:
164
+ node-version: 24
165
+
166
+ - name: Install dependencies
167
+ run: npm ci
168
+
169
+ - name: Lint
170
+ run: npx zuplo lint
171
+ ```
172
+
173
+ On other CI systems, run `git fetch origin main` (or the branch you compare
174
+ against) before the linter. See [Lint in CI/CD](./api-lint-ci-cd.mdx) for
175
+ complete workflows, including GitHub code scanning.
176
+
177
+ ## Requirements and errors
178
+
179
+ Detection uses Git, so it needs:
180
+
181
+ - Git installed where the linter runs.
182
+ - A project inside a Git repository. The project can be the repository root or
183
+ any subdirectory of it.
184
+ - The baseline ref in the local repository.
185
+
186
+ If any of these is missing, the linter can't compare, and it says so instead of
187
+ reporting no changes:
188
+
189
+ ```txt
190
+ Breaking Changes
191
+
192
+ 0:0 error Breaking change detection failed: baseline ref "main" not found in this repository; fetch it first (for example, git fetch origin main) or set breakingChanges.baseline to a ref that exists, such as origin/main breaking-change
193
+ ```
194
+
195
+ This result uses the `severity` you set, so with the default `"error"` it fails
196
+ the run. A comparison you asked for that can't happen fails the same gate a
197
+ breaking change would.
198
+
199
+ | Message | Fix |
200
+ | ----------------------------------------------- | ----------------------------------------------------------------------------------- |
201
+ | `baseline ref "main" not found` | Fetch the branch (`git fetch origin main`), or set `baseline` to `origin/main`. |
202
+ | `cannot read baseline ... not a git repository` | Run the linter inside a Git repository, or turn off detection for that environment. |
203
+ | `invalid baseline ref` | Use a branch, tag, or commit SHA rather than an expression such as `HEAD~1`. |
204
+
205
+ ## Next steps
206
+
207
+ - [Lint in CI/CD](./api-lint-ci-cd.mdx) — run detection on every pull request.
208
+ - [Lint configuration](./api-lint-configuration.mdx) — the rest of the
209
+ `zuplo.lint.jsonc` options.
210
+ - [Versioning on Zuplo](./versioning-on-zuplo.mdx) — ship a breaking change
211
+ without breaking existing consumers.
@@ -0,0 +1,242 @@
1
+ ---
2
+ title: Lint in CI/CD
3
+ sidebar_label: Lint in CI/CD
4
+ description:
5
+ Run `zuplo lint` in CI to block pull requests on lint errors, show results in
6
+ GitHub code scanning, and report breaking OpenAPI changes against the main
7
+ branch.
8
+ applies_to:
9
+ product: gateway
10
+ hosting: all
11
+ ---
12
+
13
+ `zuplo lint` exits with a non-zero code when it finds problems, so any CI system
14
+ can use it as a gate. This page shows a GitHub Actions workflow for each common
15
+ setup, then the pieces you need to adapt them to another CI system.
16
+
17
+ The examples assume your Zuplo project is at the root of the repository and
18
+ lists `zuplo` in `package.json`, as projects created with
19
+ [`create-zuplo-api`](../cli/create-zuplo-api.mdx) do. `zuplo lint` doesn't need
20
+ an API key; it reads only the files in your repository.
21
+
22
+ ## Fail pull requests on lint errors
23
+
24
+ The simplest setup runs the linter and lets its exit code pass or fail the job:
25
+
26
+ ```yaml title=".github/workflows/lint.yaml"
27
+ name: Lint
28
+
29
+ on:
30
+ pull_request:
31
+ push:
32
+ branches:
33
+ - main
34
+
35
+ jobs:
36
+ lint:
37
+ runs-on: ubuntu-latest
38
+
39
+ steps:
40
+ - uses: actions/checkout@v4
41
+
42
+ - uses: actions/setup-node@v4
43
+ with:
44
+ node-version: 24
45
+
46
+ - name: Install dependencies
47
+ run: npm ci
48
+
49
+ - name: Lint
50
+ run: npx zuplo lint
51
+ ```
52
+
53
+ The job fails when the linter finds an error. To fail on warnings too, change
54
+ the step to `npx zuplo lint --fail-on warn`, or set
55
+ [`failOn`](./api-lint-configuration.mdx#failon) in `zuplo.lint.jsonc` so local
56
+ runs and CI agree.
57
+
58
+ To lint before deploying, add the same step before `zuplo deploy` in your
59
+ deployment workflow. See [Custom CI/CD](./custom-ci-cd.mdx) for deployment
60
+ workflows.
61
+
62
+ ## Show results in GitHub code scanning
63
+
64
+ With `--format sarif`, the linter writes results in the SARIF format that GitHub
65
+ code scanning reads. Code scanning shows each result as an annotation on the
66
+ line it applies to in the pull request diff, and tracks open results in the
67
+ repository's **Security** tab.
68
+
69
+ ```yaml title=".github/workflows/lint.yaml"
70
+ name: Lint
71
+
72
+ on:
73
+ pull_request:
74
+ push:
75
+ branches:
76
+ - main
77
+
78
+ permissions:
79
+ contents: read
80
+ security-events: write
81
+
82
+ jobs:
83
+ lint:
84
+ runs-on: ubuntu-latest
85
+
86
+ steps:
87
+ - uses: actions/checkout@v4
88
+
89
+ - uses: actions/setup-node@v4
90
+ with:
91
+ node-version: 24
92
+
93
+ - name: Install dependencies
94
+ run: npm ci
95
+
96
+ - name: Lint
97
+ run: npx zuplo lint --format sarif > zuplo-lint.sarif
98
+
99
+ - name: Upload results
100
+ if: ${{ !cancelled() }}
101
+ uses: github/codeql-action/upload-sarif@v3
102
+ with:
103
+ sarif_file: zuplo-lint.sarif
104
+ category: zuplo-lint
105
+ ```
106
+
107
+ The `if: ${{ !cancelled() }}` condition uploads the results even when the lint
108
+ step fails, so the annotations appear on the pull requests that need them. The
109
+ lint step still fails the job.
110
+
111
+ :::note
112
+
113
+ Code scanning is available on public repositories, and on private repositories
114
+ with GitHub Advanced Security. Without it, use the
115
+ [pass/fail workflow](#fail-pull-requests-on-lint-errors) — the results appear in
116
+ the job log.
117
+
118
+ :::
119
+
120
+ ## Report breaking changes on pull requests
121
+
122
+ [Breaking change detection](./api-lint-breaking-changes.mdx) compares your
123
+ OpenAPI files with the version on another branch. In CI, point the baseline at
124
+ the remote branch and make sure Git has fetched it.
125
+
126
+ ```jsonc title="zuplo.lint.jsonc"
127
+ {
128
+ "version": 2,
129
+ "breakingChanges": {
130
+ "enabled": true,
131
+ "baseline": "origin/main",
132
+ "severity": "error",
133
+ },
134
+ }
135
+ ```
136
+
137
+ `actions/checkout` fetches a single commit by default, and the local `main`
138
+ branch doesn't exist in that checkout. Set `fetch-depth: 0` to fetch the full
139
+ history, including `origin/main`:
140
+
141
+ ```yaml title=".github/workflows/lint.yaml"
142
+ steps:
143
+ - uses: actions/checkout@v4
144
+ with:
145
+ fetch-depth: 0
146
+
147
+ - uses: actions/setup-node@v4
148
+ with:
149
+ node-version: 24
150
+
151
+ - name: Install dependencies
152
+ run: npm ci
153
+
154
+ - name: Lint
155
+ run: npx zuplo lint
156
+ ```
157
+
158
+ If the baseline isn't in the checkout, the run fails with
159
+ `baseline ref "origin/main" not found`. Fetch the branch before linting.
160
+
161
+ `origin/main` also works on your machine after a `git fetch`, so the same
162
+ configuration file serves local runs and CI.
163
+
164
+ To report breaking changes without blocking the merge, set `"severity": "warn"`.
165
+ The changes still appear in the output and in code scanning, and the job passes
166
+ unless `failOn` is `"warn"`.
167
+
168
+ ## Use the JSON output in scripts
169
+
170
+ `--format json` prints a single JSON document with a summary, every result, and
171
+ any breaking changes or fixes. Use it to build your own report, such as a pull
172
+ request comment or a job summary:
173
+
174
+ ```yaml title=".github/workflows/lint.yaml"
175
+ - name: Lint
176
+ id: lint
177
+ run: npx zuplo lint --format json > zuplo-lint.json || true
178
+
179
+ - name: Summarize
180
+ run: |
181
+ jq -r '"### Zuplo lint\n\n\(.summary.errors) errors, \(.summary.warnings) warnings, \(.summary.breakingChanges) breaking changes"' \
182
+ zuplo-lint.json >> "$GITHUB_STEP_SUMMARY"
183
+ jq -e '.passed' zuplo-lint.json > /dev/null
184
+ ```
185
+
186
+ `--fail-on none` keeps the lint step from failing before the summary is written;
187
+ the final `jq -e '.passed'` fails the job when the run didn't pass. The `passed`
188
+ field already takes `failOn` from your configuration file into account. See
189
+ [Output formats](./api-lint.mdx#output-formats) for the full JSON shape.
190
+
191
+ ## Other CI systems
192
+
193
+ Any CI system that runs Node.js can run the linter. Each workflow above uses
194
+ three things you need to carry over:
195
+
196
+ 1. **Install the project's dependencies** with `npm ci` (or your package
197
+ manager's equivalent), so `npx zuplo` uses the version in your lockfile.
198
+ 2. **Run `npx zuplo lint`** from the project root, or pass `--dir`. With
199
+ `--dir`, file paths in the output are relative to that directory, so SARIF
200
+ results won't line up with files in code scanning unless the project is at
201
+ the repository root.
202
+ 3. **Read the exit code.** `0` passes, `1` means the linter found problems, and
203
+ `2` means it couldn't run. Treat `2` as a broken pipeline rather than a lint
204
+ result — check the error message in the log.
205
+
206
+ For breaking change detection, also fetch the baseline branch before linting,
207
+ for example with `git fetch origin main`.
208
+
209
+ ## Troubleshooting
210
+
211
+ ### The linter binary for your platform isn't installed
212
+
213
+ The linter ships as a separate, platform-specific package that npm installs
214
+ alongside the Zuplo CLI as an optional dependency. If optional dependencies are
215
+ skipped — for example with `npm install --omit=optional` — `zuplo lint` stops
216
+ with:
217
+
218
+ ```txt
219
+ The zuplo-lint binary for your platform (linux-x64) is not installed.
220
+ ```
221
+
222
+ Install the dependencies again without skipping optional dependencies. The error
223
+ message names the exact package for your platform, such as
224
+ `@zuplo/lint-linux-x64`, if you need to install it directly.
225
+
226
+ ### Invalid directory
227
+
228
+ ```txt
229
+ Invalid directory: The project directory is not the root of a Zuplo project.
230
+ ```
231
+
232
+ The command needs a `config/` directory containing at least one file that ends
233
+ in `.oas.json`. Run it from the project root, or pass `--dir` to point at the
234
+ project. This error exits with `2`.
235
+
236
+ ## Next steps
237
+
238
+ - [Lint configuration](./api-lint-configuration.mdx) — presets, severities, and
239
+ breaking change settings.
240
+ - [Testing your API](./testing.mdx) — run tests against a live gateway in the
241
+ same pipeline.
242
+ - [Custom CI/CD](./custom-ci-cd.mdx) — deploy from your own CI system.