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/bitbucket

v2.0.0

Published

Bitbucket-backed StorageRepository for Laika CMS via the Cloud REST v2 API. Authenticates with an app password or OAuth2 bearer token. Runtime-agnostic — only depends on `fetch`.

Readme

@laikacms/bitbucket

A Bitbucket-backed StorageRepository for Laika CMS via the Cloud REST v2 API. Completes the git-platform triumvirate alongside @laikacms/github and @laikacms/gitlab.

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

@laikacms/bitbucket/storage-bb

import { BitbucketStorageRepository } from '@laikacms/bitbucket/storage-bb';
import { markdownSerializer } from 'laikacms/storage-serializers-markdown';

const repo = new BitbucketStorageRepository({
  workspace: 'esstudio',
  repo: 'content',
  branch: 'main',
  auth: {
    appPassword: { username: 'alice', password: process.env.BITBUCKET_APP_PW! },
    // or: oauthToken: process.env.BITBUCKET_OAUTH_TOKEN!,
    // or: tokenProvider: () => refreshedAccessToken(),
  },
  serializerRegistry: { md: markdownSerializer },
  defaultFileExtension: 'md',
  commitAuthor: { name: 'Laika Bot', email: '[email protected]' },
});

The Bitbucket-shaped quirk: one endpoint for every write

GitHub and GitLab each expose separate endpoints for createOrUpdateFileContents / deleteFile. Bitbucket folds them into one call: POST /repositories/{ws}/{repo}/src with a multipart body. Each form field whose name is a file path adds or updates that file; each form field literally named files whose value is a path deletes that path. The entire commit lands atomically.

This repository keeps the storage-contract surface one-file-at-a-time for parity with the other git platforms, but the underlying dataSource.commit({puts, deletes, commitMessage, author}) is a single round-trip multi-file commit you can call directly when you want one.

How operations map

| Operation | Bitbucket call | | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | getObject | GET /src/{branch}/{path} for content + GET /src/{branch}/{path}?format=meta for metadata | | createObject / updateObject / createOrUpdateObject | POST /src with the path as a form-field name | | removeAtoms | POST /src with files=<path> for each delete | | getFolder | GET /src/{branch}/{path}/ (trailing slash) — Bitbucket only returns a listing for trailing-slash URLs | | listAtomSummaries | same; paginates through next until exhausted | | createFolder | writes a .keep placeholder (git tracks files, not folders) |

Auth model

Two modes, both handled behind the scenes:

  • App password (auth.appPassword) — Bitbucket's pre-OAuth credential format. Username + app-password tuple sent as HTTP Basic.
  • OAuth 2.0 (auth.oauthToken or auth.tokenProvider) — modern flow. Token sent as Bearer.

The end-to-end auth-header test verifies that the right scheme reaches the wire (Basic <b64> for app passwords, Bearer <token> for OAuth), so misconfiguration surfaces early.

  • Extra headers (auth.headers) — a plain object of headers merged into every request, after the Authorization header and before any per-call override. No default. Useful for Bitbucket-adjacent proxies or gateways that require an additional header such as a tenant ID or a User-Agent override:

    auth: {
      oauthToken: process.env.BITBUCKET_OAUTH_TOKEN!,
      headers: { 'X-Tenant-Id': 'esstudio' },
    },

Advanced options

  • fetch — a custom fetch implementation. Defaults to globalThis.fetch. Useful for tests (inject a mock/spy) or non-standard runtimes that don't expose a global fetch:

    const repo = new BitbucketStorageRepository({
      // ...
      fetch: mySpyFetch,
    });
  • apiUrl — overrides the API base URL. Defaults to https://api.bitbucket.org/2.0. Useful for pointing at a self-hosted Bitbucket Data Center mirror or a test double:

    const repo = new BitbucketStorageRepository({
      // ...
      apiUrl: 'https://bitbucket.internal.example.com/2.0',
    });
  • 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. Omit to let Bitbucket infer the identity from the auth credential.

  • 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; the on-server file name is <key>.<ext> where <ext> is picked from the registered serializers (matches every other git-platform repository in the suite).
  • metadata.revisionId is the commit hash that most recently touched the file. No native optimistic-concurrency on update — Bitbucket's commit endpoint doesn't accept an If-Match parallel.
  • Pagination. next-URL drained to completion, then in-memory offset/page styles applied.
  • Errors. 401 → UpstreamUnAuthorizedError (Bitbucket is an upstream, so a rejected credential surfaces as UpstreamUnAuthorizedError, not AuthenticationError), 403 → ForbiddenError, 404 → NotFoundError, 429 → TooManyRequestsError, 5xx → ServiceUnavailableError.

What this does not do

  • No commit signing.
  • No PR / merge-request integration. Writes go directly to the configured branch.
  • No webhook subscriptions.