GitLab CI
Basic pipeline
Add a vipr job to your .gitlab-ci.yml. The job installs Vipr via npx, runs analysis, and
uploads the JSON report as an artifact so you can inspect it from the pipeline UI.
vipr:
image: node:22-slim
stage: test
script:
- npx --yes @vipr/cli analyze "src/**/*.{ts,tsx}"
--format json
--output vipr-report.json
--fail-threshold 70
--fail-on-critical
--quiet
artifacts:
when: always
paths:
- vipr-report.json
expire_in: 7 days
The job exits non-zero when either gate trips (score below threshold or a critical finding is present), which marks the pipeline as failed and blocks the merge request.
Merge request comments
Use the Markdown format to produce a human-readable report and post it as an MR comment with the
GitLab API. This approach requires a CI token with api scope or the built-in CI_JOB_TOKEN.
vipr:
image: node:22-slim
stage: test
script:
- npx --yes @vipr/cli analyze "src/**/*.{ts,tsx}"
--format markdown
--output vipr-comment.md
--quiet
- |
BODY=$(cat vipr-comment.md | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))")
curl --silent --fail \
--request POST \
--header "PRIVATE-TOKEN: ${GITLAB_API_TOKEN}" \
--header "Content-Type: application/json" \
--data "{\"body\": ${BODY}}" \
"${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/notes"
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
artifacts:
when: always
paths:
- vipr-comment.md
expire_in: 7 days
Store GITLAB_API_TOKEN as a masked CI variable in Settings > CI/CD > Variables — do not
hardcode it in .gitlab-ci.yml.
Changed-files mode
For fast feedback on large repositories, narrow analysis to files changed in the MR branch:
vipr:
image: node:22-slim
stage: test
script:
- git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
- npx --yes @vipr/cli analyze "src/**/*.{ts,tsx}"
--changed origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME
--format json
--output vipr-report.json
--fail-on-critical
--quiet
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
artifacts:
when: always
paths:
- vipr-report.json
expire_in: 7 days
Use changed-files mode as a fast path alongside — not instead of — a broader baseline job that runs on the main branch.
Full pipeline example
A two-job setup: a fast MR job on changed files, and a full baseline job on every push to main.
stages:
- test
vipr:analysis:mr:
image: node:22-slim
stage: test
script:
- git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
- npx --yes @vipr/cli analyze "src/**/*.{ts,tsx}"
--changed origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME
--format markdown
--output vipr-comment.md
--fail-on-critical
--quiet
- |
BODY=$(cat vipr-comment.md | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))")
curl --silent \
--request POST \
--header "PRIVATE-TOKEN: ${GITLAB_API_TOKEN}" \
--header "Content-Type: application/json" \
--data "{\"body\": ${BODY}}" \
"${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/notes"
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
artifacts:
when: always
paths:
- vipr-comment.md
expire_in: 7 days
vipr:analysis:baseline:
image: node:22-slim
stage: test
script:
- npx --yes @vipr/cli analyze "src/**/*.{ts,tsx}"
--format json
--output vipr-report.json
--fail-threshold 70
--fail-on-critical
--quiet
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
artifacts:
when: always
paths:
- vipr-report.json
expire_in: 30 days
Exit codes
| Code | Meaning |
|---|---|
0 |
Analysis completed and all configured gates passed |
1 |
A gate tripped, or the command could not complete |
GitLab treats any non-zero exit code as a job failure. Use allow_failure: true on the job if
you want Vipr to report findings without blocking the pipeline.