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

@videntia/review-stack

v0.1.5

Published

Multi-slot Docker dev stack (api/web/db) with per-PR port isolation, hot reload, idempotent up/down/migrate/seed. Runtime-agnostic (bun, pnpm, npm).

Readme

@videntia/review-stack

Multi-slot Docker dev stack — bring up an isolated web + api + db per PR with hot reload, deterministic port allocation, and one-command teardown.

Built on top of docker compose. Runtime-agnostic — works under bun, pnpm, npm.

Why

Reviewing PRs locally means spinning up a fresh DB + API + web instance per branch without colliding ports. review-stack does port math + project naming + lifecycle commands so you don't.

Install

# bun
bun add -d @videntia/review-stack

# pnpm
pnpm add -D @videntia/review-stack

# npm
npm i -D @videntia/review-stack

Or one-shot:

bunx @videntia/review-stack up
pnpm dlx @videntia/review-stack up
npx @videntia/review-stack up

Quickstart

# 1. scaffold config + the docker-compose stack (base + infra + review + shared)
#    existing files are kept; only missing ones are written
review-stack init --prefix myapp-review

# 2. start (uses current PR number from `gh pr view`, else fallback)
review-stack up

# 3. iterate — rebuild only what changed since the last `up` (fast loop)
review-stack rebuild         # diff-scoped: touches just api / web / … as needed
review-stack rebuild --all   # ignore the diff and refresh every app service

# 4. inspect
review-stack ps
review-stack logs --service api --follow

# 5. teardown
review-stack down            # drop this slot's volumes + shared db (default)
review-stack down --keep     # stop but preserve volumes + shared db
review-stack reset           # nuke + restart

Port allocation

web = base + (slot % modulus) * stride
api = web + 1
db  = web + 2

Defaults: base=10000, stride=10, modulus=5554. Slot = PR number, or fallbacks.main on main, fallbacks.noPr otherwise.

| PR | web | api | db | |-------|-------|-------|-------| | 7 | 10070 | 10071 | 10072 | | 230 | 12300 | 12301 | 12302 | | 1234 | 22340 | 22341 | 22342 |

Config (review.config.json)

{
  "projectPrefix": "myapp-review",
  "composeFile": "docker-compose.review.yml",
  "services": { "web": "web", "api": "api", "db": "db" },
  "ports": { "base": 10000, "stride": 10, "modulus": 5554 },
  "fallbacks": { "main": 80, "noPr": 90 },
  "hooks": {
    "migrate": "pnpm --filter @workspace/api db:migrate",
    "seed": "pnpm --filter @workspace/api db:seed"
  }
}

Env vars exposed to your compose file and hooks:

  • REVIEW_SLOT — PR/slot number
  • REVIEW_WEB_PORT, REVIEW_API_PORT, REVIEW_DB_PORT — computed host ports
  • COMPOSE_PROJECT_NAME<prefix>-<slot> (set automatically)

Shared backing services (opt-in)

By default every slot runs a full copy of every service — including heavy or stateful ones (Postgres, a virus scanner, an image proxy…). Running N copies of those is wasteful when they can be shared. Add a shared block to run them once for all slots, while keeping each slot's web+api isolated:

{
  "projectPrefix": "myapp-review",
  "composeFile": "docker-compose.review.yml",
  "shared": {
    "composeFile": "docker-compose.shared.yml",
    "project": "myapp-review-shared",
    "network": "myapp-review-shared",
    "db": {
      "service": "db",
      "user": "app",
      "nameTemplate": "myapp_review_{slot}",
      "hostPort": 15500
    }
  }
}

How it works:

  • One singleton stack (shared.composeFile, project shared.project) holds the shared services. up starts it automatically (idempotent) before the slot; manage it directly with review shared up|down|ps.

  • Slot isolation stays at the database layer: each slot gets its own database (nameTemplate, {slot} substituted) inside the one Postgres. up/migrate create it; down / reset drop it. The shared stack keeps running.

  • Wiring: the shared services and every slot's web+api join the external shared.network, so a slot's api reaches them by service name. Compose files declare that network (and any shared volume, e.g. uploads) as external.

  • External volumes are auto-provisioned: up creates any volume the compose file declares external: true (idempotent) before starting, so caches shared across slots — e.g. a common node_modules / package-manager cache — can be marked external to survive a per-slot down and be reused by the next slot.

  • Extra env exposed in shared mode: REVIEW_DB_NAME (per-slot database), REVIEW_SHARED_PROJECT, REVIEW_SHARED_NETWORK. REVIEW_DB_PORT points at shared.db.hostPort so hooks.migrate/hooks.seed connect to the shared PG:

    "hooks": {
      "migrate": "DATABASE_URL=postgres://app:[email protected]:${REVIEW_DB_PORT}/${REVIEW_DB_NAME} pnpm db:migrate"
    }

Trade-off: shared stateless services (scanner/imgproxy) and any shared volume (e.g. uploads) are common to all slots — fine for ephemeral review, not for isolation-critical data. Databases remain per-slot.

Commands

| Command | Purpose | |---------|---------| | init [--prefix] [--force] | Scaffold review.config.json + the compose stack (docker-compose.yml, .infra.yml, .review.yml, .shared.yml); existing files are kept unless --force | | up [--slot N] [--pull] | (shared: ensure shared stack + slot db) build + start + hooks.migrate + hooks.seed | | rebuild [--slot N] [--all] [--migrate] [--seed] | Rebuild/restart only the services affected by changes since the last up — fast iteration (see below) | | down [--keep] | Stop the slot and drop its volumes + shared database; --keep preserves them (external shared volumes are never dropped) | | reset | down -v (+ recreate slot db) then up | | ps | docker compose ps for the active stack | | logs [--service api] [--follow] | Tail logs | | migrate | Run hooks.migrate | | seed | Run hooks.seed | | shared up\|down\|ps [--volumes] | Manage the shared singleton stack (all slots) |

Slot resolution order: --slotREVIEW_SLOT env → gh pr view → fallback.

Incremental rebuild (rebuild)

up builds and starts everything. Once a slot is running, rebuild is the fast loop: it looks at what changed since the last up and touches only the services that actually depend on those changes.

Change detection. up (and reset) record HEAD as a per-slot baseline under .git/review-stack/<slot>.baseline. rebuild diffs baseline..HEAD (committed work) unioned with the working tree (staged, unstaged, and untracked files), then updates the baseline to the new HEAD on success.

Service routing. A changed file maps to a service one of two ways:

  • Inferred (default) — from the compose config: a service's build.context and any host bind-mount sources inside the repo. Buildable services (build:) are rebuilt (build + up -d); bind-mount-only services are force-recreated so the process re-reads code that a macOS bind-mount watcher won't hot-reload.
  • Explicit — set rebuild.services in the config to route paths precisely. This is authoritative: only listed services are considered. Recommended for monorepos where every service shares a root-level context/mount (.), which otherwise makes any change touch every app service.
"rebuild": {
  "services": {
    "api": ["apps/api", "packages/db", "packages/shared"],
    "web": ["apps/web", "packages/ui", "packages/shared"]
  }
}

Backing services (db, etc.) are never bounced — rebuild always runs --no-deps. The migrate hook runs automatically when the api service is affected (force it with --migrate); seed only runs with --seed. A change to the compose file itself, a missing baseline, or --all triggers a full refresh of every app service. When nothing relevant changed, rebuild is a no-op.

License

MIT