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

@browsonic/cli

v1.2.4

Published

CLI for the Browsonic SDK — sourcemap upload, release management. Apache-2.0.

Downloads

266

Readme

@browsonic/cli

Command-line companion for the Browsonic SDK. One working command: upload-sourcemaps — walks a dist tree and POSTs every .map file to the Browsonic service. help and version are the only other subcommands. No further commands exist or are planned; the package.json description still advertises "release management", which has no implementation and no design behind it anywhere in the repos.

Status (2026-07-28)

  • npm has 1.2.4, and it matches main. Published 2026-07-27; it carries the --version fix and the part-name fix below. (1.2.2 was version-stamped but never published as a release of its own.)
  • The upload part-name mismatch is fixed in every supported version. Up to and including 1.2.1 the CLI sent the map as a multipart part named sourcemap, while POST /v1/sourcemaps in browsonic-service reads file — so a real upload was rejected with 400 before any of the CLI's logic mattered. Confirmed 2026-07-27 by capturing the CLI's actual multipart body and reading SourceMapController.upload at service HEAD. From 1.2.3 the CLI posts file; upgrade if you are on <= 1.2.1. The service also accepts sourcemap as a deprecated alias, so an already-installed <= 1.2.1 works against a service carrying the alias.

Everything below describes what the code on main does today, which is what 1.2.3 shipped.

Pure TypeScript, zero npm runtime dependencies — Node 20+'s built-in fetch / FormData / Blob plus node:util.parseArgs. The pipeline design lives in docs/design/SOURCEMAP_PIPELINE.md; it now matches the shipped service on the multipart contract and the size cap, but one detail remains stale — the retention policy (see What this package does NOT do).

Install

# Per-project (recommended for CI)
npm install --save-dev @browsonic/cli

# Or run ad-hoc with npx
npx @browsonic/cli upload-sourcemaps --dist-path ./dist --release v1.2.3 --dry-run

Requires Node ≥ 20. The bin is named browsonic, so npx browsonic … works once the package is a project dependency (npx resolves node_modules/.bin first); without a local install, use npx @browsonic/cli … so npx does not go looking for an unrelated browsonic package on the registry.

Commands

| Command | What it does | | ----------------------------- | ---------------------------------------------------------------------- | | browsonic upload-sourcemaps | Walk a dist tree and upload every *.map. Flags below. | | browsonic help [<topic>] | Print usage. help upload-sourcemaps prints that command's flags. | | browsonic version | Print the CLI version. Also --version / -v, or no args for help. |

--version / -v are recognised only in the leading position (browsonic -v). Passed after a subcommand they are unknown options — see Exit codes.

Quickstart — upload-sourcemaps

Wire it into your CI's post-build step (or your bundler plugin):

BROWSONIC_API_ENDPOINT=https://api.browsonic.com \
BROWSONIC_APP_KEY=app_xyz \
BROWSONIC_SOURCEMAP_TOKEN=sm_upload_abc123 \
npx browsonic upload-sourcemaps --dist-path ./dist --release "$GITHUB_SHA"

The CLI walks ./dist recursively (skipping node_modules and .git, max depth 12), finds every *.map file, and POSTs each one to <BROWSONIC_API_ENDPOINT>/v1/sourcemaps as multipart/form-data with an Authorization: Bearer <BROWSONIC_SOURCEMAP_TOKEN> header. Parts sent: release, filename (path relative to --dist-path, forward-slashed), appKey, file (the map bytes), plus dist when --dist is passed. Uploads are sequential, not parallel.

Server-side constraints worth knowing before you wire this up:

  • release and --app-key must match [A-Za-z0-9._-]{1,128}; filename must match [A-Za-z0-9._/-]{1,512} and contain no ... A commit SHA is a fine release tag.
  • A single map above 10 MiB (sentinel.storage.sourcemap.max-upload-bytes, default 10485760) is rejected with 413.
  • /v1/sourcemaps is plan-gated behind the source_maps feature. A tenant without it gets 402, which the CLI reports as an ordinary per-file failure.
  • The service ignores distPOST /v1/sourcemaps has no such parameter, and the stored row is keyed on (tenantId, appKey, release, filename). Re-uploading the same coordinate overwrites the object and the row and returns 200 with the same id; it is not a 409.

The --release tag has to line up with what the SDK reports, or the dashboard cannot pair frames to maps. The dashboard symbolicates with event.release ?? event.clientVersion, and the SDK emits no release field through 3.21.0 (npm latest, checked 2026-07-29), so in practice the tag must equal your BrowsonicConfig.clientVersion.

Per-file progress lines stream as the upload runs ( is the service-issued sourcemap id, a UUID):

[browsonic] upload-sourcemaps: 12 file(s) found under ./dist
  ✓ chunks/main.abc123.js.map (482103 bytes) → e2f1c3d4-5a6b-7c8d-9e0f-112233445566
  ✓ chunks/about.def456.js.map (102301 bytes) → 8b7a6959-4c3d-2e1f-0a9b-887766554433
  ...
[browsonic] upload-sourcemaps: 12/12 succeeded

If the walk finds no .map files the CLI prints no *.map files found under <path> — nothing to upload. and exits 0. A build that silently stopped emitting sourcemaps will not fail your pipeline.

Dry-run mode

--dry-run reads each discovered file (so the byte counts are real) but makes no HTTP call, and exits 0:

[browsonic] upload-sourcemaps: 2 file(s) found under ./dist (dry-run)
  ✓ about.def456.js.map (61 bytes) → dry-run:v1.2.3:about.def456.js.map
  ✓ chunks/main.abc123.js.map (61 bytes) → dry-run:v1.2.3:chunks/main.abc123.js.map
[browsonic] upload-sourcemaps: 2/2 succeeded (dry-run — no HTTP requests made)

What it does prove: your build emits .map files where you think it does, their relative filenames are what you expect, their sizes are sane, and every required flag / env var is present.

What it does not prove: that the token, app key or endpoint are valid, or that the request the CLI would build is one the service accepts — dry-run never constructs the multipart body. That gap is exactly how the part-name mismatch in the status note above went unnoticed; the multipart part names are now pinned by unit tests on both sides instead (packages/cli/src/upload.test.ts, SourceMapUploadWireContractTest in browsonic-service).

npx browsonic upload-sourcemaps --dist-path ./dist --release v1.2.3 --dry-run

The command shape is identical with and without --dry-run — drop the flag for the real upload.

Flags (upload-sourcemaps)

| Flag | Required | Env | Description | | -------------------- | -------- | --------------------------- | ---------------------------------------------------------------------- | | --dist-path <path> | yes | — | Directory to walk for *.map files (e.g. ./dist). | | --release <tag> | yes | — | Release tag matching the SDK's BrowsonicConfig.clientVersion. | | --app-key <key> | yes | BROWSONIC_APP_KEY | App key from the Browsonic dashboard. | | --token <token> | yes | BROWSONIC_SOURCEMAP_TOKEN | Sourcemap-upload Bearer token (sm_upload_…). | | --base-url <url> | yes | BROWSONIC_API_ENDPOINT | Ingest service base URL. | | --dist <name> | no | — | Distribution discriminator. Sent, but the service ignores it. | | --dry-run | no | — | Skip the HTTP call (see above). | | --bail-on-error | no | — | Abort batch on first per-file failure (default: continue + aggregate). | | -h, --help | no | — | Print this command's usage and exit 0. |

Tokens passed via env vars never leak onto CI shell history. Mix-and-match (env var for --token, flag for --release) works fine.

Exit codes

| Code | Meaning | | ---- | ------------------------------------------------------------------------------------------------------ | | 0 | Every file uploaded, dry-run, no .map files found, or help / version | | 1 | At least one file failed; --dist-path does not exist or is not a directory; an unrecognised flag | | 2 | A required flag (or its env fallback) is missing, or the command is unknown | | 3 | HTTP auth failure (401 / 403) — token doesn't have sourcemap-upload scope | | 4 | HTTP payload-too-large (413) — sourcemap exceeded the ingest size limit | | 5 | HTTP transient failure (5xx) — caller should retry |

Two things CI scripts get wrong here:

  • 3 / 4 / 5 are only reachable with --bail-on-error. By default per-file HTTP failures are caught inside the run loop and aggregated, so the process exits 1 no matter what the server said. Verified by running the CLI against a local 401: exit 1 without the flag, exit 3 with it.
  • An unrecognised flag exits 1, not 2. node:util.parseArgs throws a TypeError that is not the CLI's own ArgsError, so the bin entry prints [browsonic] fatal: TypeError [ERR_PARSE_ARGS_UNKNOWN_OPTION] … with a stack trace and exits 1. Missing required flags and unknown commands do exit 2.

A 402 from the plan gate is not mapped and surfaces as 1.

Programmatic API

The command is exposed as a library function for consumers building bundler plugins (@browsonic/build-tools wraps exactly this):

import { runUploadSourcemaps } from "@browsonic/cli";

await runUploadSourcemaps(
  {
    command: "upload-sourcemaps",
    distPath: "./dist",
    release: "v1.2.3",
    appKey: process.env.BROWSONIC_APP_KEY!,
    token: process.env.BROWSONIC_SOURCEMAP_TOKEN!,
    baseUrl: process.env.BROWSONIC_API_ENDPOINT!,
    dryRun: false,
    bailOnError: false,
  },
  {
    log: (m) => console.log(m),
    error: (m) => console.error(m),
  },
);

It resolves to { discovered, uploaded, failed, results }. Lower-level building blocks (uploadOne, uploadOneDryRun, discoverSourceMaps, relativeFilenameForUpload, UploadError, parseCliArgs, CLI_VERSION) are also exported — see src/index.ts.

Defensive contract

  • Per-file failures are aggregated by default — a single bad file doesn't abort the whole batch (--bail-on-error overrides). See the exit-code caveat above for what that costs you.
  • An HTTP failure raises an UploadError carrying the status, the filename and the response body; the run loop prints ✗ <filename>: <message>, so CI logs name the file. Transport-level failures (DNS, TLS, connection refused) are plain fetch errors with no status — they still get the ✗ <filename>: prefix, but nothing maps them to exit codes 3/4/5.
  • uploadOne accepts a fetch override for tests. runUploadSourcemaps does not expose one — UploadSourcemapsArgs has no fetch field — so anything testing the command end-to-end has to stub the global.

What this package does NOT do

  • Bundler plugins. Use the companion @browsonic/build-tools package for its webpack / vite / rollup / esbuild plugins. They call runUploadSourcemaps from this package directly, so they inherit this package's wire contract — including the part-name fix described at the top, and the same caveat about published versions.
  • Inline-sourcemap extraction. Modern bundlers default to external .map files. If your config emits inline sourcemaps (//# sourceMappingURL=data:… at the end of the bundle), extract them to .map siblings before invoking the CLI. Reviewed 2026-07-27 and left closed — not deferred: every bundler this pipeline targets emits external maps by default, and no consumer has asked. Open an issue if your config genuinely cannot.
  • Symbolication. That happens server-side at dashboard read time via POST /v1/symbolicate; this CLI only uploads.
  • Sourcemap retention or pruning — and neither does the service. The design doc describes an N-releases-per-app policy (default 50) with an "archived" flag, but as of 2026-07-27 nothing implements it: browsonic-service has no retention job, no maxReleases setting and no archive flag. Old uploads stay until someone deletes them explicitly.
  • Token CRUD / sourcemap list and delete. Those shipped server-side — token CRUD (GET/POST/DELETE /v1/sourcemap-tokens, OWNER-gated, SOURCEMAP_UPLOAD-type keys added by Flyway V40) plus DELETE /v1/sourcemaps/{id} and a filtered, paginated GET /v1/sourcemaps — but this CLI stays upload-only. Mint, revoke and prune from the dashboard or those endpoints directly.

License

Apache-2.0. See the repo root LICENSE and NOTICE.