# Any CI system

Source: https://apigenie.pl/docs/ci

`apigenie` runs the same three checks on a pull request whatever system runs the pipeline: `lint`
for the issues in the files, `format --check` for the files that are not formatted, and `diff` for
the changes that break clients. Each command exits with status 1 when it finds something, which
fails the job, and writes a report in a format the system shows next to the pull request. This page
has the pieces every system needs: the three commands, the reports each system reads, installing the
CLI in a job, the comparison with the base branch, and one script that runs the checks. The pages of
[GitLab CI](/docs/ci/gitlab), [Azure Pipelines](/docs/ci/azure-pipelines), [Jenkins](/docs/ci/jenkins),
[CircleCI](/docs/ci/circleci) and [Bitbucket Pipelines](/docs/ci/bitbucket-pipelines) put them into
one pipeline file each. On GitHub, the [GitHub Action](/docs/ci/github-action) installs the CLI,
finds the base of the pull request and turns the reports into annotations.

## The three checks

| Check            | Command                                        | Fails when                                                                                                 |
| ---------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| Lint             | `apigenie lint api/openapi.yaml`               | An issue of severity `error` is reported. Warnings alone do not fail the check.                            |
| Formatting       | `apigenie format --check api/openapi.yaml`     | A file would change when formatted. The names of the files go to standard error; nothing is written.      |
| Breaking changes | `apigenie diff base.yaml api/openapi.yaml`     | A change is breaking by the [rules](/docs/reference/cli/diff/rules) of the standard or the configuration. |

The commands take several files, and glob patterns where the shell expands them. `lint`, `format`
and `diff` read the project's `apigenie.yaml` when there is one, so the job checks what the
developers check. Status 2 means a command could not run: a file that cannot be read, an invalid
configuration, an option that does not exist. The [exit statuses](/docs/reference/cli#exit-status)
are the same in the four binaries.

## Reports the systems read

A report goes to standard output, so the job redirects it to a file and hands the file to the
system. The job log then shows nothing of the issues; the exit status still fails the job, and the
system shows the report next to the pull request.

| Formatter                | Written by     | Read by                                                                                                                                                                                                 |
| ------------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `junit`                  | `lint`         | The test reports of GitLab CI, Azure Pipelines, CircleCI and Bitbucket Pipelines, and the JUnit plugin of Jenkins: a test suite per file and a failed test case per issue, named after the rule, warnings included. |
| `checkstyle`             | `lint`         | The Warnings plugin of Jenkins and other readers of Checkstyle XML, which keep the severity and the rule of each issue.                                                                                 |
| `sarif`                  | `lint`         | GitHub code scanning, the Warnings plugin of Jenkins and the SARIF viewers. The results link the rule pages, and carry fingerprints that let a dashboard follow an issue across runs.                      |
| `markdown`               | `diff`         | People: a comment on the pull request, a job summary, an artifact.                                                                                                                                      |
| `json`                   | `lint`, `diff` | Scripts, with the JSON Schemas of the [lint](/docs/reference/cli/lint#json) and [diff](/docs/reference/cli/diff#json-output) reports.                                                                   |
| `unix`, `visualstudio`   | `lint`, `diff` | One line per issue or change, with the file, the line and the column: for the job log, and for the problem matchers of editors and of some systems.                                                     |

The `junit` report marks every issue as a failed test, warnings too, so a test tab can show failures
on a job that passed: the status of the job comes from the exit status, which warnings do not
change.

## Installing the CLI in a job

With npm, on an image that has Node.js 18 or later; the package installs the binary of the job's
platform, Linux x64, macOS arm64 or Windows x64:

```sh
npm install -g @mykulyak/api-genie
```

From the release archive, on any Linux x64 image with `curl` and `tar`. The archive holds the four
binaries, and `tar` extracts the one named; the `.sha256` file next to each asset is in `sha256sum`
format:

```sh
version=0.10.0
curl -fsSLO "https://github.com/mykulyak/api-genie/releases/download/cli-$version/apigenie-$version-linux-x64.tar.gz"
curl -fsSLO "https://github.com/mykulyak/api-genie/releases/download/cli-$version/apigenie-$version-linux-x64.tar.gz.sha256"
sha256sum -c "apigenie-$version-linux-x64.tar.gz.sha256"
tar xzf "apigenie-$version-linux-x64.tar.gz" apigenie
./apigenie --version
```

In a container, with the `mykulyak/apigenie` image, Alpine Linux with the four binaries and
`apigenie` as its entry point; the files are read from the mounted directory:

```sh
docker run --rm -v "$PWD:/mnt/data" mykulyak/apigenie:0.10.0 lint api/openapi.yaml
```

A system that runs the job inside the image has to replace the entry point with a shell; the
[GitLab CI](/docs/ci/gitlab#the-docker-image) page shows how. Pin the version in every case: a check
that moves with `latest` changes under the pull requests. [Downloads](/downloads) lists the
packages for the other platforms.

## Comparing with the base branch

The `diff` check compares the file of the pull request with its version on the branch the pull
request merges into. The system names that branch in a variable, and the job fetches its tip, which
a shallow clone does not have, and takes the file from it:

```sh
git fetch --depth=1 origin "$BASE"
git show FETCH_HEAD:api/openapi.yaml > base.yaml
apigenie diff base.yaml api/openapi.yaml
```

`FETCH_HEAD` is what the fetch brought, whatever branches the clone tracks; a clone of the pull
request branch alone, which most systems make, has no `origin/main` even after the fetch.

| System              | The base branch                                                                                   |
| ------------------- | ------------------------------------------------------------------------------------------------- |
| GitHub Actions      | `github.base_ref`; the action finds it itself.                                                    |
| GitLab CI           | `CI_MERGE_REQUEST_TARGET_BRANCH_NAME`, in a merge request pipeline.                               |
| Azure Pipelines     | `System.PullRequest.TargetBranch`, as `refs/heads/main` for Azure Repos and `main` for GitHub.    |
| Jenkins             | `CHANGE_TARGET`, in a multibranch pipeline.                                                       |
| CircleCI            | None. The job names the branch, or reads the pull request through the provider's API.            |
| Bitbucket Pipelines | `BITBUCKET_PR_DESTINATION_BRANCH`, in a pull request pipeline.                                    |

A file the pull request adds has no base version, and `git show` fails; the script below skips the
comparison then. To compare with the last release instead of the base branch, fetch its tag:
`git fetch --depth=1 origin tag v1.4.0` and `git show v1.4.0:api/openapi.yaml`.

## One script for the three checks

Every check runs even when an earlier one fails, so the pull request shows all three results at
once; the exit status is 1 when any of them failed. `BASE` is the base branch from the table above.

```sh
#!/bin/sh
# The three checks of api/openapi.yaml; exits with 1 when one of them fails.
status=0
apigenie lint --formatter junit api/openapi.yaml > lint.xml || status=1
apigenie format --check api/openapi.yaml || status=1
git fetch --depth=1 origin "$BASE"
if git show FETCH_HEAD:api/openapi.yaml > base.yaml 2>/dev/null; then
  apigenie diff base.yaml api/openapi.yaml || status=1
fi
exit $status
```

The script was run with the CLI 0.10.0 in a shallow clone of a pull request branch that removes an
operation, with `BASE=main`: the lint report was written, the formatting check passed, the
comparison reported the breaking change, and the exit status was 1. For a file the branch added,
the comparison was skipped.
