Publish Kubb snapshots with GitLab CI
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.tson 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_TOKENin 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 forKUBB_TOKENand type Registry for installs. tsdownin the development dependencies, next tokubb, sonpm ciinstalls it. A snapshot builds itsdistfiles with it, and Kubb does not install it by default:npm i -D tsdown.
Configure the job
Add this job:
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_BRANCHWith 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:
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'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.