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

@lubed/test-uploader

v0.6.0

Published

Upload test results (JUnit XML, Vitest/Jest/Go/Rust/Python/Ruby JSON) to Merged.

Readme

@lubed/test-uploader

Warning This package is not ready for public use yet. The package is public, but you will not be able to finish setting up test result uploads until the main app is available. We will do a proper launch soon.

Upload test results from any framework that emits JUnit XML or a native JSON reporter (Vitest, Jest, Go, Rust, Python, Ruby) to Merged. The CLI captures git and CI context, attaches it to the run, and posts the raw report to the API. Parsing happens server-side.

Install

npm install -D @lubed/test-uploader
# or
pnpm add -D @lubed/test-uploader
# or
yarn add -D @lubed/test-uploader

For one-off use without installing:

npx -y @lubed/test-uploader upload --app <id>

Non-JS users can skip the CLI and curl against the same endpoint directly (see the per-framework recipes in the platform docs).

Authentication

Set MERGED_API_KEY in your environment. Create the key in the dashboard under Settings → API keys. The key needs the testRun.create permission.

| Env var | Required | Default | Description | | ---------------- | -------- | ----------------------- | -------------------------------------------------- | | MERGED_API_KEY | yes | — | Bearer token used for the upload. | | MERGED_API_URL | no | https://api.lube.work | API base URL. Override for staging or self-hosted. |

CI context is auto-detected from the standard env vars (CI, GITHUB_ACTIONS, BUILDKITE, CIRCLECI, GITLAB_CI, TRAVIS). Commit SHA and branch are pulled from GITHUB_SHA / GITHUB_REF_NAME / BUILDKITE_COMMIT / BUILDKITE_BRANCH / CI_COMMIT_SHA / CI_COMMIT_REF_NAME when present, falling back to git rev-parse.

Usage

merged-tests upload --app <applicationId> [--file <path>] [--format <format>] [--env <label>] [--api-url <url>]

Flags

| Flag | Required | Default | Description | | ------------------- | -------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | --app <id> | yes | — | Application ID to tag the run to. | | --file <path> | no | auto-detected | Path to the report file. | | --format <format> | no | auto-detected from filename | One of: junit_xml, vitest_json, jest_json, gotest_json, gotestsum_json, nextest_json, pytest_json, rspec_json. | | --env <label> | no | none | Optional environment label for filtering. | | --service-id <id> | no | none | Stable id of the contributing service within the application. Preferred over --service-name when known. | | --service-name <n>| no | none | Contributing service name (e.g. api-primary-v2). Matched or auto-created within the application. | | --api-url <url> | no | $MERGED_API_URL or https://api.lube.work | API base URL. | | --json | no | off | Emit the result as a single JSON object on stdout ({ runId, format, totals, dashboardUrl }) for CI consumption. |

Auto-detection

When --file is omitted, the CLI probes the current working directory in this order. The first match wins.

| Probe order | File / Directory | Format | | ----------- | ------------------------------- | ---------------- | | 1 | vitest-results.json | vitest_json | | 2 | jest-results.json | jest_json | | 3 | gotestsum.json | gotestsum_json | | 4 | junit.xml | junit_xml | | 5 | test-results/*.xml | junit_xml | | 6 | target/surefire-reports/*.xml | junit_xml | | 7 | target/test-results/*.xml | junit_xml | | 8 | build/test-results/*.xml | junit_xml |

When --file is provided without --format, the format is inferred from the filename extension (.xmljunit_xml, known JSON filenames → their matching format). Pass --format explicitly to override either lookup.

Per-framework recipes

Vitest

pnpm exec vitest run --reporter=json --outputFile=vitest-results.json
merged-tests upload --app $MERGED_TEST_APP_ID

For JUnit XML output instead:

pnpm exec vitest run --reporter=junit --outputFile=junit.xml
merged-tests upload --app $MERGED_TEST_APP_ID --file junit.xml --format junit_xml

Jest

jest --json --outputFile=jest-results.json
merged-tests upload --app $MERGED_TEST_APP_ID --format jest_json

Playwright

npx playwright test --reporter=json > playwright-results.json
merged-tests upload --app $MERGED_TEST_APP_ID --file playwright-results.json --format jest_json

Go (go test -json)

Non-JS users typically curl directly. The CLI also works if you have Node available:

go test ./... -json | tee gotest.ndjson
merged-tests upload --app $MERGED_TEST_APP_ID --file gotest.ndjson --format gotest_json

Go (gotestsum, preferred in CI)

gotestsum --jsonfile gotestsum.json -- ./...
merged-tests upload --app $MERGED_TEST_APP_ID

Rust (cargo nextest, preferred)

cargo nextest run --message-format libtest-json-plus > nextest.ndjson
merged-tests upload --app $MERGED_TEST_APP_ID --file nextest.ndjson --format nextest_json

Python (pytest with pytest-json-report)

pip install pytest-json-report
pytest --json-report --json-report-file=pytest.json
merged-tests upload --app $MERGED_TEST_APP_ID --file pytest.json --format pytest_json

Ruby (RSpec)

rspec --format json --out rspec.json
merged-tests upload --app $MERGED_TEST_APP_ID --file rspec.json --format rspec_json

Java / Kotlin (Maven Surefire)

mvn test
# Auto-detection finds target/surefire-reports/*.xml
merged-tests upload --app $MERGED_TEST_APP_ID

Java / Kotlin (Gradle)

./gradlew test
merged-tests upload --app $MERGED_TEST_APP_ID --file build/test-results/test/TEST-*.xml --format junit_xml

.NET

dotnet test --logger "junit;LogFilePath=TestResults/junit.xml"
merged-tests upload --app $MERGED_TEST_APP_ID --file TestResults/junit.xml --format junit_xml

Any other framework (JUnit XML fallback)

Any test runner that can emit JUnit XML works. Examples: PHPUnit (--log-junit), ExUnit (junit_formatter), Mocha (mocha-junit-reporter), Karma (karma-junit-reporter).

<your-test-runner> --junit-output=junit.xml
merged-tests upload --app $MERGED_TEST_APP_ID --file junit.xml --format junit_xml

CI recipes

GitHub Actions

name: Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: pnpm/action-setup@v6
      - uses: actions/setup-node@v6
        with: { node-version: "lts/*", cache: pnpm }
      - run: pnpm install
      - run: pnpm vitest run --reporter=json --outputFile=vitest-results.json
      - name: Upload test results
        if: always()
        env:
          MERGED_API_KEY: ${{ secrets.MERGED_API_KEY }}
          MERGED_TEST_APP_ID: ${{ secrets.MERGED_TEST_APP_ID }}
        run: npx -y @lubed/test-uploader upload --app $MERGED_TEST_APP_ID

if: always() ensures failing runs still upload. Add the same upload step to GitLab CI, CircleCI, or Buildkite using their secret-injection conventions.

Add --json to capture the result as a single JSON object on stdout ({ runId, format, totals, dashboardUrl }) when you want to wire the run id or dashboard URL into later CI steps.

Programmatic usage

import { uploadTestRun, captureContext, detectReportFile } from "@lubed/test-uploader"

const { file, format } = detectReportFile({ cwd: process.cwd() })
const context = captureContext({ environmentLabel: "staging" })

const result = await uploadTestRun({
  apiUrl: process.env.MERGED_API_URL ?? "https://api.lube.work",
  apiKey: process.env.MERGED_API_KEY,
  applicationId: "app_abc123",
  file,
  format,
  context,
})

console.log(`Uploaded run ${result.runId}: ${result.totals.passed} passed, ${result.totals.failed} failed`)

Exit codes

| Code | Meaning | | ---- | ----------------------------------------------- | | 0 | Upload succeeded. | | 1 | Upload failed (any error). Stderr explains why. |

The CLI surfaces server errors verbatim. Common cases:

| Error | Cause | Fix | | ---------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | | MERGED_API_KEY env var is required. | Env var not set. | Export it or pass it in the CI step. | | Could not find a test report file. Tried: ... | Auto-detection found none of the conventional paths. | Pass --file <path> explicitly. | | Upload failed with status 404: Application ... not found. | Wrong app ID or API key belongs to a different org. | Confirm the app ID in the dashboard URL and the API key's organization. | | Upload failed with status 413: ... | Report file or result count exceeded the limit (10MB / 50,000 results). | Split the run by package or test suite. | | Upload failed with status 400: Unsupported test report format ... | Format key typo. | Use one of the eight valid keys listed above. | | Upload failed with status 422: Failed to parse report with ...Parser | Report file is malformed for the chosen format. | Confirm --format matches the file; re-emit the report. |

Supported formats

| Format key | Description | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | junit_xml | JUnit XML. Universal fallback. Works for Java, Kotlin, .NET, pytest, RSpec, Rust (cargo2junit), Go (gotestsum --junitfile), Vitest/Jest/Playwright JUnit reporters, and anything else that emits JUnit. | | vitest_json | Vitest JSON reporter. Includes retry metadata. | | jest_json | Jest JSON reporter. Includes invocations for retries. | | gotest_json | NDJSON event stream from go test -json. | | gotestsum_json | gotestsum --jsonfile output (same shape as go test -json). | | nextest_json | cargo nextest libtest-json-plus format. | | pytest_json | pytest-json-report plugin output. | | rspec_json | RSpec --format json output. |