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

@radleta/just-one

v1.5.0

Published

A CLI tool that ensures only one instance of a command runs at a time

Readme

just-one

npm version CI license node

A CLI tool that ensures only one instance of a command runs at a time. Tracks processes by name using PID files, kills the previous instance before starting a new one, and verifies process identity to prevent PID reuse accidents.

Quick start:

npx @radleta/just-one -n dev -- npm run dev

The -- separator tells just-one where its own options end and your command begins.

Table of Contents

Motivation

When developing with dev servers (Storybook, Vite, webpack-dev-server, etc.), you often get:

Error: Port 6006 is already in use

Existing solutions have drawbacks:

  • kill-port — Kills ANY process on that port (imprecise, might kill unrelated processes)
  • Manual — Find PID, kill it, restart (tedious)
  • pm2 — Overkill for dev servers

just-one tracks processes by name using PID files. When you run a command, it kills the previous instance (if any) and starts fresh — precisely targeting only the process it started.

Features

  • Named process tracking — Each process gets a unique name for precise targeting
  • Automatic cleanup — Previous instance killed before starting new one
  • PID reuse protection — Verifies process identity before killing to prevent accidents
  • Log capture — stdout/stderr captured to log file in both foreground and daemon modes
  • Daemon mode — Run processes in the background (detached)
  • Log viewing — View captured logs, follow in real-time (like tail -f)
  • Log rotation — Automatic rotation at 10MB (keeps 1 backup)
  • Cross-platform — Works on Windows, macOS, and Linux
  • Minimal dependencies — Only pidusage for process verification, plus the optional koffi for Windows daemon handle isolation

Installation

npm install -g @radleta/just-one

Or use with npx (no install required):

npx @radleta/just-one -n myapp -- npm run dev

Usage

Basic Usage

# Run storybook, killing any previous instance named "storybook"
just-one -n storybook -- npx storybook dev -p 6006

# Run vite dev server
just-one -n vite -- npm run dev

# Run any command
just-one -n myapp -- node server.js

Ensure a Process Is Running (Idempotent)

# Only starts if not already running — safe to call repeatedly
just-one -n vite -e -- npm run dev

Log Capture

Output is automatically captured to .just-one/<name>.log in both foreground and daemon modes:

# Foreground — output appears in terminal AND is saved to log file
just-one -n myapp -- npm start

# Disable log capture if you don't need it
just-one -n myapp --no-log -- npm start

Daemon Mode (Background)

# Run in background — parent exits immediately, output captured to log file
just-one -n myapp -D -- npm start

On Windows, the daemon is started without inheriting any of just-one's handles, so piping a command that starts one (... | tail, a CI step, an agent reading the output) finishes when just-one exits rather than when the daemon stops. This uses the optional dependency koffi, which npm installs by default. If it is missing (for example, installed with --omit=optional) or the launch fails, the daemon still starts, and just-one prints a Warning: daemon started without handle isolation line on stderr naming the cause.

Viewing Logs

# View all captured logs (works for both foreground and daemon processes)
just-one -L myapp

# View last 50 lines
just-one -L myapp --lines 50

# Follow logs in real-time (like tail -f) — auto-exits when process dies
just-one -L myapp -f

# Show last 20 lines then follow
just-one -L myapp -f --lines 20

Check If a Process Is Running

just-one -s storybook        # exit 0 if running, exit 1 if stopped
just-one --status myapp

Get the PID for Scripting

pid=$(just-one -p myapp -q)  # prints just the PID number

Kill a Named Process

just-one -k storybook
just-one --kill myapp

Kill All Tracked Processes

just-one -K
just-one --kill-all

Wait for a Process to Exit

just-one -w myapp             # wait indefinitely
just-one -w myapp -t 30       # wait up to 30 seconds

List Tracked Processes

just-one -l
just-one --list

Clean Up Stale PID Files and Logs

just-one --clean              # removes stale PID files, their log files, and abandoned temp files

Specify Custom PID Directory

# Default: ./.just-one/<name>.pid
just-one -n storybook -- npx storybook dev

# Custom directory
just-one -n storybook -d /tmp -- npx storybook dev

CLI Options

| Option | Alias | Description | | ------------------ | ----- | ------------------------------------------------------------------- | | --name <name> | -n | Required for run. Name to identify this process | | --daemon | -D | Run in background (detached) | | --no-log | | Disable log file capture in foreground mode | | --logs <name> | -L | View captured logs for a named process | | --tail | -f | Follow log output in real-time (use with --logs) | | --lines <n> | | Number of lines to show (use with --logs) | | --kill <name> | -k | Kill the named process and exit | | --kill-all | -K | Kill all tracked processes | | --status <name> | -s | Check if a named process is running (exit 0/1) | | --ensure | -e | Only start if not already running (use with -n) | | --pid <name> | -p | Print the PID of a named process | | --wait <name> | -w | Wait for a named process to exit | | --timeout <secs> | -t | Timeout in seconds (use with --wait) | | --grace <secs> | -g | Grace period before force kill (default: 5s) | | --clean | | Remove stale PID files, orphaned log files and abandoned temp files | | --list | -l | List all tracked processes and their status | | --pid-dir <dir> | -d | Directory for PID files (default: .just-one/) | | --quiet | -q | Suppress output | | --help | -h | Show help | | --version | -v | Show version |

package.json Scripts

{
  "scripts": {
    "storybook": "just-one -n storybook -- storybook dev -p 6006",
    "dev": "just-one -n vite -e -- vite",
    "dev:api": "just-one -n api -D -- node server.js",
    "dev:logs": "just-one -L api -f",
    "dev:quick": "just-one -n quick --no-log -- node scripts/check.js",
    "stop": "just-one -K"
  }
}

How It Works

.just-one/
  storybook.pid    # Contains: 12345
  storybook.log    # Captured output (both foreground and daemon)
  storybook.log.1  # Rotated backup (auto-managed)
  vite.pid         # Contains: 67890 (plus recorded identity evidence)
  1. Check if a PID file exists for that name
  2. If yes, verify it's the same process we started (see PID Reuse Protection below)
  3. If verified, send SIGTERM and wait up to 5 seconds (configurable with --grace)
  4. If still alive, escalate to SIGKILL (force kill)
  5. Start the new process
  6. Save its PID for next time

PID Reuse Protection

Operating systems can reuse PIDs after a process terminates. To prevent accidentally killing an unrelated process that received the same PID, just-one verifies the process's identity before killing it.

On Linux, the PID file records the process's start ticks (field 22 of /proc/<pid>/stat), which are counted from boot and never move with the wall clock. The check is an exact match.

On Windows, it records the process's creation time in milliseconds — a fixed value the OS reports rather than a computed one — the first time the PID file is verified. That check is an exact match too.

On macOS, it records the process's absolute start time from ps -o lstart, at whole-second resolution but identical on every read. That check is an exact match too. It is not the elapsed time other tools report on macOS, which is derived from a moving clock and differs by up to a second between two reads of the same process.

On every other platform, just-one falls back to comparing the PID file's modification time against the process's start time reported by the OS; if these don't match within 5 seconds, the PID file is considered stale and the process is not killed. The fallback stays in place there because those systems report a process's elapsed time in whole seconds, which is too coarse to compare exactly.

PID files written by older versions hold only a bare PID and use that fallback. The exact value is recorded the first time such a file is verified, so a process started by an older version becomes exact without needing a restart. The file's modification time is preserved, so an older just-one sharing the same PID directory keeps working.

When a check fails, the message says what it failed on. A mismatch against a recorded start is definite and reports a different process. A mismatch on the modification-time fallback is not, so it reports an unverified result along with the measured gap — a gap of hours is a clock that moved, not a reused PID. If the process's start time cannot be read at all, the message says that instead, and claims no mismatch.

| Platform | Kill Method | Signal Handling | | -------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------- | | Windows | taskkill /PID <pid> /T /F (kills process tree) | On Ctrl+C, relies on OS-delivered CTRL_C_EVENT for graceful shutdown with a force-kill safety net | | Unix/Mac | kill -SIGTERM -<pid> (process group) | Forwards SIGTERM to child process |

Windows graceful shutdown: In foreground mode with log capture, stdout/stderr are piped so CTRL_C_EVENT may not reach the child automatically. just-one sends an explicit SIGTERM as a defensive fallback, with a force-kill safety net after 2 seconds. With --no-log, the child shares the console directly and receives CTRL_C_EVENT from the OS.

Use Cases

  • Dev servers — Storybook, Vite, webpack-dev-server, Next.js
  • Background processes — API servers, database seeders, watchers
  • CI/CD — Ensure clean state before running tests
  • Multiple instances — Run named instances on different ports
# Run two storybooks on different ports
just-one -n storybook-main -- storybook dev -p 6006
just-one -n storybook-docs -- storybook dev -p 6007

Comparison

| Feature | just-one | kill-port | pm2 | | ---------------------- | --------------- | ------------ | ------------ | | Kills by PID (precise) | Yes | No (by port) | Yes | | PID reuse protection | Yes | No | No | | Status check | Yes | No | Yes | | Cross-platform | Yes | Yes | Yes | | Zero config | Yes | Yes | No | | Remembers processes | Yes (PID file) | No | Yes (daemon) | | Lightweight | Yes (1 dep) | Yes | Heavy | | Daemon mode | Yes | No | Yes | | Log capture & tailing | Yes | No | Yes |

Programmatic Usage

The package exports a main() function that mirrors the CLI interface:

import { main } from '@radleta/just-one';

// main() reads from process.argv and returns an exit code
const exitCode = await main();

This is primarily designed for embedding just-one in custom CLI tooling. For most use cases, the CLI is the recommended interface.

Requirements

  • Node.js >= 20.0.0

Development

# Install dependencies
npm install

# Build
npm run build

# Run tests
npm test

# Lint + typecheck + test
npm run validate

Contributing

Contributions are welcome! Please open an issue to discuss what you'd like to change, or submit a pull request directly.

Changelog

See CHANGELOG.md for a history of changes to this project.

License

MIT