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

@jdtzmn/port

v0.1.3

Published

CLI tool for managing git worktrees with automatic Traefik configuration

Readme

Port

Run 2+ Docker compose worktrees on the same service ports at the same time without conflicts.

Features

  • Git Worktree Management: Create and manage git worktrees with a single command
  • Automatic Traefik Configuration: Dynamically configure Traefik reverse proxy for local domain access
  • Port Conflict Resolution: Run multiple worktrees simultaneously without port conflicts
  • Host Process Support: Run non-Docker processes (like npm serve) with Traefik routing
  • DNS Setup: Automated DNS configuration for *.port domains
  • Service Discovery: Easy access to services via hostnames instead of port numbers

Installation

# Port is published on npm, but requires Bun at runtime
npm install -g @jdtzmn/port
# or install globally with Bun
bun add -g @jdtzmn/port

port executes with a Bun shebang (#!/usr/bin/env bun), so Bun must be installed and available on PATH even when the package is installed via npm.

Quick Start

Want a guided workflow in the CLI?

port onboard

1. Initialize Project

port init

This sets up the .port/ directory structure and checks DNS configuration.

2. Configure Project

Create .port/config.jsonc in your project:

{
  // Optional, defaults to "port"
  "domain": "port",

  // Optional, defaults to "docker-compose.yml"
  "compose": "docker-compose.yml",
}

3. Set Up DNS (One-time)

port install

Configures your system to resolve your configured wildcard domain (default *.port) to 127.0.0.1.

On macOS, port install runs privileged steps through a centralized elevation helper: it uses the native admin credential dialog when a GUI session is available and falls back to terminal sudo in headless/non-GUI environments.

You can optionally specify a custom IP address:

# Resolve to a specific IP (useful for Docker networks, etc.)
port install --dns-ip 172.25.0.2

# Skip confirmation prompt
port install --yes

# Combine options
port install --yes --dns-ip 192.168.1.100

# Explicit custom domain
port install --domain custom

Linux DNS Setup

On Linux systems with systemd-resolved running (most modern Ubuntu/Debian systems), the install command automatically:

  1. Detects that systemd-resolved is using port 53
  2. Runs dnsmasq on port 5354 to avoid conflicts
  3. Configures systemd-resolved to forward your wildcard domain queries to dnsmasq

This "dual-mode" setup allows both services to coexist without conflicts.

4. Shell Integration (Recommended)

Add this to your shell profile so port enter and port exit can change your working directory:

# ~/.bashrc
eval "$(port shell-hook bash)"

# ~/.zshrc
eval "$(port shell-hook zsh)"

# ~/.config/fish/config.fish
port shell-hook fish | source

Without shell integration, port enter and port exit will print a cd command for you to run manually.

5. Enter a Worktree

port feature-1
port enter feature-1

This creates a new worktree and changes into it (with shell integration) or prints the path to cd into. Use port enter <branch> when your branch name collides with a command (for example status or install). If a branch and command collide, running port <command> shows a hint to use port enter <branch>.

6. Exit a Worktree

port exit

Returns to the repository root and clears the PORT_WORKTREE environment variable.

7. Start Services

port up

Starts docker-compose services and makes them available at feature-1.port:PORT.

8. Stop Services

port down

Stops services and optionally shuts down Traefik if no other projects are running.

9. Run Host Processes (Non-Docker)

port run 3000 -- npm run dev

Runs a host process (not in Docker) and routes traffic through Traefik. The command receives the PORT environment variable set to an ephemeral port, while users access it via <branch>.port:3000.

This is useful for:

  • Development servers that don't run in Docker
  • Quick testing without containerization
  • Running multiple instances of the same service on different worktrees

10. List Active Worktrees

port list

Shows a concise worktree-level running/stopped summary and any running host services.

For per-service details by worktree:

port status

Show URLs for services in the current worktree:

port urls
port urls ui-frontend

port urls works in either a worktree or the main repository.

11. Remove a Worktree

port remove feature-1
# Skip confirmation for non-standard/stale worktree entries
port rm -f feature-1
# Keep the local branch name unchanged
port rm --keep-branch feature-1

Stops services, removes the worktree, and soft-deletes the local branch by archiving it under archive/<name>-<timestamp>. Use --keep-branch to preserve the local branch name.

12. Clean Up Archived Branches

port cleanup

Shows archived branches created by port remove and asks for confirmation before deleting all of them.

Commands

| Command | Description | | ------------------------------------------------ | ----------------------------------------------------- | | port init | Initialize .port/ directory structure | | port onboard | Print recommended workflow and command usage guide | | port install [--dns-ip IP] [--domain DOMAIN] | Set up DNS for wildcard domain (default from config) | | port shell-hook <bash\|zsh\|fish> | Print shell integration code for automatic cd | | port enter <branch> | Enter a worktree explicitly (including command names) | | port <branch> | Enter a worktree (creates if doesn't exist) | | port exit | Exit the current worktree and return to repo root | | port up | Start docker-compose services in current worktree | | port down | Stop docker-compose services and host processes | | port run <port> -- <command...> | Run a host process with Traefik routing | | port kill [port] | Stop host services (optionally by logical port) | | port remove <branch> [--force] [--keep-branch] | Remove worktree and archive local branch | | port compose <args...> | Run docker compose with auto -f flags | | port list | List worktree and host-service summary | | port status | Show per-service status by worktree | | port urls [service] | Show service URLs for current worktree | | port cleanup | Delete archived local branches with confirmation | | port uninstall [--yes] [--domain DOMAIN] | Remove DNS configuration for wildcard domain |

How It Works

Architecture

┌─────────────────────────────────┐
│ CLI Tool: port                  │
│ (installed globally)            │
└─────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────┐
│ Your Project: ~/projects/my-app │
│ ├── .port/                      │
│ │   ├── config.jsonc            │
│ │   └── trees/                  │
│ │       ├── feature-1/          │
│ │       └── feature-2/          │
│ └── docker-compose.yml          │
└─────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────┐
│ Traefik (global)                │
│ ~/.port/traefik/                │
│ - Routes by hostname            │
│ - Manages all services          │
└─────────────────────────────────┘

Port Conflict Resolution

Multiple worktrees can run simultaneously because:

  1. Host port bindings are disabled in worktree overrides
  2. Services only listen on internal container ports
  3. Traefik routes by Host header, not port number

Example:

# feature-1 worktree
port feature-1
port up
# Available at: feature-1.port:3000

# In another terminal, feature-2 worktree (same ports!)
port feature-2
port up
# Available at: feature-2.port:3000

# No conflicts! Traefik routes both to the same internal port on different containers

Host Process Routing

The port run command enables running non-Docker processes with Traefik routing:

# In .port/trees/feature-1 directory
port run 3000 -- npm run dev
# Service available at http://feature-1.port:3000

# In another terminal, .port/trees/feature-2 directory
port run 3000 -- npm run dev
# Service available at http://feature-2.port:3000

# No port conflicts! Both run simultaneously.

How it works:

  1. Allocates a unique ephemeral port (e.g., 49152)
  2. Sets PORT=49152 environment variable for the command
  3. Registers with Traefik: feature-1.port:3000localhost:49152
  4. Cleans up when the process exits (Ctrl+C, crash, etc.)

Most frameworks (Express, Next.js, Vite, etc.) respect the PORT environment variable automatically.

Project Structure

port/
├── package.json
├── tsconfig.json
├── eslint.config.ts
├── prettier.config.js
├── src/
│   ├── index.ts                 # Entry point
│   ├── commands/
│   │   ├── init.ts
│   │   ├── install.ts
│   │   ├── enter.ts
│   │   ├── exit.ts
│   │   ├── shell-hook.ts        # Shell integration (bash/zsh/fish)
│   │   ├── up.ts
│   │   ├── down.ts
│   │   ├── run.ts               # Host process runner
│   │   ├── remove.ts
│   │   ├── list.ts
│   │   └── status.ts
│   ├── lib/
│   │   ├── config.ts
│   │   ├── git.ts
│   │   ├── compose.ts
│   │   ├── shell.ts             # Shell command generation
│   │   ├── traefik.ts
│   │   ├── registry.ts
│   │   ├── hostService.ts       # Host service management
│   │   ├── dns.ts
│   │   ├── sanitize.ts
│   │   └── worktree.ts
│   └── types.ts
├── traefik/
│   └── docker-compose.yml
└── README.md

Requirements

  • Bun 1.0+ (required runtime for the port CLI)
  • Git 2.7+
  • Docker & Docker Compose v2.24.0+
  • macOS or Linux

Configuration

See PLAN.md for detailed configuration options and examples.

Development

# Install dependencies
bun install

# Run in development
bun run dev init

# Build
bun run build

# Type check
bun run typecheck

# Format code
bun run format

# Lint
bun run lint

# Test
bun run test

Testing in Ubuntu Container

The project includes a Docker container running Ubuntu 24.04 with systemd for testing the CLI in a Linux environment. This is useful for testing DNS configuration and other Linux-specific features.

# Start the container and open a bash shell
make ubuntu

# Stop the container
make down

Once inside the container, you can test the CLI:

# Set up DNS for *.port domains
port install --yes

# Test DNS resolution
dig test.port

Note: The container overrides /etc/resolv.conf to use systemd-resolved for DNS, which allows *.port domain resolution to work. However, this means the container does not have access to the outside network (e.g., apt-get update or curl to external URLs will fail).

Compose Overrides Reference

Port isolates worktrees in layered compose files:

  1. Your base compose file (docker-compose.yml by default)
  2. A generated Port override (.port/override.yml)
  3. An optional rendered user override (.port/override.user.yml)

Port runs compose with user overrides last so local customization wins:

docker compose -p <project-name> -f docker-compose.yml -f .port/override.yml -f .port/override.user.yml up -d

.port/override.user.yml is generated at runtime from .port/override-compose.yml if that file exists.

Here are all Port-managed overrides/compose controls and why they exist:

| Port-managed change | Why it is necessary | | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | -p <project-name> (compose flag) | Namespaces compose resources per repo/worktree so similarly named stacks do not collide. | | -f .port/override.yml (compose flag) | Applies Port's deterministic runtime adjustments without mutating your source compose file. | | -f .port/override.user.yml (compose flag, optional) | Applies user-provided overrides rendered from .port/override-compose.yml, after Port defaults, so user rules win. | | services.<name>.ports: !override [] (for services with published ports) | Removes host port binds so two worktrees can both run services that declare the same host ports. | | services.<name>.labels: [...] | Adds Traefik router/service metadata so requests route by hostname (<branch>.port) instead of host port ownership. | | services.<name>.networks: [traefik-network] | Ensures Traefik can reach exposed services on the shared network. | | services.<name>.container_name rewrite (only when upstream sets one) | Prevents global Docker container name conflicts when upstream hard-codes a fixed container_name. | | networks.traefik-network.external: true | Connects project services to the globally managed Traefik network instead of creating per-project duplicates. |

Notes:

  • For services without published ports, Port does not inject Traefik labels/ports/network wiring.
  • Port intentionally does not override image, build, environment, volumes, depends_on, or command.
  • .port/override-compose.yml is optional and user-editable; if missing, Port skips the user layer.
  • Supported user override variables: PORT_ROOT_PATH, PORT_WORKTREE_PATH, PORT_BRANCH, PORT_DOMAIN, PORT_PROJECT_NAME, PORT_COMPOSE_FILE.

Example generated shape:

services:
  web:
    container_name: my-repo-feature-1-web
    ports: !override []
    networks:
      - traefik-network
    labels:
      - traefik.enable=true
      - traefik.http.routers.feature-1-web-3000.rule=Host(`feature-1.port`)
      - traefik.http.routers.feature-1-web-3000.entrypoints=port3000
      - traefik.http.routers.feature-1-web-3000.service=feature-1-web-3000
      - traefik.http.services.feature-1-web-3000.loadbalancer.server.port=3000

networks:
  traefik-network:
    external: true
    name: traefik-network

License

MIT