Skip to content
Integrations

Publish snapshots with GitLab CI

Publish Kubb Studio snapshots from GitLab CI and review generated-file changes.

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 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 > Developer > Tokens, with type CI for KUBB_TOKEN and type Registry for installs.

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

kubb ships the Studio runtime, so 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

Open the snapshot in Studio to review generated changes. 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.

See also