Skip to content
Integrations

Publish Kubb snapshots with GitLab CI

Add a GitLab CI job that runs kubb studio snapshot on each merge request and compares the generated files with the target branch and the previous pipeline.

Publish an installable Kubb Studio snapshot from GitLab CI so reviewers can compare generated files against the target branch and previous pipelines. Store KUBB_TOKEN as a masked CI/CD variable, commit your npm lockfile, and keep kubb and tsdown in the development dependencies so npm ci installs the CLI.

Prerequisites

  • A repository with a working kubb.config.ts on Kubb 5.6.2 or later. Follow Installation if you have not set up Kubb.
  • A Kubb Studio organization and an organization CI API key, stored as KUBB_TOKEN in a secret only trusted pipelines can read. Snapshot installation uses a separate registry key. Create both in Studio under Settings › Developers › Tokens, with type CI for KUBB_TOKEN and type Registry for installs.
  • tsdown in the development dependencies, next to kubb, so npm ci installs it. A snapshot builds its dist files with it, and Kubb does not install it by default: npm i -D tsdown.

Configure the job

Add this job:

.gitlab-ci.yml
snapshot:
  stage: build
  image: node:22
  script:
    - npm ci
    - npx kubb studio snapshot
  rules:
    - if: $CI_MERGE_REQUEST_IID
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

With kubb and tsdown in the development dependencies, npm ci is the only setup. The rules run the job on merge request pipelines and on the default branch, so a merge request has a snapshot of its target branch to compare with.

Post the result as a note

Use --json to capture the snapshot result, then post it as a merge request note with this script. It updates the same note on each pipeline and uses only Node:

scripts/kubb-note.mjs
import { readFileSync } from 'node:fs'

const snapshot = JSON.parse(readFileSync('snapshot.json', 'utf8'))
const marker = '<!-- kubb-snapshot -->'
const counts = (c) => `${c.added.length} added · ${c.changed.length} changed · ${c.removed.length} removed`
const lines = [marker, `Kubb snapshot: \`npm i ${snapshot.url}\``]

const { branchChanges: branch, changes } = snapshot
if (branch) {
  if (branch.base) lines.push(`Changes against \`${branch.branch}\`: ${counts(branch)}`)
  else if (branch.baseFound) lines.push(`No snapshot of \`${branch.branch}\` for this package yet`)
  else lines.push(`No snapshot of \`${branch.branch}\` to compare with`)
}
if (changes?.base) lines.push(`Changes since ${changes.base.commit?.slice(0, 7) ?? changes.base.createdAt}: ${counts(changes)}`)

const { CI_API_V4_URL, CI_PROJECT_ID, CI_MERGE_REQUEST_IID, GITLAB_API_TOKEN } = process.env
const notes = `${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/notes`
const headers = { 'PRIVATE-TOKEN': GITLAB_API_TOKEN, 'Content-Type': 'application/json' }
const existing = (await (await fetch(`${notes}?per_page=100`, { headers })).json()).find((note) => note.body.startsWith(marker))
const response = await fetch(existing ? `${notes}/${existing.id}` : notes, {
  method: existing ? 'PUT' : 'POST',
  headers,
  body: JSON.stringify({ body: lines.join('\n\n') }),
})
if (!response.ok) throw new Error(`GitLab answered ${response.status}`)
.gitlab-ci.yml
  script:
    - npm ci
    - npx kubb studio snapshot --json > snapshot.json
    - '[ -z "$CI_MERGE_REQUEST_IID" ] || node scripts/kubb-note.mjs'
Writing a note needs a project access token with the api scope, stored as GITLAB_API_TOKEN. CI_JOB_TOKEN does not carry it.

Review and install the result

Studio has no diff view for snapshots. The GitHub Action lists the changed generated files in its pull request comment, and kubb studio snapshot --json reports them in changes and branchChanges with any provider. To consume the generated package, follow Install the snapshot.

Snapshots expire after seven days. Schedule a default-branch run if it can go a week without a push.