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

birthplace

v1.0.1

Published

Record where and when an artifact was built and load it into OpenTelemetry Node.js resources

Readme

birthplace

npm version npm downloads CI

Record where and when an artifact was built: capture build metadata with a CLI, package the generated birthplace file with your application, and load it through an OpenTelemetry Node.js ResourceDetector. Every instance of the same artifact gets the same build identity, without needing Git in production.

Build CLI → birthplace.json → deployment package → NodeSDK.resourceDetectors

Tested on Node.js 22 and 24 with OpenTelemetry Resources 2.x.

Usage

Capture metadata during the build

pnpm add birthplace

Generate the birthplace file after compiling if your build cleans dist, while the source checkout and .git directory are still available. Include the file in the final image or deployment package:

pnpm build
pnpm exec birthplace generate --cwd . --output dist/birthplace.json --strict

The birthplace file describes the build. Do not add deployment environment or deployment time to it; those values change independently of the immutable artifact.

Where to run it

Run one birthplace generate call from the build script in package.json, chained explicitly: after the compiler when the file is read at runtime, before the bundler when it is imported.

{ "scripts": { "build": "tsc && birthplace generate --output dist/birthplace.json" } }

Do not rely on prebuild / postbuild scripts; not every package manager version or platform runs them. The platform guides cover the details:

| Platform | What to know | | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | | GitHub Actions | No workflow change; pull-request builds record a temporary merge commit | | Vercel | Expose system environment variables; prefer the import-based setup | | Docker | Generate in the build stage, COPY into the runtime stage; .git is often excluded | | Turborepo and other task caches | A cache hit restores a birthplace file from an older commit | | OpenTelemetry SDK | resourceDetectors replaces the defaults; a failing detector is skipped silently |

Register the detector in NodeSDK()

Install the OpenTelemetry SDK pieces used by your application. birthplace supplies the resource detector; your application owns its exporters, instrumentation, and SDK lifecycle.

pnpm add @opentelemetry/api @opentelemetry/resources @opentelemetry/sdk-node \
  @opentelemetry/exporter-trace-otlp-http @opentelemetry/auto-instrumentations-node

Create instrumentation.mjs next to dist/, include it in the deployment, and preload it before application code. In Docker, copy both dist/ (including birthplace.json) and this module into the final runtime image; the build stage's environment variables alone are not preserved automatically.

import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { envDetector, hostDetector, processDetector } from '@opentelemetry/resources';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { birthplaceDetector } from 'birthplace/otel';

const sdk = new NodeSDK({
  resourceDetectors: [
    birthplaceDetector({ file: new URL('./dist/birthplace.json', import.meta.url) }),
    processDetector,
    hostDetector,
    envDetector,
  ],
  traceExporter: new OTLPTraceExporter(),
  instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start();

Run the application with the instrumentation module loaded first:

node --import ./instrumentation.mjs ./dist/app.js

Supplying resourceDetectors replaces the SDK's default detector list, so the example keeps the process, host, and environment detectors. envDetector runs last so deployment environment attributes can override build attributes deliberately. Detection requires autoDetectResources (true by default). With autoDetectResources: false, use toOtelAttributes with an explicitly created Resource instead.

The example assumes the compiled application uses CommonJS. For ESM dependencies, also configure the OTel ESM loader hook as required by your instrumentation. See the OpenTelemetry documentation for resources and instrumentation initialization.

birthplaceDetector({ file?, includeCustomAttributes?, includeHostAttributes? }) accepts a JSON path or file URL. Relative paths are resolved when the detector is created; the default is birthplace.json. The file is read and validated during detect(), not when the package is imported. The detector never invokes Git, reads package.json, regenerates metadata, or mutates process.env.

Direct detect() failures throw BirthplaceError. OpenTelemetry catches detector failures and skips the failed detector (diagnostic logging can expose the error). If metadata is required for application startup, explicitly call readBirthplace before starting the SDK. Do not rely on detector failures to terminate the application. With file, the detector reads JSON only; to use a generated ESM birthplace file, import it and pass the object as info, as shown next.

Import instead of read

Bundled and serverless deployments (a single-file bundle, a serverless function, Next.js output file tracing) ship only the files their tooling can trace from imports. A birthplace.json that is read through fs at runtime is easily left out, and because OpenTelemetry swallows detector failures, the missing file does not crash anything: the build attributes are silently dropped. An imported module is always part of the bundle, so importing is the robust choice there.

Generate an ESM birthplace file inside the source tree, before bundling:

birthplace generate --format esm --output src/birthplace.mjs

Import it and hand the object to the detector:

import { NodeSDK } from '@opentelemetry/sdk-node';
import { birthplaceDetector } from 'birthplace/otel';
import birthplace from './birthplace.mjs';

const sdk = new NodeSDK({
  resourceDetectors: [birthplaceDetector({ info: birthplace })],
});

sdk.start();

birthplaceDetector({ info, includeCustomAttributes?, includeHostAttributes? }) performs no file access. The object is validated during detect(), exactly like a file. Passing both file and info throws a BirthplaceError with code BIRTHPLACE_VALIDATION_ERROR when the detector is created, so that mistake is not swallowed by OpenTelemetry. The generated file is build output: add src/birthplace.mjs to .gitignore.

TypeScript projects need allowJs to import the file. It carries a /** @type {import('birthplace').Birthplace} */ annotation, so the import is typed as Birthplace and needs no cast. Without allowJs, put a declaration next to it instead (src/birthplace.d.mts):

declare const birthplace: import('birthplace').Birthplace;
export default birthplace;

birthplace/otel requires Node.js: it loads node:fs, node:path, node:url, and node:crypto when imported, even if only info is used. For a runtime or bundle without Node.js built-ins, import the mapping from birthplace/attributes, which loads no node: module, and build the resource yourself:

import { resourceFromAttributes } from '@opentelemetry/resources';
import { toOtelAttributes } from 'birthplace/attributes';
import birthplace from './birthplace.mjs';

const resource = resourceFromAttributes(toOtelAttributes(birthplace));

Build metadata

A JSON birthplace file has this shape; unavailable optional fields are omitted:

{
  "schemaVersion": 1,
  "service": { "name": "checkout", "version": "2.0.0" },
  "source": { "revision": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "dirty": false },
  "build": {
    "timestamp": "2026-09-21T01:02:03.000Z",
    "timestampSource": "clock",
    "url": "https://ci.example.test/builds/42"
  }
}

collectBirthplace(options) discovers values with these exact precedence rules:

  • Service name and version: explicit name / version, then cwd/package.json.
  • Source revision: explicit revision, then the checkout's Git HEAD, then the recognized provider's revision.
  • Dirty state: explicit dirty, then the checkout's Git status. It stays absent when Git cannot determine it; birthplace never assumes that a checkout is clean.
  • CI pipeline run URL: explicit buildUrl, then the recognized CI provider's run URL.
  • Timestamp: timestamp: false omits it; an explicit ISO timestamp wins over SOURCE_DATE_EPOCH; a valid SOURCE_DATE_EPOCH wins over the collection clock.
  • Build machine: recorded only with host: true; see Build machine.

Provider values are a fallback only, and exactly one provider is used. The first matching marker wins; values from different providers are never mixed:

| Order | Provider | Marker | Revision | Run URL | | ----- | -------------- | --------------------- | ----------------------- | ---------------------------------------------------------------------- | | 1 | GitHub Actions | GITHUB_ACTIONS=true | GITHUB_SHA | <GITHUB_SERVER_URL>/<GITHUB_REPOSITORY>/actions/runs/<GITHUB_RUN_ID> | | 2 | GitLab CI | GITLAB_CI=true | CI_COMMIT_SHA | CI_PIPELINE_URL | | 3 | Vercel | VERCEL=1 | VERCEL_GIT_COMMIT_SHA | https://<VERCEL_URL>/_logs |

On Vercel the checkout is often unavailable: vercel deploy source uploads do not include .git, and a monorepo Root Directory can hide it. The fallback needs Vercel's system environment variables, so enable Automatically expose System Environment Variables in the project's Environment Variables settings. VERCEL_GIT_COMMIT_SHA is only populated for deployments created from a connected Git repository; an empty value is treated as unknown, so --strict still fails instead of recording a guess. VERCEL_URL is a bare hostname without a scheme, and the run URL points at that deployment's build logs. A VERCEL_URL that is empty or not a plain hostname is ignored. The Vercel URL is stored in build.url and still maps to cicd.pipeline.run.url.full.

GitHub Actions and GitLab CI are checked before Vercel because a vercel build executed inside a CI job keeps VERCEL=1 while the CI provider is the machine that ran the build.

SOURCE_DATE_EPOCH is interpreted as UTC Unix seconds and produces timestampSource: "source-date-epoch". Explicit values use "explicit", and the current clock uses "clock". Invalid package manifests, revisions, URLs, timestamps, and epoch values throw a BirthplaceError. URLs must use HTTP or HTTPS and cannot contain credentials. Full SHA-1 and SHA-256 commit IDs are accepted.

Normal mode omits unavailable information. strict: true requires service name, service version, and source revision. Dirty state and timestamp are not strict-mode requirements.

import { collectBirthplace, readBirthplace, writeBirthplace } from 'birthplace';

const info = collectBirthplace({
  cwd: process.cwd(),
  buildUrl: 'https://ci.example.test/builds/42',
  strict: true,
});

writeBirthplace(info, { file: 'dist/birthplace.json' });
const packagedInfo = readBirthplace('dist/birthplace.json');

Collection only reads metadata. writeBirthplace creates parent directories and atomically replaces the target. readBirthplace parses and validates JSON without executing it.

Failures throw a BirthplaceError whose code is one of BIRTHPLACE_VALIDATION_ERROR, BIRTHPLACE_COLLECTION_ERROR, BIRTHPLACE_READ_ERROR, or BIRTHPLACE_WRITE_ERROR.

For a generated JavaScript module, choose ESM explicitly and import it directly. The module has a default export only:

npx birthplace generate --format esm --output dist/birthplace.mjs --strict
import birthplace from './dist/birthplace.mjs';
import { toOtelAttributes } from 'birthplace/otel';

const attributes = toOtelAttributes(birthplace);

readBirthplace is for JSON birthplace files; it intentionally does not execute generated ESM files.

Build machine

birthplace can also record the machine that ran the build. Capture is off by default: pass --host to birthplace generate, or host: true to collectBirthplace. The birthplace file then gains an optional top-level host group:

{
  "schemaVersion": 1,
  "service": { "name": "checkout", "version": "2.0.0" },
  "source": { "revision": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "dirty": false },
  "build": {},
  "host": {
    "arch": "arm64",
    "cpu": { "model": { "name": "Apple M2" }, "logical": { "count": 8 } },
    "memory": { "total": 17179869184 },
    "endianness": "little"
  }
}

Exporting the group as resource attributes is a separate opt-in: pass includeHostAttributes: true to birthplaceDetector or toOtelAttributes, or --include-host-attributes to birthplace inspect --otel. Each attribute key is birthplace. followed by the JSON path:

| JSON path | Resource attribute | Type and unit | Source | | ------------------------ | ----------------------------------- | ------------------------- | -------------------------------------- | | host.arch | birthplace.host.arch | string | os.arch(), mapped as described below | | host.cpu.model.name | birthplace.host.cpu.model.name | string | os.cpus()[0].model, trimmed | | host.cpu.logical.count | birthplace.host.cpu.logical.count | integer, logical CPUs | os.cpus().length | | host.memory.total | birthplace.host.memory.total | integer, bytes | os.totalmem() | | host.endianness | birthplace.host.endianness | string, little or big | os.endianness() (LE / BE) |

host.arch uses the OTel semantic conventions host.arch vocabulary rather than Node.js names: x64 becomes amd64, ia32 becomes x86, arm becomes arm32, ppc becomes ppc32, and arm64, ppc64, and s390x keep their names. An architecture without a semconv value, such as riscv64, is stored unchanged.

A fact the platform cannot report is omitted instead of guessed: when os.cpus() is empty, both CPU fields are absent. The logical count is os.cpus().length, the machine's logical CPUs, not os.availableParallelism(), which reflects the CPU affinity and container limits of the build process. birthplace reads hardware facts only. It never records the user name, home directory, shell, or host name.

These are birthplace-specific attributes, not OTel semantic conventions. They mirror semconv host.* naming so they are easy to recognize: host.arch and host.cpu.model.name exist in the host registry with Development stability as of semantic conventions 1.43.0, while host.cpu.logical.count, host.memory.total, and host.endianness have no semconv counterpart.

Build host versus runtime host

The birthplace. prefix is deliberate. OpenTelemetry's hostDetector and the semconv host.* attributes describe the machine the process is running on. birthplace.host.* describes the machine that built the artifact, which is usually a CI runner with a different architecture, CPU, and memory size. Writing build-machine values to unprefixed host.* keys would overwrite or contradict the runtime host, so birthplace never does that. Register both detectors to see both machines on the same resource:

resourceDetectors: [
  birthplaceDetector({ file, includeHostAttributes: true }), // birthplace.host.arch: amd64
  hostDetector, // host.arch: arm64
];

Machine facts make the birthplace file differ between two builds of the same commit on different runners, so capturing them makes the build output non-reproducible. That is why capture is opt-in and why reproducible builds should leave --host off, just as they pin SOURCE_DATE_EPOCH or pass --no-timestamp.

OpenTelemetry attributes

toOtelAttributes(info, options?) validates the input and returns only defined values. The dedicated birthplace/otel entrypoint includes birthplace file reading and conversion, without loading the Git collector. No OpenTelemetry SDK is installed as a runtime dependency.

| Entry point | Exports | Needs Node.js built-ins | | ----------------------- | ---------------------------------------------------- | ----------------------- | | birthplace | Collection, reading, writing, detector, and mapping | Yes | | birthplace/otel | birthplaceDetector, toOtelAttributes | Yes | | birthplace/attributes | toOtelAttributes, BirthplaceError, and the types | No |

| Build metadata | Resource attribute | | ----------------- | ---------------------------- | | service.name | service.name | | service.version | service.version | | source.revision | vcs.ref.head.revision | | build.url | cicd.pipeline.run.url.full |

The mapping was checked against OTel semantic conventions 1.43.0, the version of @opentelemetry/semantic-conventions in this repository's development tree. service.name and service.version are Stable. vcs.ref.head.revision and cicd.pipeline.run.url.full in the VCS and CI/CD registries have been Release Candidate since semantic conventions 1.43.0; they are not Stable yet. birthplace writes these names as literals and does not import the semantic-conventions package, so upgrading the SDK does not silently rename them.

Custom attributes are off by default. Pass includeCustomAttributes: true to the detector or converter to additionally emit birthplace.source.dirty, birthplace.build.timestamp, and birthplace.build.timestamp_source. These are birthplace-specific attributes, not OTel semantic conventions. The birthplace file always retains those fields whether or not you export them as attributes.

includeHostAttributes: true is an independent switch for the birthplace.host.* attributes described in Build machine. Attribute values are strings, booleans, or numbers.

CLI

Generate a JSON birthplace file:

npx birthplace generate \
  --cwd . \
  --output dist/birthplace.json \
  --service-name checkout \
  --service-version 2.0.0 \
  --revision bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb \
  --build-url https://ci.example.test/builds/42 \
  --strict

generate accepts --format json|esm, --timestamp <ISO timestamp>, --no-timestamp, and --host (record the build machine) in addition to the options above. Without --output, JSON writes birthplace.json and ESM writes birthplace.mjs in the current directory.

Bare birthplace is equivalent to birthplace generate.

Inspect validated JSON or its OpenTelemetry mapping:

npx birthplace inspect dist/birthplace.json
npx birthplace inspect dist/birthplace.json --otel
npx birthplace inspect dist/birthplace.json --otel --include-custom-attributes
npx birthplace inspect dist/birthplace.json --otel --include-host-attributes

Malformed data, invalid options, missing strict fields, and filesystem failures print a concise error to stderr and exit nonzero.

Development

mise install
mise run setup
pnpm install --frozen-lockfile
pnpm check
pnpm test
pnpm build
pnpm test:packaging

Tests include temporary Git repositories, built CLI subprocesses, actual OTel resource detection, and a real NodeSDK exporting spans from the packaged birthplace file and from an imported birthplace object. The package smoke test installs the tarball into an isolated consumer and verifies CommonJS/ESM imports and declarations, and that birthplace/attributes loads no Node.js built-in.

Node.js 22 or later is required. CJS and ESM have separate entry points and matching declarations; use birthplace, birthplace/otel, and birthplace/attributes rather than depending on generated filenames under dist/. See CONTRIBUTING.md for the issue, PR, and Changesets workflow.