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

@dileep-ct/grove

v1.5.0

Published

Run every branch of a Laravel app side by side: one git worktree per branch with its own URLs, databases, dependencies and logs (php-fpm with nginx or Apache, or Laravel Sail; MySQL/MariaDB or SQLite; Linux and macOS; optional HTTPS)

Readme

grove

npm license

Run every branch of your Laravel app side by side. grove gives each branch its own git worktree with its own URLs, its own copy of the databases, its own vendor/ and node_modules/, .env and logs. Review a pull request, try a migration or keep a long feature branch open while your main checkout stays exactly as it was: no stashing, no re-seeding, no switching back and forth.

grove new feat/5000  →  ~/www/my-app-worktrees/feat-5000
                        http://feat-5000.my-app.test, http://acme.feat-5000.my-app.test, …
                        databases grove_feat_5000 + grove_feat_5000__<tenant-id> per tenant

Features

  • A worktree per branch in a folder next to your checkout, created from an existing branch, a remote branch or a new one. grove cd feat-5000 takes you there.
  • Its own URLs, served by nginx or Apache with php-fpm, or with Laravel Sail by its own app container in Docker, over HTTP or HTTPS (with mkcert certificates), including one per tenant subdomain.
  • Its own databases, cloned from the main checkout or another worktree: MySQL/MariaDB with mysqldump (stancl/tenancy tenant databases too), or SQLite with VACUUM INTO. Or share the main checkout's with --db shared.
  • Fast dependencies: vendor/ and node_modules/ are copied copy-on-write where the filesystem allows, and composer or npm only run when the lock files differ from the main checkout's.
  • A ready .env, generated from the main checkout's, pointing at the worktree's own URL, databases, cache, session cookie and logs, plus a "grove keep" block for your own per-worktree settings.
  • Migrations and the asset build run for you; every step is logged, and an interrupted grove new continues where it stopped.
  • Housekeeping: grove list shows activity and push/merge state, grove stale and grove prune find and remove idle worktrees safely, grove rm backs up uncommitted work and deletes a branch only when it is merged, and grove doctor finds anything left behind.
  • Shell integration for bash, zsh and fish: grove cd and tab completion.
  • Linux and macOS.

Requirements

  • Linux or macOS. On Linux, grove knows the packages, web server layouts and php-fpm sockets of the Arch, Debian/Ubuntu, Fedora/RHEL and openSUSE families; on macOS it uses Homebrew. WSL is not tested.
  • Node 20.12+, git and flock (util-linux on Linux; brew install flock on macOS).
  • One way to run the app (grove calls it the runtime; see Runtimes):
    • php-fpm on the machine (host): php with php-fpm, composer, npm (or the pnpm, yarn or bun your app uses) and nginx or Apache. MySQL apps need the mysql/mariadb client tools, SQLite apps PHP's pdo_sqlite.
    • Laravel Sail (sail): Docker with its compose plugin (Docker Desktop, OrbStack or Colima on macOS). PHP, Composer, Node and the database client all come from Sail's containers. MySQL/MariaDB apps only, so far.
  • sudo, for the small root helper that writes /etc/hosts entries (and, with php-fpm, the worktrees' web server sites).
  • For HTTPS: mkcert (and certutil, so Firefox trusts it too).

grove setup lists anything missing and offers to install it with your package manager (apt, pacman, dnf, zypper or Homebrew).

Install

npm install -g @dileep-ct/grove

Update with npm install -g @dileep-ct/grove@latest, then run grove setup once if grove asks for it. Install grove globally, not in your app: it is a machine-wide tool, and its root helper and shell integration expect one grove per machine.

Quick start

cd ~/www/my-app
grove init                  # once per repository: writes .grove.json; commit it
grove setup --passwordless  # once per machine and repository (asks for your sudo password)
grove new feat/123          # create the worktree, then open the URL it prints
grove cd feat-123           # work in it
grove rm feat-123           # remove it and everything it owns

Run grove inside the app's checkout or one of its worktrees; from anywhere else it uses the checkout it worked with last (GROVE_MAIN=<path> picks one explicitly). grove help lists every command.

With Laravel Sail, start Sail in the main checkout first (./vendor/bin/sail up -d); the rest is the same.

What grove new does

Each step is recorded in the worktree's manifest, so running grove new again after an error or Ctrl-C continues where it stopped.

  1. worktree: git worktree add for the branch, which it creates from --from (default <remote>/<baseBranch>) when it does not exist yet.
  2. deps: copies vendor/ and node_modules/ from the main checkout, copy-on-write where the filesystem supports it (--fresh-deps installs them from the lock files instead).
  3. files: copies the git-ignored files listed in copyIgnored, such as .env.testing.
  4. env: writes the worktree's .env (see The worktree's .env).
  5. databases: clones the databases (and tenant databases), or uses the main checkout's with --db shared.
  6. storage: an empty storage/logs, the tenants' storage folders, the public/storage link.
  7. permissions: lets php-fpm's user write storage/ and bootstrap/cache/ (with ACLs when php-fpm runs as another user, as on Debian/Ubuntu; Sail needs none).
  8. container (Sail): starts the worktree's own app container (see Laravel Sail).
  9. composer: only composer dump-autoload when vendor/ was copied and composer.lock matches the main checkout's, else composer install. When PHP doesn't satisfy composer.lock, it offers to retry with --ignore-platform-reqs (set GROVE_COMPOSER_FLAGS to always use it).
  10. npm: installs the JavaScript dependencies (with npm, pnpm, yarn or bun) when no node_modules/ was copied or the lock file differs from the main checkout's; otherwise the copy is used as is.
  11. migrate: php artisan migrate --step, and tenants:migrate for stancl/tenancy.
  12. assets: the build command, npm run build by default (a failure only warns).
  13. web: the web server site (with Sail, a route in grove-router) and /etc/hosts entries for the worktree and its tenants.

With Sail, composer, npm, artisan and the build run inside the worktree's app container. It ends with a summary: the URLs with their HTTP status, the databases, and what to do next.

Commands

Most commands take -y/--yes (do not ask) and --offline (do not fetch). Where a command takes [name], it defaults to the worktree you are in; a branch name works too.

Setting up

| Command | What it does | | --- | --- | | grove init [--force] | Detects the app (Laravel; asks which database) and writes .grove.json, the repository's grove settings, to commit for the whole team. --force detects everything again. | | grove setup [--passwordless] [--check] | Installs the root helper and the web server include (asks for your sudo password), plus completions and grove cd for bash, zsh and fish; with Sail, starts grove-router instead of using a web server. Offers to install missing tools. --passwordless lets your user run only grove's root helper without a password; --check only reports. | | grove setup --user | Only the per-user parts, without sudo: the ~/.local/bin/grove link and the shell integration. | | grove config | Shows the effective settings and where they come from. | | grove config --https / --http | Serves the worktrees over HTTPS with mkcert certificates, or back over plain HTTP: saves GROVE_SCHEME, offers to install mkcert if needed, runs grove setup, and offers to update the worktrees' .env. | | grove config --runtime host\|sail\|auto | Runs new worktrees with php-fpm on this machine or with Laravel Sail, or lets grove detect it from .env (auto, the default): saves GROVE_RUNTIME, then runs grove setup. Existing worktrees keep the runtime they were made with. |

Worktrees

| Command | What it does | | --- | --- | | grove new <branch> [options] | Creates a worktree, or continues one that stopped halfway. | | grove list [--fetch] [--json] | Every worktree with its activity, uncommitted work, and push and merge state. | | grove info [name] | Everything about one worktree. | | grove rm [name] [options] | Removes a worktree and everything it owns: folder, databases, web server site (or Sail container and route), /etc/hosts entries, cache keys, and its branch when merged. | | grove stale [--days N] | Worktrees idle for N+ days (default 30), rated SAFE (nothing would be lost), UNMERGED (all pushed, not merged) or AT-RISK (work only on this machine), plus every worktree holding work that is not on the remote. | | grove prune [--days N] [--dry-run] | Removes the idle worktrees rated SAFE, after asking. | | grove doctor [--fix] | Checks the setup and every worktree, and finds databases, web server sites, Sail containers and routes, and git worktrees that no worktree owns; --fix offers to remove them. |

Remove worktrees with grove rm, not by deleting the folder: the folder is only part of a worktree.

Deleted a worktree folder by hand? git still counts its branch as checked out there, and its databases (MySQL/MariaDB), site, names and container are still there. grove notices and tells you:

  • grove new <branch> makes git forget a deleted folder that holds the branch, then creates the worktree as usual. For a grove worktree, it offers to rebuild the folder instead, keeping its MySQL databases (a SQLite copy lived in the folder, so it is copied again).
  • grove rm <name> removes whatever is left, as with any worktree.
  • grove list, grove info and grove doctor point out deleted folders, and commands that need the folder (cd, run, dev, test, env, web, db:reset, deps:reset) say how to fix it instead of failing. grove doctor --fix makes git forget deleted folders that grove did not make.

Options for grove new:

| Option | | | --- | --- | | --from <ref> | Base for a branch that does not exist yet (default <remote>/<baseBranch>; without it, grove asks: that, your current branch, or a branch, tag or commit you type, where a branch name means the remote's branch first). | | --name <name> | Folder and URL name (default: the branch, with / → -). | | --db clone\|shared | clone (default) copies the databases; shared uses the main checkout's. | | --db-from main\|<name> | Clone from the main checkout (default) or another worktree. | | --no-migrate, --no-build | Skip the migrations or the asset build. | | --fresh-deps | Install vendor/ and node_modules/ from the lock files instead of copying them. | | --no-web | No site, route or names (no URLs); add them later with grove web <name>. | | --open | Open the first tenant (or the app) in the browser when done. |

Options for grove rm:

| Option | | | --- | --- | | --force | Remove even with uncommitted work (backed up first) or processes running in the folder. | | --keep-branch, --delete-branch | By default the branch is deleted only when it is verified merged. | | --backup-db | Dump the databases before dropping them. |

Working in a worktree

| Command | What it does | | --- | --- | | grove cd [name] | Changes into it (needs the shell integration from grove setup). | | grove path [name] | Prints its folder. | | grove url [name] [tenant\|central] | Prints a URL; grove open opens it in the browser. | | grove run <name> -- <command…> | Runs a command in it, e.g. grove run feat-5000 -- php artisan tinker; with Sail, in its app container. | | grove dev [name] | npm run dev (Vite picks a free port per worktree; with Sail it runs in the container, on the port grove gave it). | | grove test [name] <file\|--filter=…\|--all> [args…] | php artisan test --compact, one worktree at a time, since they share the testing database; the whole suite only with --all. | | grove env [name] | Regenerates its .env from the main checkout's, keeping the "grove keep" block. | | grove db:reset [name] [--from main\|<name>] [--no-migrate] | Clones the databases again and migrates. | | grove deps:reset [name] [--fresh] | Replaces vendor/ and node_modules/ with clean copies. | | grove web [name] | (Re)creates its web server site (or route) and /etc/hosts entries; run it after adding tenants in it. With Sail it also re-creates its app container when it is missing or the main app runs a newer image (after sail build). |

Configuration

.grove.json

grove init reads the checkout once and proposes this file. It asks which database the app uses (the one in .env's DB_CONNECTION is preselected) and, when APP_URL is localhost, which domain the worktrees get. After that every command follows the file. Commit it, so your whole team gets the same settings. (The runtime is the one thing each machine decides for itself, from its own .env, unless the file pins it.)

{
  "profile": "laravel",
  "database": "mysql",                 // "mysql" (MySQL or MariaDB) or "sqlite"
  "tenancy": "none",                   // or (MySQL only) { "driver": "stancl", "prefixEnv": "TENANCY_TENANT_PREFIX", "defaultPrefix": "tenant-" }
  "domain": "my-app.test",             // optional: default is APP_HOST, else APP_URL's host, from .env
  "runtime": "sail",                   // optional: "host" or "sail" for everyone; default: each machine detects it
  "baseBranch": "main",                // new branches start from <remote>/<baseBranch>
  "packageManager": "npm",             // npm | pnpm | yarn | bun | none
  "build": "npm run build",            // "" for no asset build
  "copyIgnored": [".env.testing"],     // git-ignored files each worktree gets a copy of
  "skipDataTables": ["jobs", "cache"]  // tables cloned without their rows
}

Personal settings

Each developer can override .grove.json and set more in ~/.config/grove/config (KEY=value lines) or as GROVE_* environment variables; a value in the file wins over the environment variable. grove config shows the effective values.

| Setting | Default | | | --- | --- | --- | | GROVE_ROOT | <checkout>-worktrees, next to the checkout | Where worktrees are created. | | GROVE_DOMAIN | domain in .grove.json, else from .env | The domain worktrees get subdomains of. | | GROVE_SCHEME | http | https serves the worktrees with mkcert certificates (set it with grove config --https). | | GROVE_RUNTIME | detected | host (php-fpm) or sail; set it with grove config --runtime. | | GROVE_WEB | detected | nginx or apache; detected from the web server that serves the main checkout. | | GROVE_HTTP_PORT, GROVE_HTTPS_PORT | 80, 443 | Sail: the ports grove-router publishes; other ports end up in the URLs. | | GROVE_FPM | detected | php-fpm's address, unix:/path/to.sock or 127.0.0.1:9000. | | GROVE_FPM_USER | detected | The user php-fpm runs as, for storage/ permissions. | | GROVE_REMOTE | origin | The git remote. | | GROVE_BASE_REF | <remote>/<baseBranch> | Where new branches start. | | GROVE_BUILD_CMD | build in .grove.json | The asset build command. | | GROVE_COMPOSER_FLAGS | none | Extra composer install flags, e.g. --ignore-platform-reqs. | | GROVE_COPY_IGNORED | copyIgnored in .grove.json | Space-separated. | | GROVE_SKIP_DATA_TABLES | skipDataTables in .grove.json | Space-separated. | | GROVE_MIN_FREE_GB | 5 | Free disk space grove new keeps, on top of what the worktree needs. | | GROVE_STALE_DAYS | 30 | The idle threshold for grove stale and grove prune. | | GROVE_MAIN | | Environment only: which checkout to use when run outside one. |

The worktree's .env

grove new and grove env write the worktree's .env from the main checkout's, changing only what must differ per worktree:

  • APP_URL (and APP_HOST, if your app uses it) → the worktree's URL
  • DB_DATABASE → its own database, and stancl's tenant prefix variable → its own tenant prefix
  • CACHE_PREFIX, SESSION_COOKIE and HORIZON_PREFIX → its own, so worktrees do not share cache entries or log each other out
  • SESSION_DOMAIN and SANCTUM_STATEFUL_DOMAINS → with the worktree's domain
  • LOG_CHANNEL=daily

Settings of your own go between the "grove keep" markers at the end of the file; they survive grove env and win over everything above them:

# >>> grove keep: per-worktree settings >>>
MAIL_HOST=mailpit
# <<< grove keep <<<

Runtimes: php-fpm or Laravel Sail

grove runs a worktree the way the main checkout runs:

  • host: php-fpm on this machine, behind nginx or Apache. grove adds a site per worktree to the web server, and runs composer, npm and artisan on the machine.
  • sail: Laravel Sail's Docker Compose stack. Each worktree gets its own app container, and grove-router routes the names.

By default each machine detects it from its own .env: Sail when the app has Laravel Sail and a Compose file, and DB_HOST (or REDIS_HOST) names one of its services, as Sail's .env does (DB_HOST=mysql). So teammates on Sail and on php-fpm can share one .grove.json. grove config shows what was detected and why; grove config --runtime sail (or host) decides for your machine, and "runtime" in .grove.json for everyone. Each worktree keeps the runtime it was made with.

Laravel Sail

Each worktree gets the same isolation as with php-fpm: its own folder, URLs, databases, vendor/, node_modules/, .env, cache prefix and logs. What changes is where PHP runs:

  • An app container per worktree. grove new starts grove-<repo>-<name>, a copy of the main checkout's app container (laravel.test, or APP_SERVICE): the same image, environment, extra hosts, networks and mounts, but with the worktree's folder mounted at /var/www/html. It shares the main checkout's MySQL, Redis, Mailpit and other services, and restarts with Docker. composer, npm, artisan, grove run, grove test and grove dev run inside it, as Sail's sail user, so files stay yours.
  • grove-router is one small nginx container for the whole machine (nginx:stable-alpine) that publishes ports 80 (and 443 with HTTPS) on 127.0.0.1 and sends each name to its container: feat-123.my-app.test and *.feat-123.my-app.test to the worktree's, the main checkout's domain (and its APP_URL host, such as localhost) to the main app container. Its configuration lives in ~/.local/state/grove/router/, so routes change without root.
  • Port 80: Sail's app publishes it itself by default (APP_PORT=80). grove setup offers to move the main app to another port (it sets APP_PORT in the main .env and restarts the app container); the main app stays reachable at its usual names through grove-router. If you would rather keep port 80 for something else, set GROVE_HTTP_PORT (and GROVE_HTTPS_PORT) and the worktree URLs include that port.
  • Databases: grove clones them inside Sail's database container with its own mysqldump, connecting as root (Sail sets the root password from DB_PASSWORD), and lets Sail's database user (DB_USERNAME) use the grove_… databases. .env keeps DB_HOST=mysql; only DB_DATABASE changes, as before.
  • Vite: each worktree's container publishes its own port (5174 and up) and sets VITE_PORT, which laravel-vite-plugin listens on under Sail; grove dev prints it.
  • After sail build, run grove web <name> to move a worktree to the new image.
  • HTTPS: grove-router serves the mkcert certificates and hands requests to the containers as plain HTTP with X-Forwarded-Proto: https, so the app must trust proxies to see HTTPS (Laravel 11+: $middleware->trustProxies(at: '*') in bootstrap/app.php).

Sail must be running in the main checkout (./vendor/bin/sail up -d) for grove setup, grove new and anything that touches the databases. sail down leaves the worktree containers and grove-router running (Compose keeps its network while they use it); the worktrees answer again once Sail is back.

macOS

grove works on macOS with either runtime:

  • Sail needs only Docker (Docker Desktop, OrbStack or Colima), Node, git and brew install flock.
  • php-fpm uses Homebrew's nginx (its servers/ folder, so Laravel Valet's nginx works too) or Apache (Homebrew's httpd, or the one macOS comes with), and Homebrew's php-fpm (127.0.0.1:9000) or Valet's socket. The worktree sites live in /etc/grove/sites/<repo>/ (nginx) or /etc/apache2/grove/<repo>/ (Apache). Homebrew's nginx and httpd often run as you; if so, run them as root (sudo brew services start nginx, as Valet does), since grove's root helper tests and reloads their configuration as root. With HTTPS, the certificates belong to you, so a web server running as you can read them.
  • vendor/ and node_modules/ are APFS clones (cp -c), instant and sharing disk space, when the worktrees are on the same volume as the checkout.
  • mkcert puts its CA in the System keychain; /etc/hosts changes flush macOS's name cache.
  • The shell integration goes in ~/.zshrc (or ~/.bash_profile for bash), and works with macOS's bash 3.2.

Web server, HTTPS and names

nginx or Apache

grove serves the worktrees with the web server that serves your main checkout (nginx if it cannot tell); set GROVE_WEB=nginx or GROVE_WEB=apache to choose, then run grove setup. Each worktree gets a site for its domain and *. its domain, with PHP going to php-fpm.

With Apache, PHP goes to php-fpm through mod_proxy_fcgi (mod_php is not supported) and Laravel's public/.htaccess does the routing. grove setup turns on the modules the sites need (proxy, proxy_fcgi, rewrite, and ssl for HTTPS): with a2enmod on Debian/Ubuntu and openSUSE, by uncommenting their LoadModule lines in httpd.conf on Arch and macOS, and on Fedora/RHEL by offering to install mod_ssl.

HTTPS

Worktrees are served over plain HTTP until you run:

grove config --https   # and `grove config --http` to go back

Then each worktree gets a certificate from mkcert for its domain and its tenant subdomains (feat-123.my-app.test and *.feat-123.my-app.test), the web server (or grove-router) serves it on port 443, plain HTTP redirects to HTTPS, and APP_URL starts with https://.

grove config --https offers to install mkcert when it is missing (and certutil, which Firefox needs), saves GROVE_SCHEME=https, then runs grove setup. That runs mkcert -install, so your system and browsers trust mkcert's local CA, renews the sites of the worktrees you already have, and offers to update their .env with the new URLs. Run it again to repair the setup, or with --http to switch back. mkcert runs as you and its CA stays in your home folder; the root helper only receives each worktree's certificate, which it keeps readable by root only in /etc/grove/certs/ (with Sail, grove-router reads them from ~/.local/state/grove/router/certs/, readable by you only).

Worktree names

grove puts each worktree's names in /etc/hosts (feat-123.my-app.test, and one per tenant, like acme.feat-123.my-app.test) through its root helper, so they resolve on any Linux or macOS system without a DNS server. /etc/hosts has no wildcards, so a tenant you create inside a worktree later gets its name with grove web <name>. The main checkout's own names (my-app.test and its tenants) are yours to set up, as before grove.

sudo and --passwordless

Writing web server sites and /etc/hosts needs root, which grove gets through a small root helper, /usr/local/sbin/grove-helper. It is a root-owned bash script that does only four things (add or remove a worktree's site, set or remove its /etc/hosts block), checks every argument against a strict pattern, runs with a fixed PATH, and takes the domain, folders and php-fpm address from the root-owned /etc/grove/<repo>.conf that grove setup writes, never from the caller. With Sail it only manages /etc/hosts.

Without --passwordless, sudo asks for your password whenever grove needs the helper (grove new, grove web, grove rm). grove setup --passwordless adds /etc/sudoers.d/grove-<you>, which lets your user run only that helper without a password; use it on your own machine, and especially when grove runs without a terminal (scripts, editors, agents). It is per user, covers every repository, and stays until you remove it.

Databases and tenancy

  • MySQL/MariaDB: each worktree gets grove_<name>, plus grove_<name>__<tenant-id> per tenant, cloned with mysqldump using the main checkout's database credentials from .env. The database user needs to create and drop grove\_% databases; grove checks its privileges before creating anything. Tables in skipDataTables are cloned without their rows. With Sail, all of this happens inside the database container (see Laravel Sail).
  • SQLite: a copy of the database file at the same place inside the worktree, made with VACUUM INTO, so it is consistent even while the app writes in WAL mode.
  • stancl/tenancy (MySQL): tenant databases are cloned and renamed into the worktree's namespace, new tenants created inside a worktree get databases there too (through the prefix variable), and tenants are reached at <tenant>.<worktree>.<domain>, from the subdomain labels in the domains table.
  • --db shared: the worktree uses the main checkout's databases, and grove runs no migrations.

Where things live

  • Worktrees: GROVE_ROOT, by default <checkout>-worktrees next to the checkout.
  • State: ~/.local/state/grove/<repo>/: a manifest per worktree in worktrees/, logs in logs/, backups of uncommitted work and dumped databases in backups/.
  • Personal settings: ~/.config/grove/config.
  • Sail: the containers grove-<repo>-<name> (their environment in ~/.local/state/grove/<repo>/containers/) and grove-router, whose routes and certificates are in ~/.local/state/grove/router/.
  • Shell integration: ~/.local/bin/grove, ~/.local/share/grove/ (sourced from ~/.bashrc and ~/.zshrc, or ~/.bash_profile on macOS) and ~/.config/fish/{functions,completions}/grove.fish.
  • Root side: /usr/local/sbin/grove-helper, /etc/grove/<repo>.conf, /etc/grove/certs/<repo>/, the sites in /etc/nginx/grove/<repo>/ (or /etc/grove/sites/<repo>/ on macOS, /etc/apache2/grove/<repo>/, /etc/httpd/grove/<repo>/), a grove-<repo>.conf include in the web server's site folder, the grove:<repo>:<name> blocks in /etc/hosts, and /etc/sudoers.d/grove-<you> with --passwordless.

Limitations

  • Linux or macOS, with nginx or Apache and php-fpm, or with Laravel Sail: no mod_php, Caddy, Herd, WSL or Docker setups other than Sail (Valet works as Homebrew nginx with php-fpm).
  • MySQL/MariaDB or SQLite (SQLite with php-fpm only): no PostgreSQL or SQL Server.
  • Tenancy means stancl/tenancy with tenants identified by subdomain; tenants on their own domains are not mirrored.
  • Worktrees still share some services with the main checkout: Redis (with Redis queues, the main checkout's worker may pick up a worktree's jobs), queues and schedulers in general, S3 buckets, mail, search indexes, websockets, secondary database connections and the testing database. If your .env sets DB_URL, it overrides DB_DATABASE, so the worktree would use the main database: unset it.

Uninstall

Remove the worktrees first (grove rm <name>, or grove prune), then grove's files (and with Sail, docker rm -f grove-router):

repo=my-app   # the repository's folder name
sudo rm -rf /usr/local/sbin/grove-helper /etc/grove/$repo.conf /etc/grove/certs/$repo /etc/sudoers.d/grove-$USER \
  /etc/nginx/grove/$repo /etc/nginx/sites-enabled/grove-$repo.conf /etc/nginx/conf.d/grove-$repo.conf
sudo systemctl reload nginx   # with Apache: the same files under /etc/apache2 or /etc/httpd, then reload it
rm -rf ~/.local/bin/grove ~/.local/share/grove ~/.config/fish/functions/grove.fish \
  ~/.config/fish/completions/grove.fish ~/.local/state/grove ~/.config/grove
npm rm -g @dileep-ct/grove

Also delete the # grove shell integration line from ~/.bashrc and ~/.zshrc.

Layout

  • src/: the CLI (TypeScript, no runtime dependencies; it drives git, mysql, cp, composer, npm and docker)
  • libexec/grove-helper: the only part that runs as root (through sudo). It stays a small bash script on purpose: a root-owned /bin/bash script is safer to allow in sudoers than a Node program, whose interpreter may live in a user-writable folder such as ~/.nvm.
  • shell/: grove cd and completions for fish, bash and zsh, copied into place by grove setup

Development

From a clone, npm ci installs and builds, and npm install -g . points grove at the clone.

npm run typecheck
npm test          # Node 22.18+ (runs the TypeScript tests directly)
npm run build

To release, run npm version patch (or minor/major), npm publish (it asks for your 2FA code) and git push --follow-tags.

License

MIT