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

dockman

v0.4.1

Published

Build, tag, and push images with Docker or Podman from Bun and Node.

Readme

dockman

Build, tag, and push container images from Bun or Node with Docker or Podman, and deploy with kubectl. Install with bun add dockman. Requires Node 22+ or Bun 1.3+ and an engine CLI on PATH.

import { DockmanConnection } from 'dockman'

const connection = await DockmanConnection.create({
    preference: ['docker', 'podman'],
    startIfNeeded: true,
    interactive: true,
})

const image = await connection.build({
    context: '.',
    file: 'Dockerfile',
    buildArgs: { NODE_ENV: 'production' },
})
await connection.tag(image, 'ghcr.io/my-org/my-app:latest')
await connection.push('ghcr.io/my-org/my-app:latest')

Connecting

DockmanConnection.create(options?) checks engines in preference order, defaulting to Docker then Podman. Use ['podman'] or ['docker'] to restrict discovery. An already-running engine is used before attempting to start any engine. The readonly connection.engine identifies the selected engine; operations never fail over to a different image store. The CLI's existing environment and context/connection settings apply.

startIfNeeded defaults to true. Automatic startup is Windows-only: Dockman starts Docker Desktop or Podman's existing default machine and polls for readiness. startupTimeoutMs defaults to 120,000 per engine; individual probes have a five-second limit. Set startIfNeeded: false to require an already-running engine. On macOS/Linux, start engines yourself. Dockman does not install engines, initialize machines, elevate privileges, change connection defaults, or stop engines after use.

Docker launches use hidden windows as a best effort. Docker controls its dashboard; if it still opens, disable Open Docker Dashboard when Docker Desktop starts in Docker's settings. Dockman does not change saved preferences.

Images

  • build(options?) streams progress and returns a sha256: image ID. Options: context (default .), file, tags, buildArgs, labels, target, platform (one platform), and noCache. An automatically cleaned temporary iidfile avoids parsing progress output. Build arguments are not a safe channel for secrets.
  • tag(image, target) assigns one local name. Repeat it for additional tags.
  • push(reference) streams progress and uploads an explicitly tagged reference. Any registry can use existing engine credentials; automatic login supports GHCR only.

Kubernetes deployments

KubernetesConnection uses kubectl on PATH and existing kubeconfig credentials independently of Docker or Podman. Its constructor accepts context and namespace; omitted values use kubectl's current settings. It does not change kubeconfig or contact the cluster until a method runs.

import { KubernetesConnection, kustomizeBuild, kustomizeSetImage } from 'dockman'

const cluster = new KubernetesConnection({ context: 'production', namespace: 'my-app' })
await kustomizeSetImage('./k8s', 'ghcr.io/example/my-app:v2')
const yaml = await kustomizeBuild('./k8s')
const diff = await cluster.diff(yaml)
console.log(diff)

await cluster.apply(yaml, {
    dryRun: 'server', // Use 'none' to persist changes after your application's confirmation.
    serverSide: true,
    fieldManager: 'kustomize',
    applySet: 'my-app-deploy',
})

// After applying with dryRun: 'none':
// await cluster.rolloutRestart('deploy/deployment')
// await cluster.rolloutStatus('deploy/deployment', { timeout: '10m' })
  • diff(manifests, options?) returns the complete diff text and accepts kubectl exit codes 0 (unchanged) and 1 (differences). Other failures reject. Options include serverSide and fieldManager so previews can match apply semantics. kubectl requires a diff executable on PATH or KUBECTL_EXTERNAL_DIFF configured.
  • get(resources, options?) returns parsed JSON for a named type/name or a nonempty array of type/name selections. One selection returns a resource object; multiple selections return kubectl's List. The result is typed unknown; validate its shape in your application. Missing names reject by default; a missing single selection produces an error identifying the requested type/name. ignoreNotFound: true omits missing entries, and returns undefined for empty output (including when all names are missing). Any JSON List, including an empty one, is returned unchanged. Authorization, connection, and other failures still reject. Output is captured in full and always treated as sensitive: neither stdout nor stderr is streamed, and errors omit subprocess diagnostics and JSON excerpts.
  • getSecret(name, options?) retrieves one Secret by name (without secret/) and returns its data as Record<string, Buffer>, automatically decoding each base64 value and preserving binary data exactly. Use value.toString('utf8') for text. It retains the same context, namespace, and output protection as get(). Metadata is omitted; a Secret without data returns {}. Missing Secrets reject by default, or return undefined with ignoreNotFound: true. Invalid Secret data and malformed base64 reject with redacted errors.
  • getConfigMap(name, options?) returns Record<string, string | Buffer> without metadata. It preserves data strings and combines them with base64-decoded binaryData Buffers. Empty ConfigMaps return {}; missing ConfigMaps reject unless ignoreNotFound: true, which returns undefined. Malformed fields, invalid base64, and keys shared by both fields reject with redacted errors. Context, namespace, and output protection match getSecret(). Resource types have separate names: getSecret('kube-root-ca.crt') reports a missing Secret even if that name exists as a ConfigMap. Use getConfigMap('kube-root-ca.crt') to retrieve it.
  • apply(manifests, options?) streams output by default. dryRun accepts 'none' (default), 'client', or 'server'. serverSide and fieldManager control field ownership. Setting applySet enables --prune --applyset=..., removing previously managed resources absent from the manifests. It sets KUBECTL_APPLYSET=true only for that subprocess. Set sensitive: true for Secrets: stdout and stderr are suppressed, the method returns no output, and failures contain a fixed message without subprocess diagnostics, original errors, or causes. This works with every dry-run mode, server-side apply, and ApplySet pruning.
  • rolloutRestart(resource) restarts a resource specified as type/name.
  • rolloutStatus(resource, { timeout? }) streams progress until the latest rollout finishes, rejecting on failure or timeout. Durations use kubectl syntax, such as '10m'; the default waits indefinitely. A newer rollout is followed if one starts while watching.

Manifests accept strings, Uint8Array (including Node buffers), or ArrayBuffer and are piped to stdin. Diff formatting and confirmation prompts remain in your script. See the kubectl reference for command behavior and prerequisites.

Secrets, ConfigMaps, and existence checks

Both resource types store flat key/value data. A Secret's data values represent arbitrary bytes encoded as base64 in JSON; stringData is a write-only convenience merged into data by Kubernetes. A ConfigMap has UTF-8 strings in data and optional base64 values in binaryData, with no overlapping keys. A value may contain an entire JSON, YAML, or other file, but Kubernetes still treats it as one string or byte sequence. These helpers return the data maps; use get() for resource metadata or the original field structure. See the Secret API and ConfigMap documentation.

const cluster = new KubernetesConnection({ context: 'production', namespace: 'my-app' })

// A single lookup makes an existence check explicit. Other failures still throw.
const namespaceExists =
    (await cluster.get('namespace/my-app', { ignoreNotFound: true })) !== undefined
const deploymentExists =
    (await cluster.get('deployment/web', { ignoreNotFound: true })) !== undefined

// Each name is explicit; this returns kubectl's JSON List without printing credentials.
const credentials = await cluster.get(['secret/app', 'secret/registry'])
// Validate credentials before persisting them to a protected local file. Do not log the result.

// For one Secret, get decoded Buffers directly without resource metadata.
const appCredentials = await cluster.getSecret('app')
const password = appCredentials.DB_PASSWORD?.toString('utf8')
// Buffers preserve every byte, including binary data and empty values. Do not log them.

// ConfigMaps preserve text as strings and decode binaryData into Buffers.
const appConfig = await cluster.getConfigMap('app')
const hostname = appConfig.DB_HOST
if (typeof hostname !== 'string') throw new Error('DB_HOST must be text')

// secretManifests is YAML or JSON read or constructed by your application.
await cluster.apply(secretManifests, {
    sensitive: true,
    serverSide: true,
    fieldManager: 'my-app-deploy',
    dryRun: 'server', // Use 'none' after review to persist changes.
    applySet: 'my-app-secrets',
})

All operations retain the connection's context and namespace, including resource retrieval. The namespace flag does not change the scope of cluster-scoped resources such as Namespaces. Selections are passed as separate arguments after --; resource names cannot inject kubectl flags. See kubectl get for selection and missing-resource semantics.

Sensitive mode protects Dockman's output and thrown errors; returned resources still contain the original Secret data and must be handled privately by the caller. It does not alter manifests or cluster-side logging. Current kubectl diff masks Secret values in normal diff output by default (--show-secrets disables masking). Dockman's diff() still includes raw command diagnostics on failure, so it does not provide the same output protection as sensitive apply. kustomizeBuild() also returns manifest contents and includes CLI diagnostics on failure; sensitive applies only to the apply() call, not to earlier rendering. Use a separate ApplySet for Secrets if application deployments must not prune them.

Kustomize utilities

These standalone utilities require kustomize on PATH and work without a cluster or container engine connection. Both require an explicit local directory containing the kustomization. Relative paths resolve from the current working directory; neither utility changes it.

  • kustomizeBuild(directory) returns the complete UTF-8 YAML string, ready for diff or apply.
  • kustomizeSetImage(directory, image) persists an image change to the local kustomization file. It accepts native image expressions such as ghcr.io/example/my-app:v2, my-app=ghcr.io/example/my-app:v2, and digest references.

Both reject on command failures with CLI diagnostics. They use the standalone executable without falling back to kubectl's bundled Kustomize.

GHCR login

On recognizable GHCR authentication failures, interactive: true (the default) allows GitHub CLI browser authentication in a terminal. Install gh beforehand. Dockman reuses the active account, requests write:packages when needed, logs the selected engine into ghcr.io using secret stdin, and retries the push once. GitHub CLI and the engine manage credential persistence through their usual stores. Generic permission denials, missing images, and network errors do not trigger login.

With interactive: false or no terminal, authentication failures include manual instructions. For example:

gh auth login --hostname github.com --web --scopes write:packages
# For an existing account that needs the package scope:
gh auth refresh --hostname github.com --scopes write:packages
gh auth token --hostname github.com | docker login ghcr.io --username YOUR_GITHUB_USERNAME --password-stdin

Replace docker with podman when appropriate. Verify package write permissions and organization SSO authorization. Environment-provided GH_TOKEN/GITHUB_TOKEN values take precedence in GitHub CLI; replace or unset insufficient tokens rather than attempting browser refresh. Configure registry credentials ahead of time in CI.

See GitHub CLI login and GHCR authentication.