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

laracrew

v0.3.0

Published

Boot and supervise all the long-running processes of your projects with one command. Laravel-aware, works with anything.

Readme

laracrew

Boot every long-running process of every Laravel project you're working on — with one command.

php artisan serve · queue:work · horizon · Redis stream listeners · schedule:work · npm run dev — across two, three or ten projects, in the right order, supervised, in one terminal.

github.com/vidux/laracrew


The problem

You develop two Laravel apps that talk to each other. Starting work means opening eight to fourteen terminal tabs and typing, in the right order:

cd D:/work/api     && php artisan serve
cd D:/work/api     && php artisan queue:work redis --queue=high,default
cd D:/work/api     && php artisan streams:listen orders
cd D:/work/api     && php artisan schedule:work
cd D:/work/api     && npm run dev
cd D:/work/portal  && php artisan serve --port=8001
cd D:/work/portal  && php artisan queue:work redis --queue=default
cd D:/work/portal  && php artisan streams:listen inventory

Every one is long-running. When a worker dies you don't notice. When you edit a job class you have to remember PHP workers cache code. When you close the terminal, half of them survive as orphans still holding port 8000.

The fix

laracrew up dual
  laracrew · dual                                     up 00:15:27  · 6/7 running  all healthy
  ────────────────────────────────────────────────────────────────────────────────────────────
  up/down select   enter inspect log   0-9 jump   r restart   s stop/start   a all logs
  ? help   q quit
  STACK DETAILS (process tree)

  DEPENDENT SERVICES (checked, not managed)
      ● redis            ready      tcp 127.0.0.1:6379

  processes (7 managed)
  │
  ├─ ▸ [0] ● api:serve       running    15m 04s
  ├─   [1] ● api:queue       running    15m 04s   ⟳2
  ├─   [2] ● api:streams     running    15m 04s
  ├─   [3] ● api:schedule    running    15m 04s
  ├─   [4] ● portal:serve    running    14m 58s
  ├─   [5] ◼ portal:queue    stopped
  └─   [6] ● portal:streams  running    14m 58s
  │
  └─ Ready in 1.9s. Awaiting keyboard input…

Services marked external: true — Redis, Postgres, anything laracrew checks but never runs — sit above the tree under their own heading, with the probe they are checked with. They carry no index, because there is nothing to start or stop; if one is unreachable the header says 1 dependency down before anything else.

The default screen never streams logs. Nine services interleaving output is unreadable — you can't see the shape of the fleet and you can't follow any one process. So you get the tree, and you open logs deliberately: press 2, read api:queue, press esc.

  api:queue   running  · pid 14184  · up 15m 04s
  $ php artisan queue:work redis --queue=high,default
  D:/work/api
  ────────────────────────────────────────────────────────────────────────────────────────────
  08:51:30 | Processing: App\Jobs\SyncOrder
  08:51:30 | Processed:  App\Jobs\SyncOrder (412ms)
  08:51:31 | Processing: App\Jobs\NotifyPortal

One Ctrl-C stops all of it, in reverse dependency order, gracefully — workers finish the job they're holding before they exit, and nothing is left behind.


Why not concurrently, pm2 or docker compose

Those run processes. laracrew knows what the processes are.

  • It stops workers on their own terms. php artisan queue:restart for Laravel, celery control shutdown for Celery, any command you name — run first, wait for the process to finish the job it is holding, then terminate. Never a job killed mid-flight.
  • It kills whole process trees. php artisan serve spawns a child PHP server; npm run dev spawns Vite. Killing the parent orphans them and the port stays bound. Every stop is a tree kill (taskkill /T /F on Windows, process-group kill on POSIX).
  • It gates on readiness, not on sleep. portal:serve doesn't start until Redis answers and api:serve returns HTTP 200.
  • It knows your .env. laracrew doctor catches two projects quietly sharing one Redis database and a queue name — where each app's workers silently steal the other's jobs. That class of bug eats afternoons.

Install

npm install -g laracrew
laracrew init --examples

Or from source:

git clone https://github.com/vidux/laracrew && cd laracrew
npm install && npm run build && npm link

Requires Node 20+. Works on Windows, macOS and Linux. PHP is only needed for the projects laracrew runs, not for laracrew itself — and only if those projects are PHP.

Quick start

laracrew init --examples   # creates ~/.laracrew with a demo stack and a two-project template
laracrew up example        # runs the demo — no Laravel project needed, proves it works here
laracrew ls                # what's defined

laracrew init on its own creates just the config files. Add --examples when you want the runnable demo stack and the ready-made two-project template to start from.

Then point it at real projects. Edit ~/.laracrew/projects.yaml:

projects:
  api:
    path: D:/work/api
    php: php            # or an absolute path to a specific PHP build
    envFile: .env
    color: cyan
  portal:
    path: D:/work/portal
    php: php
    color: magenta

laracrew init already wrote you a dual stack wired for exactly this scenario. Check it, then boot it:

laracrew doctor dual
laracrew up dual

Prefer not to write YAML by hand? Open docs/stack-builder.html in a browser. Add your projects and the commands that run under them, and it writes the stack.yaml as you go — checking the same rules laracrew does, so a stack that looks right there boots. No install, no build step; it is one file.

Or build it from the command line, one service at a time, from inside the project:

cd D:/work/api
laracrew draft dual                                                      # starts ./dual.laracrew.yaml
laracrew draft add --project api --command "php artisan queue:work"      # --path defaults to this folder
laracrew draft add --project api --command "php artisan schedule:work"   # the path is remembered
laracrew draft add --project portal --path D:/work/portal --command "php artisan queue:work"
laracrew draft add --project portal --command "npm run dev" --default-state stopped   # idle until you press s
laracrew draft publish                                                   # validates it, installs ~/.laracrew/stacks/dual

laracrew draft on its own shows what the draft holds. Service names come from the project and the command (api:queue:work, portal:dev); --name overrides. The draft is a plain stack.yaml: edit it by hand, keep it next to the code, commit it. publish checks it with the same rules as any other stack, and --link also installs the global command (dual).


One command per project set

Typing laracrew up dual every morning gets old, and you have more than one project set. Give each set its own global command:

laracrew link dual
✔ created dual → laracrew up dual

in C:\Users\you\AppData\Roaming\npm

Run it from anywhere:  dual
Flags pass straight through:  dual --only workers

From then on, one word boots that whole set — from any directory:

dual                    # boots all 8 services of the dual stack
dual --only workers     # every `laracrew up` flag still works

A stack names its own command in stack.yaml:

name: dual
command: dual          # what `laracrew link` installs; defaults to the stack name

So you end up with one command per set — dual, billing, legacy — each booting its own fleet of projects.

| | | |---|---| | laracrew link <stack> | Install the command. Re-run any time to update it. | | laracrew link <stack> --as <name> | Use a different name than the stack declares. | | laracrew link --all | Install for every stack that declares command:. | | laracrew link <stack> --dir <path> | Install somewhere other than the default. | | laracrew unlink <name> | Remove it again. | | laracrew ls | Shows which stacks have a command installed. |

Where they go. Into the same directory as laracrew itself — the npm global bin, which is already on your PATH. On Windows you get three files (dual, dual.cmd, dual.ps1) so the command behaves identically in Git Bash, cmd and PowerShell. Override with --dir or the LARACREW_BIN environment variable. If laracrew can't find a directory that's on your PATH, it falls back to ~/.laracrew/bin and prints the one line you need to add it.

It won't stomp on anything. Every generated file carries a laracrew-generated marker. laracrew refuses to overwrite a file it didn't write, refuses to shadow names like npm or git, and unlink leaves foreign files alone. Pass --force if you genuinely mean it.


Logs survive

The live view keeps the last few thousand lines per service in memory — minutes, on a busy stack. Everything is also written to disk, so the exception you watched scroll past is still there tomorrow.

laracrew logs api:queue                  # last 200 lines, after the fact
laracrew logs api:queue -f               # and keep following
laracrew logs --all --since 10m          # every service, merged and time-ordered
laracrew logs --list                     # which services have a log

Files land in ~/.laracrew/logs/<stack>/<service>.log, rotated at 5 MB with one older copy kept. They are plain text with a sortable local timestamp and no colour codes, so your usual tools work on them directly:

2026-09-19 11:35:25.565 stderr PaymentFailedException: card declined
grep -i exception ~/.laracrew/logs/dual/api-queue.log

This is on by default. Turn it off per stack if you would rather not:

defaults:
  logs: { toFile: false }

| Setting | Default | Meaning | |---|---|---| | logs.toFile | true | Write every line to disk | | logs.maxLines | 5000 | Lines kept in memory for the live view | | logs.maxFileBytes | 5000000 | Rotate a service's log past this size | | logs.keepFiles | 1 | Rotated copies kept beside the current file |


Starting things on demand

Not every command should run all day. A scheduler tick, a one-off sync listener, a queue you only drain occasionally — define them in the stack, but don't launch them:

  - name: api:queue
    project: api
    cmd: ["php", "artisan", "queue:work"]        # starts with the stack

  - name: api:streams
    project: api
    cmd: ["php", "artisan", "redis-stream:run", "orders_sync"]
    autostart: false                              # defined, listed, not launched

  - name: api:schedule
    project: api
    cmd: ["php", "artisan", "schedule:run"]
    autostart: false
    restart: never                                # one tick, not a daemon

They appear in the tree as idle, waiting for you:

  ├─   [1] ● api:serve       running    8s
  ├─   [2] ● api:queue       running    8s
  ├─   [3] ○ api:streams     idle       press s
  ├─   [4] ○ api:schedule    idle       press s
  ├─   [5] ● portal:queue    running    8s

Select one and press s to start it; s again to stop it. The header counts them (4/7 running · 2 idle) so you can see at a glance what is dormant.

One rule the config enforces: a service that starts at launch may not needs: a service you have to start by hand — that would leave it waiting on a gate nobody opened. laracrew refuses the stack with a message naming both services rather than hanging.


Driving it

| Key | Does | |---|---| | up down / j k | Move the selection | | 0-9 | Jump to that process and open its log | | enter | Inspect the selected process | | esc | Back to the tree | | a | Merged log across every service - the firehose, on demand | | r | Restart the selected process (graceful: queue:restart first) | | R | Restart every running process, in dependency order. Stopped, idle and failed ones stay as they are | | s | Stop it, or start it again if stopped | | f / g / G | Follow-pause tailing; jump to top or bottom | | ? | Help | | q / ctrl-c | Quit - stops every process first |

The view is plain ANSI on node:readline, no Ink and no React. It repaints on a 250 ms poll rather than per log line, so a worker emitting 500 lines a second costs nothing to display. LARACREW_ASCII=1 swaps in an ASCII glyph set for terminals that mangle box drawing.

When stdout is not a TTY you get prefixed interleaved logs automatically, so laracrew up dual | tee dev.log does the right thing with no flag.


Examples

Five working stacks in examples/ — only one of them is Laravel. Each is self-contained, so copy one, fix the paths, and run it:

cp -r examples/node-api-and-web ~/.laracrew/stacks/
laracrew up node-api-and-web

| Example | What it is | |---|---| | laravel-dual | Two interconnected Laravel apps: queues, stream listeners, schedulers, Vite | | node-api-and-web | TypeScript API, Vite frontend, BullMQ worker, Postgres and Redis | | django-celery | Django, a Celery worker, beat, and optional extras | | polyglot-microservices | Go, Rust, Node and Python behind a gateway, with Docker Compose for infrastructure | | frontend-monorepo | tsc, Tailwind, Storybook and docs watchers in one repo — no servers at all |

examples/README.md explains what each one is there to teach, plus the patterns worth stealing: gating on reality instead of sleeping, keeping occasional commands in the stack but idle, and letting laracrew own docker compose too.


Concepts

| Concept | What it is | |---|---| | Project | One Laravel app root: path, PHP binary, .env. Defined once in projects.yaml, referenced by key. | | Service | One long-running process: command, cwd, dependencies, readiness gate, restart policy. | | Stack | A named set of services spanning one or more projects — the thing you laracrew up, and what a linked command boots. | | Group | A tag on a service (workers, http, assets) for --only / --except. | | Profile | A named filter stored in the stack (light = everything except assets). | | Task | A one-shot ordered sequence across projects (reset = migrate:fresh + seed on both). |

Everything lives under ~/.laracrew/, never inside your Laravel projects. laracrew only ever reads your project files.

~/.laracrew/
├── config.yaml              # theme, default stack, poll intervals
├── projects.yaml            # your projects, referenced by key
├── stacks/
│   ├── dual/
│   │   ├── stack.yaml       # the definition
│   │   └── services/        # optional: split a big stack into fragments
│   └── example/stack.yaml
├── tasks/reset.yaml
└── fragments/

One folder per stack, so a stack can carry its own fragments and notes. The whole directory is safe to keep in git and sync between machines.


Configuring a stack

A complete two-project setup:

name: dual
description: API + Portal with queues, streams and schedulers
command: dual                 # `laracrew link dual` installs this as a global command

use: [api, portal]            # from ~/.laracrew/projects.yaml

defaults:                     # inherited by every service, overridable per service
  restart: on-failure
  backoff: { initialMs: 1000, maxMs: 30000, factor: 2, maxRestarts: 10 }
  stop: { graceMs: 10000 }

services:
  - name: redis
    external: true            # health-checked, never started by laracrew
    ready: { tcp: "127.0.0.1:6379" }

  - name: api:serve
    project: api
    cmd: php artisan serve --port=${port:8000}
    groups: [http]
    needs: [redis]
    ready: { http: "http://127.0.0.1:8000/up", timeoutMs: 20000 }
    url: http://127.0.0.1:8000

  - name: api:queue
    project: api
    cmd: php artisan queue:work redis --queue=high,default --tries=3
    groups: [workers]
    needs: [redis]
    stop: { artisan: "queue:restart", graceMs: 15000 }   # finish the current job first
    metrics: { queues: [high, default] }

  - name: portal:serve
    project: portal
    cmd: php artisan serve --port=${port:8001}
    groups: [http]
    needs: [redis, api:serve]       # waits for the API to actually answer
    ready: { http: "http://127.0.0.1:8001/up" }

profiles:
  light: { except: [assets] }
  workers-only: { only: [workers] }

Service fields

| Field | Notes | |---|---| | name | Required. Convention is project:role; used as the log prefix. | | project | Supplies cwd, the PHP binary, the .env and the colour. | | cmd | A shell string, or an argv array (["php", "artisan", "queue:work"]). The array form skips shell parsing — prefer it when arguments contain spaces. | | cwd | Defaults to the project path. | | env | Extra environment variables, merged over the inherited environment. | | groups | Tags for --only / --except. | | needs | Dependency edges. Cycles are a config error that names the members. | | ready | tcp, http, logMatch (regex over output) or delayMs, plus timeoutMs (default 30000) and intervalMs (default 250). Without it, "spawned" means ready. | | restart | never · on-failure (default) · always. | | backoff | initialMs, maxMs, factor, maxRestarts, resetAfterMs. Delay is min(initialMs × factor^n, maxMs); the counter resets after the service stays up for resetAfterMs. | | stop | exec (any graceful shutdown command), artisan (sugar for one that runs artisan, such as queue:restart or horizon:terminate), signal, graceMs. | | url | Recorded for the service; laracrew open is not built yet. | | external | Health-checked but never spawned — Redis, MySQL, a Docker service. | | autostart | false defines the service without launching it. It shows as idle in the tree; select it and press s when you need it. | | enabled | Quick off switch without deleting the block. | | color | Log-prefix colour; defaults to the project's. |

Interpolation

| Token | Expands to | |---|---| | ${env:FOO} / ${env:FOO:fallback} | laracrew's own environment | | ${project.path} | the service's project root | | ${project.env:REDIS_PORT} | a value from that project's .env | | ${stack.dir} | the stack's own folder | | ${port:8000} | a port, recorded so doctor can check it for clashes |


Commands

laracrew init [--examples]         # create ~/.laracrew; --examples adds a demo stack + template
laracrew draft [name]              # start a stack draft in this folder, or show it
laracrew draft add --command "…" [--project key] [--path dir] [--name svc] [--default-state stopped]
laracrew draft publish [--force] [--link]   # validate the draft and install it under ~/.laracrew
laracrew ls [--json]               # list stacks, projects and tasks
laracrew doctor [stack]            # check a stack before booting it
laracrew up [stack] [options]      # boot the fleet and supervise it
laracrew logs [service] [--stack name] [-n 200] [-f] [--since 10m] [--all] [--list]
laracrew link [stack] [--as name] [--all] [--dir path] [--force]
laracrew unlink <name>             # remove a command laracrew installed
laracrew --version

laracrew up options:

| Option | Effect | |---|---| | --only <selector> | Only these services or groups. Repeatable, comma-separated. | | --except <selector> | Skip these services or groups. | | --profile <name> | Apply a profile defined in the stack. | | --json | Newline-delimited JSON events instead of logs — one object per line. | | --plain | Prefixed interleaved logs instead of the full-screen view. Automatic when stdout is not a TTY. |

Omit the stack name and laracrew uses defaultStack from config.yaml, or the only stack that exists, or tells you which ones it found.

laracrew up dual --only workers            # just the queue workers and listeners
laracrew up dual --except assets           # skip Vite
laracrew up dual --profile light
laracrew up dual --json | jq 'select(.type=="service:exit")'

What doctor checks

doctor · dual

✔ stack "dual" is valid — 8 services
✔ php: PHP 8.3.11 (cli)
✖ api and portal share Redis 127.0.0.1:6379/0# AND queue(s): default
  each project's workers will steal the other's jobs — set a different REDIS_DB or REDIS_PREFIX
✖ project "api" runs a queue worker but QUEUE_CONNECTION=sync
  jobs run inline on dispatch, so the worker will sit idle forever — set it to redis or database
✖ port 8000 is already in use
▲ redis not reachable at 127.0.0.1:6380

✖ 3 problems, 1 warning

Exit code is 1 when anything is at ✖, so it drops straight into a pre-flight script.

Every Laravel-specific check is skipped on a stack that isn't Laravel. doctor works out which projects actually run PHP — from the commands they declare and from stop.artisan — and only those get the php --version check and the "no artisan file" warning. The same goes for Redis: a project is only checked for namespace collisions and reachability if its .env or its commands say it talks to Redis. Run doctor on a Django or Node stack and you get the checks that apply to it, not a wall of PHP complaints:

doctor · django-celery

✔ stack "django-celery" is valid — 8 services
✔ port 8000 is free
✔ postgres is reachable — tcp 127.0.0.1:5432
▲ redis is not reachable — tcp 127.0.0.1:6379 (ECONNREFUSED)
  laracrew never starts an external service; anything that needs it will wait at its gate

✔ no blocking problems, 1 warning

Services marked external: true are checked through the gate they already declare, so whatever your stack depends on — Postgres, RabbitMQ, an HTTP service — gets verified before boot.


How shutdown works

This is where most process managers leave a mess, so it's worth stating exactly. Each step runs only if the previous one timed out:

  1. Graceful — if the service declares stop.exec, run it and wait up to graceMs for the process to exit on its own, having finished whatever it was holding.
  2. Signal — SIGTERM to the process group. Skipped on Windows, which has no equivalent.
  3. Tree kill — taskkill /pid <pid> /T /F on Windows, kill(-pid) on POSIX. This is what catches the child PHP server behind artisan serve and the Vite process behind npm run dev.
  4. Verify — re-check the pid and warn loudly if anything survived.

Stacks come down in reverse dependency order, parallel within a level. A second Ctrl-C escalates immediately and says so.

Step 1 is any command, so every worker gets the same treatment your queue workers do:

stop: { exec: ["celery", "-A", "app", "control", "shutdown"], graceMs: 20000 }
stop: { exec: "npm run drain", graceMs: 5000 }
stop: { exec: ["docker", "compose", "stop"], graceMs: 30000 }
stop: { artisan: "queue:restart", graceMs: 15000 }   # shorthand for `<php> artisan queue:restart`

artisan is the Laravel shorthand: it runs through the project's PHP binary, from the project root, even when the service sets its own cwd. It needs a project; anything else uses exec. Without either, the ladder starts at the signal.

If a readiness gate fails during boot, laracrew rolls back everything it already started before exiting non-zero — you never get a half-booted stack you have to clean up by hand.


Environment

| Variable | Effect | |---|---| | LARACREW_HOME | Override ~/.laracrew. | | LARACREW_BIN | Where laracrew link installs global commands. | | LARACREW_ASCII | 1 swaps box-drawing glyphs for ASCII. | | NO_COLOR | Disable colour, even on a TTY. | | FORCE_COLOR | Enable colour when piping. |

Each child process is given LARACREW=1, LARACREW_SERVICE=<name> and, when it belongs to a project, LARACREW_PROJECT=<key>.


Status

v0.3.0 — supervisor, full-screen view and laracrew draft are built and tested.

Working now: config pipeline, dependency-ordered boot with readiness gates, restart policies with exponential backoff, the graceful stop ladder — stop.exec for any process, stop.artisan as the Laravel shorthand — the full-screen process tree with per-process log inspection and a one-key restart of everything running, logs persisted to disk with laracrew logs to read them back, plain and JSON renderers, per-stack global commands (link / unlink), doctor, init, ls.

See CHANGELOG.md for what changed in each release.

Accepted by the config schema but not yet acted on — they validate, so your stack files are future-proof, but nothing happens yet:

| Key | Lands in | |---|---| | watch | M4 — file-change restarts via queue:restart | | metrics | M3 — live queue depth and stream lag (today doctor reads it for collision checks) | | hooks.preUp / hooks.postDown | not scheduled |

Roadmap

| | | |---|---| | M3 | laracrew scan project discovery, Redis queue depth and stream consumer lag on screen, failed-job badge | | M4 | File watching with graceful queue:restart, and laracrew run <task> | | M5 | Background daemon: up --detach, attach, status, logs -f | | M6 | Themes, JSON Schema for editor autocomplete, shell completions |

The full plan lives in .claude/PLAN.md, with the design in ARCHITECTURE.md, CONFIG-SPEC.md and TUI-UX.md.


Development

npm install
npm run dev -- up example     # tsx, no build step
npm run build                 # tsup -> dist/index.js
npm test                      # vitest, 281 tests
npm run typecheck
npm link                      # put `laracrew` on PATH while hacking on it

Runtime dependencies, in total: commander, yaml, zod and chalk. Process spawning and tree-killing are hand-rolled — see ARCHITECTURE.md §9 for why execa and tree-kill were dropped, and what chalk costs. Startup time is a feature for a tool you run twenty times a day.

The test suite spawns real child processes, binds real ports and asserts that no pid survives a shutdown — including a deliberately spawned grandchild and a process that ignores SIGTERM. Every test runs against a throwaway LARACREW_HOME.

Architectural rule worth knowing before you contribute: nothing in src/core/ may import from src/cli/. Core emits typed events; the plain renderer, the JSON renderer and the coming TUI are all just subscribers. That's what keeps --plain, --detach and the tests honest.

Try it without installing from npm

Five steps from a fresh clone to a running stack, without touching your real ~/.laracrew:

  1. Clone and install.

    git clone https://github.com/vidux/laracrew.git && cd laracrew && npm install
  2. Point it at a throwaway home. Everything below reads and writes there, not your real config.

    export LARACREW_HOME=/tmp/laracrew-try            # PowerShell: $env:LARACREW_HOME = "$env:TEMP\laracrew-try"
  3. Run from source. npm run dev runs src/index.ts through tsx — no build step, every edit is live.

    npm run dev -- init --examples
    npm run dev -- doctor example
    npm run dev -- up example                          # the full-screen view; q quits
    npm run dev -- up example --plain                  # what a pipe or CI sees
  4. Run the real binary. laracrew link needs the build, and this is what users get.

    npm run build && npm link                          # `laracrew` on your PATH -> this checkout's dist/
    laracrew up example
    npm unlink -g laracrew                             # when you are done

    Rebuild after each change: the link runs dist/index.js, not the TypeScript.

  5. Test the exact tarball npm would publish. The smoke test packs it, installs it into an empty directory, runs the installed binary and checks that no child process survived:

    bash .claude/skills/prepare-for-publish/smoke-test.sh

    By hand: npm pack, then in an empty directory npm install ../laracrew/laracrew-<version>.tgz and npx laracrew --version.

License

MIT