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

branch-localhost

v0.1.0

Published

Per-branch <branch>.localhost:<port> for local dev — deterministic port per git branch, free-port probing, env injection.

Downloads

194

Readme

branch-localhost

CI npm version npm downloads license

Run your local dev server at a stable, per-git-branch URL like my-feature.localhost:4123. Deterministic port per branch, free-port probing, env injection — one command, no config files, no global state.

branch-localhost --base-port 4000 -- next dev
# - Branch URL:    http://my-feature.localhost:4123
# (next dev runs with PORT=4123 and DEV_HOST_URL/DEV_HOST_HOST set)

Why?

  • Stable URL per branch. Same branch → same port, every time. No more "which tab was the auth feature on?".
  • Isolated cookies & storage. *.localhost is a separate origin in modern browsers — branches don't stomp each other's session.
  • No collisions across branches/apps. Different branches in the same app get different ports (hash of branch). Different apps get disjoint ranges (each picks its own --base-port).
  • Free-port aware. If the seeded port is busy, probes the next ones in the range.
  • Works great with git worktrees. Each worktree is on its own branch, so each gets its own deterministic URL — run several dev servers in parallel with zero coordination. See Git worktree workflow.

Installation

npm install -D branch-localhost
# or pnpm add -D branch-localhost / yarn add -D branch-localhost

Requires Node 18+ and a git working tree (uses git rev-parse --abbrev-ref HEAD).

Usage

branch-localhost [options] [-- <command> [args...]]

The bit after -- is the command to spawn. It inherits stdio and receives the computed env vars. Exit code and signals are forwarded.

CLI

| Option | Type | Required | Default | Description | | ------ | ---- | -------- | ------- | ----------- | | --base-port <n> | number | no | 3000 | First port of the range | | --range <n> | number | no | 1000 | Range size (ports [base, base+range)) | | --probe-limit <n> | number | no | 50 | Max attempts to find a free port | | --env-url <NAME> | string | no | — | Set NAME=http://<host>:<port>. Repeatable. | | --env-url-slash <NAME> | string | no | — | Set NAME=http://<host>:<port>/. Repeatable. | | --env-port <NAME> | string | no | — | Set NAME=<port>. Repeatable. | | --env-host <NAME> | string | no | — | Set NAME=<host>. Repeatable. | | --show | boolean | no | false | Print URL to stdout and exit. Skips port probing. Implies --quiet. | | --quiet | boolean | no | false | Suppress the "Branch URL" stderr line. | | -h, --help | boolean | no | — | Show help | | -v, --version | boolean | no | — | Show version |

Always-set env vars (wrapper mode): DEV_HOST_URL, DEV_HOST_PORT, DEV_HOST_HOST.

Examples

Next.js — Next reads PORT, so just point it there:

{
  "scripts": {
    "dev": "branch-localhost --base-port 4000 --env-port PORT -- next dev"
  }
}

Next.js with public site URL injection:

{
  "scripts": {
    "dev": "branch-localhost --base-port 4000 --env-port PORT --env-url-slash NEXT_PUBLIC_SITE_URL --env-url BASE_URL -- next dev"
  }
}

Vite — Vite reads PORT too, or pass via --port:

branch-localhost --base-port 5173 --env-port PORT -- vite

Monorepo with two apps on disjoint ranges:

// apps/web/package.json
{ "scripts": { "dev": "branch-localhost --base-port 4000 --env-port PORT -- next dev" } }

// apps/admin/package.json
{ "scripts": { "dev": "branch-localhost --base-port 5000 --env-port PORT -- next dev" } }

Print the URL without running anything:

branch-localhost --base-port 4000 --show
# → http://my-feature.localhost:4123

open "$(branch-localhost --base-port 4000 --show)"   # open it in the browser

Git worktree workflow

This is where the tool shines. With git worktrees, each checkout lives in its own directory on its own branch. Spin up a few in parallel and each gets a unique, stable URL — no port collisions, no "which terminal had which feature?".

# Layout:
#   ~/proj/main         (master)
#   ~/proj/auth         (worktree, branch: feat/auth)
#   ~/proj/checkout     (worktree, branch: feat/checkout)

cd ~/proj/auth      && pnpm dev   # → http://feat-auth.localhost:4291
cd ~/proj/checkout  && pnpm dev   # → http://feat-checkout.localhost:4807
cd ~/proj/main      && pnpm dev   # → http://master.localhost:4145

All three run side-by-side. Cookies and localStorage are isolated per subdomain. The port is the same every time you start that worktree.

To grab a URL from a script (e.g., to open in browser or paste in Slack):

# from inside any worktree:
branch-localhost --base-port 4000 --show

To open every running worktree at once (Bash):

for wt in $(git worktree list --porcelain | awk '/^worktree /{print $2}'); do
  open "$(cd "$wt" && branch-localhost --base-port 4000 --show)"
done

Detached HEAD (e.g. git checkout <sha>) falls back to sha-<short> as the branch label, so each checked-out commit still gets a stable URL.

How it works

  1. Read current git branch via git rev-parse --abbrev-ref HEAD. If detached (returns HEAD), fall back to sha-<short>.
  2. Sanitize → lowercase [a-z0-9-], trim dashes, cap at 63 chars (DNS label limit). Host becomes <sanitized>.localhost, or just localhost if the branch sanitizes to empty.
  3. Hash the sanitized branch (deterministic) → pick a port inside [base-port, base-port + range).
  4. If that port is busy, probe successive ports (wrapping inside the range) up to --probe-limit times. (--show skips this — it returns the deterministic seed.)
  5. Spawn <command> with stdio inherited and the chosen env vars set.

FAQ

Does *.localhost actually resolve? Yes — Chrome, Firefox, Safari, and Edge all resolve *.localhost to 127.0.0.1 by default (per RFC 6761). Most modern OS resolvers (macOS, Linux with systemd-resolved) also do this. If your environment doesn't, add an /etc/hosts entry or pick another suffix and patch this script.

What about Windows? The wrapper uses shell: true on Windows to resolve .cmd shims. Should work but is less battle-tested than Mac/Linux.

Does the port survive a branch rename? No — different branch name → different hash → different port. Same branch name is deterministic across machines.

License

MIT