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

@webpressvn/lane

v0.3.2

Published

Parallel development lanes: one git worktree, one port block, one forked database and one pm2 process group per task.

Readme

lane

Parallel development lanes. One lane = one git worktree + one port block + one forked database + one pm2 process per service.

Built for running several tasks (or several AI coding agents) against one monorepo at the same time, without them colliding on ports, processes, or schema.

pnpm lane new crm-fix     # worktree, config, ports, forked database, dependencies
pnpm lane up crm-fix      # start every service under pm2
pnpm lane ls              # what exists, what is serving
pnpm lane down crm-fix    # stop, and free the ports for real
pnpm lane rm crm-fix      # stop + drop the database + remove the worktree
lane 0  main worktree      api 3000   web 4000   hieupv_webpress_app
lane 1  .worktrees/<name>  api 3100   web 4100   hieupv_webpress_app_l1
lane 2  .worktrees/<name>  api 3200   web 4200   hieupv_webpress_app_l2

Why each piece exists

The worktree isolates the code. The port block lets two servers run at once. The forked database lets a lane run its own migrations, which is the reason lanes cannot simply share one schema. pm2 owns the processes so ls can tell you what is actually running and down can stop it.

The awkward part is that a new worktree is broken on arrival: the files that carry the database credentials and the API origin are gitignored, so git worktree add does not bring them. lane new copies and patches them, which is most of what it does.

git worktree add also leaves every submodule as an empty directory, so a repository that keeps a library in one does not compile in a fresh lane. lane new runs git submodule update --init --recursive in the new worktree whenever a .gitmodules exists, and warns instead of failing the lane when that command does not succeed.

Removal is the mirror image: git worktree remove refuses outright once .gitmodules exists ("working trees containing submodules cannot be moved or removed"), populated or not, so lane rm passes --force for those repositories. It costs no safety, because the gate that protects real work is lane rm's own dirty-path check, which looks only at that worktree and still refuses without --force.

Why the API command carries --skip-nx-cache

libs/server/config/src/environments/environment.ts is gitignored, so Nx leaves it out of the build hash. Without the flag a lane that patched its own environment.ts (fork database, lane ports) can be served from a cached dist/apps/server/main.js built for the main lane: the lane API then reads and writes the MAIN database and registers its RBAC presets there. Keep the flag on api.command (and on any custom command that builds the server) until the build target lists environment.ts as an input.

Commands

| Command | What it does | |---|---| | lane new <name> | Worktree → copy gitignored config → allocate ports → fork database → patch config → install | | lane up [name] | Start every service under pm2. Idempotent, so it also means restart | | lane down [name] | Stop the services, then sweep the lane's ports | | lane ls | Every lane: branch, database, ports, process state | | lane logs [name] | Stream pm2 logs (--service, --lines) | | lane rm <name> | Full cleanup: processes, ports, database, worktree, lane index | | lane describe | MCP-shaped tool manifest for agents |

[name] defaults to main (the main worktree, lane 0), so lane up with no argument manages your primary checkout like any other lane.

Useful flags: --no-fork-db (share the source database), --schema-only (fork structure without rows, much faster), --keep-db on rm, --dry-run on up/down, --branch / --base on new.

For agents

pnpm -s lane describe --json prints an MCP-shaped manifest: every command with a description, JSON Schema for its inputs, the exact shell template to call, and what it returns. There is no MCP server: an agent reads the manifest and shells out.

  • Always pass --json, and always run pnpm with -s. Human output goes to stderr and one JSON object goes to stdout; without -s, pnpm prints its own banner onto stdout and the JSON will not parse.
  • Success is exit 0 with {"ok":true,...}; failure is exit 1 with {"ok":false,"error":...}.
  • lane logs is the exception: it streams raw pm2 output and never terminates on its own.
pnpm -s lane ls --json                    # find a free lane / see what is serving
pnpm -s lane new my-task --json           # returns ports, database, path
pnpm -s lane up my-task --json            # returns the health url to poll
pnpm -s lane up my-task --dry-run --json  # print the pm2 commands, change nothing

lane up returns before the servers are ready (the task runner is still compiling). Poll the returned health url until it responds.

Configuration

lane.config.json at the repository root overrides any subset of the shipped defaults (lane.config.default.json). Ports, services, the files to copy and the patches to apply are all data, so adding a third served process or a new file to patch is a config edit rather than a code change.

{
  "ports": { "stride": 100 },              // lane N: portBase + stride * N
  "services": [
    { "name": "api", "portBase": 3000, "command": "nx serve server",
      "env": { "PORT": "{port}" }, "healthPath": "/api/health" }
  ],
  "pm2": { "prefix": "wp", "runner": "pnpm" },
  "database": { "fork": true, "nameTemplate": "{source}_l{index}", "dropOnRemove": true },
  "files": [
    { "path": "apps/frontend/.env", "required": true, "format": "dotenv",
      "patches": [{ "key": "NEXT_PUBLIC_API_BASE_URL", "value": "http://localhost:{port:api}/api" }] }
  ]
}

Templates understand {index}, {name}, {database}, {source}, {port} (the current service's) and {port:<service>}. Patch formats are ts-object (key: 'value' in a TypeScript literal) and dotenv (KEY=value).

State lives in .worktrees/lanes.json, the lane database, holding each lane's index, ports, branch, database and pm2 names. Indexes come from that file rather than from position in git worktree list, because removing a worktree would otherwise renumber the survivors while their processes are still bound to the old ports.

Implementation notes

The database fork uses mysql2, not mysqldump. Source and target live on the same server, so CREATE TABLE … LIKE + INSERT … SELECT copies server-side with no dump crossing the wire. It also sidesteps MySQL client 9.x, which dropped mysql_native_password and cannot authenticate against servers that still require it. Routines and triggers are not copied; lane new warns when the source has any.

The copy runs in two phases, never interleaved. planCopy (src/core/copy-plan.ts, pure and unit-tested) emits every CREATE TABLE … LIKE as phase one and every INSERT … SELECT as phase two; forkDatabase awaits a phase completely before starting the next, and only the batches inside a phase run in parallel. The earlier single flat list sliced into 25-statement batches let a worker run a table's INSERT while another worker still held its CREATE, which failed every data fork with Table <db>_l1.attendance_departmentSchedule doesn't exist.

Tables with a generated column get an explicit column list. INSERT … SELECT * refuses a value for a STORED/VIRTUAL GENERATED column (23 CRM tables carry one, e.g. crm_account.codeActiveKey), so the plan reads information_schema.COLUMNS and leaves those columns out of both lists; the server recomputes them on insert.

Expect the fork to take a minute or two. Per-table DDL on a remote server dominates: a 413-table schema takes ~40s spread across COPY_CONCURRENCY connections, against ~2m40s on a single one. --schema-only skips the rows when a lane only needs somewhere to run migrations.

down sweeps ports, it does not just stop processes. pm2 stops what it spawned, but the package-manager → task-runner → node chain beneath it can survive and keep holding the port. The sweep is scoped to the lane's own ports, so it can never touch another lane.

new rolls back. Anything that can fail without side effects is checked before the worktree exists; anything created after that is undone if a later step throws.

Running E2E on a lane

The Playwright suite is lane-aware end to end, so a lane runs it against its own servers and its own database while lane 0 keeps serving:

pnpm -s lane new e2e-finance --json          # worktree + ports + forked database
pnpm -s lane up e2e-finance --json           # poll the returned health url until it answers
cd .worktrees/e2e-finance
pnpm exec playwright test --config apps/frontend/playwright.config.ts tests/e2e/finance
pnpm exec playwright test --config apps/frontend/playwright.config.ts   # the whole suite
cd - && pnpm lane rm e2e-finance

What makes that work, and what to check when it does not:

  • Ports. apps/frontend/tests/e2e/helpers/lane.ts reads the worktree's .lane.json (written by lane new) and derives baseURL and the API origin from the lane index; a plain checkout has no .lane.json and stays on 3000/4000. Never hardcode a port in a spec.
  • Login. tests/e2e/global-setup.ts signs every role in through the id-service API from Node (tests/e2e/helpers/session.ts) and hands the browser the three cookies the login form would set. The form itself cannot log in from a lane: the shared id-service allows the origin http://localhost:4000 only, so its preflight for :4100 fails with "Failed to fetch". The id-service URL comes from apps/frontend/.env (NEXT_PUBLIC_AWS_API_URL, copied by lane new) or E2E_ID_SERVICE_URL in .env.e2e; on a lane the setup refuses to start without one (on lane 0 it falls back to driving the login form, the pre-lane behaviour).
  • Credentials are read from apps/frontend/.env.e2e (gitignored, copied by lane new; the template is .env.e2e.example). Nothing is hardcoded in a spec or helper.
  • e2eOrganizationId must be set in environment.ts (gitignored, so lane new copies the one on your machine and a new lane already has it; a fresh MACHINE has to fill it in). Set it to the same value as E2E_ORG_ID in .env.e2e. Global setup calls POST /application/debug/finance/wipe once per run to clear that organization's finance data, because every finance flow works in its own fresh fiscal year and never gives it back: after four or five runs the range up to the 2037 ceiling is used up and EVERY finance flow fails at once, with a message about fiscal years that has nothing to do with what it was testing. Unset means the endpoint refuses (403) and the wipe is skipped with a warning, which is survivable for one run. Leave it empty on any machine holding real books: it is the second lock behind the production flag.
  • Gitignored keys. apps/server/src/assets/nats/certs/client.key (and ca.key) are ignored by git, so a fresh worktree has none; apps/server/src/main.ts reads client.key at boot and the lane's API dies without it. Both are listed as optional entries in the repository's lane.config.json files (and in the shipped lane.config.default.json), so lane new copies them when the main worktree has them. Arrays in lane.config.json replace the defaults wholesale: keep the two entries when you edit that list.
  • Data. Each finance flow works in its own fresh fiscal year, so a lane that already holds data or a period closed by the previous run never blocks the next one; run RPC-heavy flows one lane at a time until the NATS bus is per-lane (below).

Resetting a lane database in place

lane rm then lane new gives the lane a new index, new ports and a new database name. When other sessions are configured against a lane, reset its database in place instead: docs/ai/testing/lane-db-reset.md (stop, drop and re-fork under the same name with dropDatabase and forkDatabase from src/core/database.ts, delete the module's test data, then migrate, start and seed).

Known constraint: the NATS bus

A lane gets its own database but still shares the message bus, so another lane's server can answer its RPCs against a different database. Set nats.enabled in lane.config.json (and install nats-server) to give each lane its own broker; until then, run RPC-heavy flows one lane at a time. lane new warns about this whenever it forks a database. The CRM posting E2E flows (crm-order-revenue-posting, crm-billing-revenue, finance-posting-retry) are such flows: see docs/ai/design/finance/finance.md «E2E fiscal years».

Requirements

  • Node >= 20, git, pm2 (npm i -g pm2)
  • A MySQL-compatible server reachable with an account that can CREATE/DROP DATABASE