GitLab CI
Publish an installable Kubb Studio snapshot from GitLab CI so reviewers can compare generated files against the base branch and previous runs.
Prerequisites
- A repository with a working
kubb.config.ts. Follow Installation if you have not set up Kubb. - A Kubb Studio organization and an organization CI API key.
- Commit your npm lockfile and add
kubbto the project’s development dependencies sonpm ciinstalls the CLI.
Store the CI API key as KUBB_TOKEN in a masked CI/CD variable. Snapshot installation uses a separate registry key. Only expose the CI secret to trusted pipelines.
Configure the workflow
Add this job:
snapshot:
stage: build
image: node:22
resource_group: kubb-snapshot-$CI_COMMIT_REF_SLUG
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.
resource_group keeps two pipelines on the same branch or merge request from running the job at once. Registering the agent again ends the other run's session.
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:
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}`)
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. Snapshots expire after seven days; schedule a default-branch run if it can go a week without a push.
To consume the generated package, follow Install the snapshot.