npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@laikacms/gitlab

v2.0.0

Published

GitLab-backed StorageRepository for Laika CMS. Stores content in a GitLab project via the REST v4 API; authenticates with a Personal Access Token or OAuth bearer token. Runtime-agnostic — only depends on `fetch`.

Readme

@laikacms/gitlab

GitLab-backed StorageRepository for Laika CMS. Stores content as commits in a GitLab project via the REST v4 API; authenticates with a Personal Access Token, an OAuth bearer token, or a CI job token. The runtime parallel of @laikacms/github, with one major simplification: GitLab tokens are long-lived, so there is no App-installation flow — bring a token, point at a project, and write.

Runtime-agnostic: only depends on fetch. Works on Node, Bun, Deno, Cloudflare Workers, and the browser.

Why a PAT (vs an App)

GitLab Personal Access Tokens scope by project membership and permission set (read_repository, write_repository, api). Combined with the GitLab "service account" feature (or a regular bot user), they cover the same threat model as a GitHub App installation token but without the JWT-mints-installation-token dance — one fewer moving part to break.

For multi-tenant hosting where end users grant you access to their own projects, prefer OAuth 2 bearer tokens (oauthToken) over PATs.

Usage

import { GitlabStorageRepository } from '@laikacms/gitlab/storage-gl';
import { markdownSerializer } from 'laikacms/storage-serializers-markdown';

const repo = new GitlabStorageRepository({
  projectId: 'esstudio/content', // numeric id OR `group/subgroup/project`
  branch: 'main',
  auth: { token: process.env.GITLAB_PAT! },
  // apiUrl: 'https://gitlab.example.com/api/v4', // self-hosted; defaults to gitlab.com
  serializerRegistry: { md: markdownSerializer },
  defaultFileExtension: 'md',
  commitAuthor: { name: 'Laika Bot', email: '[email protected]' },
});

OAuth bearer token

new GitlabStorageRepository({
  projectId: 12345,
  branch: 'main',
  auth: { oauthToken: userOauthToken },
  serializerRegistry,
  defaultFileExtension: 'json',
});

CI job token

Useful when the same repo also hosts a GitLab CI pipeline that writes content back:

new GitlabStorageRepository({
  projectId: process.env.CI_PROJECT_ID!,
  branch: process.env.CI_COMMIT_REF_NAME!,
  auth: { jobToken: process.env.CI_JOB_TOKEN! },
  serializerRegistry,
  defaultFileExtension: 'md',
});

Extra auth headers

auth.headers are merged into every request alongside the auth-method header. The extra headers are applied first; the auth-method header (PRIVATE-TOKEN, Authorization, or JOB-TOKEN) is always written after them, so you cannot accidentally override credentials via auth.headers. Defaults to none. Useful for GitLab instances behind a reverse proxy that requires its own header (e.g. an internal gateway token) alongside your normal GitLab credentials:

new GitlabStorageRepository({
  projectId: 'esstudio/content',
  branch: 'main',
  auth: {
    token: process.env.GITLAB_PAT!,
    headers: { 'X-Gateway-Token': process.env.GATEWAY_TOKEN! },
  },
  serializerRegistry,
  defaultFileExtension: 'md',
});

Advanced options

  • ignoreList — glob patterns for files excluded from directory listings. When supplied, overrides the built-in list entirely. Default:

    **/.keep
    **/.DS_Store
    **/Thumbs.db
    **/desktop.ini
    **/.catalog
    **/.laikacms
  • commitAuthor{ name: string; email: string } stamped as both the author and committer on every write call (shown in the usage examples above). Omit to let GitLab fall back to the identity of the authenticated token.

  • determineExtension — custom resolver that picks the file extension for a new object given its key and metadata. Replaces the built-in defaultDetermineExtension logic when provided.

Behaviour notes

  • Extension hiding. Keys are extension-free at the boundary, exactly like @laikacms/github and laikacms/storage-fs. The on-server file extension is chosen from the registered serializers and looked up on read.
  • Upsert via POST → PUT. createOrUpdate first tries POST /repository/files/... (create); on the "already exists" path (HTTP 400 with the matching message) it transparently retries with PUT (update). The revisionId returned in metadata is the file's last_commit_id, which you can pass back via update.metadata.revisionId for optimistic-concurrency updates.
  • Empty directories. Git tracks files, not directories. createFolder writes a .keep file (filtered out of listings via the same ignore list as storage-fs and @laikacms/github).
  • Listings on missing folders are reported as recoverableErrors (a NotFoundError). Note that @laikacms/github behaves differently: GitHub's API cannot distinguish an empty directory from a missing one (both return 404), so the GitHub backend maps 404 → empty results instead of a NotFoundError. Bitbucket matches GitLab — missing folders surface as a recoverableError.
  • Pagination. Cursor pagination is not supported. The directory listing pages through X-Next-Page until exhausted, then offset/page styles are applied in memory.
  • Self-hosted. Pass apiUrl: 'https://gitlab.example.com/api/v4' for a self-hosted instance.
  • Custom fetch. Defaults to globalThis.fetch. Pass fetch to swap in a different implementation — e.g. a mocked fetch in tests, or an instrumented/proxied one on a runtime without a global fetch.
  • userAgent. Sent as the User-Agent header on every request. Defaults to @laikacms/gitlab. Override it to identify your app in GitLab's request logs, e.g. userAgent: 'my-cms/1.4.0'.

What this does not do

  • No GitLab Merge Request integration. Each write is a direct commit on the configured branch. If you want a "draft → MR → review" workflow, do it at a layer above (e.g. wire branch to a per-editor branch and open the MR yourself).
  • No webhooks. If you need to react to changes pushed from elsewhere, subscribe to GitLab webhooks separately and invalidate your caches.
  • No LFS. Objects are stored as plain files; binary assets belong behind the assets API, not the storage API.