Linting

Lint in CI/CD

Applies to
Gatewayruntime for API, AI, and MCP gateways
Hosting
Managed EdgeManaged DedicatedSelf-Hosted

zuplo lint exits with a non-zero code when it finds problems, so any CI system can use it as a gate. This page shows a GitHub Actions workflow for each common setup, then the pieces you need to adapt them to another CI system.

The examples assume your Zuplo project is at the root of the repository and lists zuplo in package.json, as projects created with create-zuplo-api do. zuplo lint doesn't need an API key; it reads only the files in your repository.

Fail pull requests on lint errors

The simplest setup runs the linter and lets its exit code pass or fail the job:

YAML.github/workflows/lint.yaml
name: Lint on: pull_request: push: branches: - main jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 24 - name: Install dependencies run: npm ci - name: Lint run: npx zuplo lint

The job fails when the linter finds an error. To fail on warnings too, change the step to npx zuplo lint --fail-on warn, or set failOn in zuplo.lint.jsonc so local runs and CI agree.

To lint before deploying, add the same step before zuplo deploy in your deployment workflow. See Custom CI/CD for deployment workflows.

Show results in GitHub code scanning

With --format sarif, the linter writes results in the SARIF format that GitHub code scanning reads. Code scanning shows each result as an annotation on the line it applies to in the pull request diff, and tracks open results in the repository's Security tab.

YAML.github/workflows/lint.yaml
name: Lint on: pull_request: push: branches: - main permissions: contents: read security-events: write jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 24 - name: Install dependencies run: npm ci - name: Lint run: npx zuplo lint --format sarif > zuplo-lint.sarif - name: Upload results if: ${{ !cancelled() }} uses: github/codeql-action/upload-sarif@v3 with: sarif_file: zuplo-lint.sarif category: zuplo-lint

The if: ${{ !cancelled() }} condition uploads the results even when the lint step fails, so the annotations appear on the pull requests that need them. The lint step still fails the job.

Code scanning is available on public repositories, and on private repositories with GitHub Advanced Security. Without it, use the pass/fail workflow — the results appear in the job log.

Report breaking changes on pull requests

Breaking change detection compares your OpenAPI files with the version on another branch. In CI, point the baseline at the remote branch and make sure Git has fetched it.

JSONzuplo.lint.jsonc
{ "version": 2, "breakingChanges": { "enabled": true, "baseline": "origin/main", "severity": "error", }, }

actions/checkout fetches a single commit by default, and the local main branch doesn't exist in that checkout. Set fetch-depth: 0 to fetch the full history, including origin/main:

YAML.github/workflows/lint.yaml
steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: 24 - name: Install dependencies run: npm ci - name: Lint run: npx zuplo lint

If the baseline isn't in the checkout, the run fails with baseline ref "origin/main" not found. Fetch the branch before linting.

origin/main also works on your machine after a git fetch, so the same configuration file serves local runs and CI.

To report breaking changes without blocking the merge, set "severity": "warn". The changes still appear in the output and in code scanning, and the job passes unless failOn is "warn".

Use the JSON output in scripts

--format json prints a single JSON document with a summary, every result, and any breaking changes or fixes. Use it to build your own report, such as a pull request comment or a job summary:

YAML.github/workflows/lint.yaml
- name: Lint id: lint run: npx zuplo lint --format json > zuplo-lint.json || true - name: Summarize run: | jq -r '"### Zuplo lint\n\n\(.summary.errors) errors, \(.summary.warnings) warnings, \(.summary.breakingChanges) breaking changes"' \ zuplo-lint.json >> "$GITHUB_STEP_SUMMARY" jq -e '.passed' zuplo-lint.json > /dev/null

--fail-on none keeps the lint step from failing before the summary is written; the final jq -e '.passed' fails the job when the run didn't pass. The passed field already takes failOn from your configuration file into account. See Output formats for the full JSON shape.

Other CI systems

Any CI system that runs Node.js can run the linter. Each workflow above uses three things you need to carry over:

  1. Install the project's dependencies with npm ci (or your package manager's equivalent), so npx zuplo uses the version in your lockfile.
  2. Run npx zuplo lint from the project root, or pass --dir. With --dir, file paths in the output are relative to that directory, so SARIF results won't line up with files in code scanning unless the project is at the repository root.
  3. Read the exit code. 0 passes, 1 means the linter found problems, and 2 means it couldn't run. Treat 2 as a broken pipeline rather than a lint result — check the error message in the log.

For breaking change detection, also fetch the baseline branch before linting, for example with git fetch origin main.

Troubleshooting

The linter binary for your platform isn't installed

The linter ships as a separate, platform-specific package that npm installs alongside the Zuplo CLI as an optional dependency. If optional dependencies are skipped — for example with npm install --omit=optional — zuplo lint stops with:

Code
The zuplo-lint binary for your platform (linux-x64) is not installed.

Install the dependencies again without skipping optional dependencies. The error message names the exact package for your platform, such as @zuplo/lint-linux-x64, if you need to install it directly.

Invalid directory

Code
Invalid directory: The project directory is not the root of a Zuplo project.

The command needs a config/ directory containing at least one file that ends in .oas.json. Run it from the project root, or pass --dir to point at the project. This error exits with 2.

Next steps

Last modified on