laravel-dev-mesh maintained by aenzenith
Laravel Dev Mesh
Run Horizon, the scheduler and any other long-running artisan service of one or more Laravel projects from a single command, with a live terminal dashboard.
Shop mesh · 17:42:10 · uptime 01:12:33 ↑↓←→ select · r restart · s stop/start · q quit
┌──────────────┬───────────────────────────────┬────────────────────────┬────────┬──────────┬──────────┬──────────────────────┬──────────────────────┐
│ Project │ horizon │ schedule │ ✓ Done │ ✕ Failed │ Jobs/min │ Last job │ Last error │
├──────────────┼───────────────────────────────┼────────────────────────┼────────┼──────────┼──────────┼──────────────────────┼──────────────────────┤
│ shop │ ● 01:12:33 84211 142 MB │ ● 01:12:33 84212 38 MB │ 142 │ 2 │ 1.4 │ 17:42:08 SyncUsers… ✓│ 17:30:12 SendSmsJob │
│ payments-api │ ✕ exit 255 · 4 s ↻3 · 17:41 │ ● 00:01:01 84390 36 MB │ 42 │ 7 │ 0.6 │ 17:42:05 DeliverWe… ✕│ 17:42:05 stopped (e… │
└──────────────┴───────────────────────────────┴────────────────────────┴────────┴──────────┴──────────┴──────────────────────┴──────────────────────┘
Recent activity
17:42:08 shop horizon App\Jobs\SyncUsersJob .............................................. 120.34ms DONE
17:42:05 payments-api horizon App\Jobs\DeliverWebhookJob ............................................. 1.02s FAIL
17:42:00 shop schedule reports:rollup ............................................................ 1s DONE
- One row per project, one column per service: state, uptime, PID, memory (a Horizon master is summed with its supervisors and workers) and restart history.
- Project totals: finished and failed jobs, jobs per minute over the last
five minutes, the latest job and the latest error, parsed from the output of
queue workers and
schedule:work. - Self-healing: a crashed service is restarted after 1, 2, 4 … seconds.
- Restart on code changes: Horizon and
queue:workkeep the application in memory; when a file inapp,config,routes… changes, the project's services are restarted so they run the new code. - Keyboard controls: select a cell with the arrow keys, restart its
service with
r, stop or start it withs. - Isolated environments: every service starts with
env -i, so the variables loaded from the dashboard application's.envnever leak into a sibling project. - Plain output without a TTY: piped or in CI, lines are printed as
[project|service] line.
Requirements
- PHP 8.2+ with
pcntlandposix - Laravel 12 or 13
- macOS or Linux (
sttyandpsare used)
Windows
Native Windows is not supported. Horizon itself needs pcntl and posix,
which PHP does not provide on Windows, and so does this package: Composer
refuses to install it there. The dashboard also relies on POSIX tools and
signals (env -i, stty, ps, SIGTERM).
Use WSL 2 instead: install
PHP with pcntl and posix inside the Linux distribution, keep the projects
on the Linux file system (e.g. ~/code, not /mnt/c) and run the command
from a WSL terminal.
Installation
composer require --dev aenzenith/laravel-dev-mesh
php artisan vendor:publish --tag=dev-mesh-config
Configuration
config/dev-mesh.php:
return [
'command' => env('DEV_MESH_COMMAND', 'dev-mesh'),
'title' => null, // defaults to the application name
// label => absolute path; empty runs the current application only
'projects' => [
'shop' => base_path(),
'payments-api' => base_path('../payments-api'),
],
// column => command, started in every project
'services' => [
'horizon' => ['php', 'artisan', 'horizon'],
'schedule' => ['php', 'artisan', 'schedule:work'],
],
// restart a project's services when its code changes; [] paths turns it off
'watch' => [
'paths' => ['app', 'config', 'lang', 'routes', 'bootstrap/app.php',
'bootstrap/providers.php', '.env', 'composer.lock'],
'except' => [], // services left running, e.g. ['schedule']
],
// variables passed through `env -i`
'environment' => [
'keep' => ['HOME', 'PATH', 'USER', 'SHELL', 'TMPDIR', 'TERM', 'LANG'],
'keep_prefixes' => ['HERD_', 'NVM_'],
],
'max_restart_delay' => 30,
];
Keep the HERD_ prefix when you use Laravel Herd: its PHP reads the ini that
holds the certificate authority for .test TLS from those variables.
Usage
php artisan dev-mesh
Set DEV_MESH_COMMAND (or command in the config) to use another name, e.g.
dev.
Keys
| Key | Action |
|---|---|
↑ ↓ / k j |
select a project |
← → / h l |
select a service |
r |
restart the selected service (starts it if it is down) |
s |
stop the selected service, or start it again |
q / Ctrl+C |
stop every service and quit |
A restart is graceful: the service gets SIGTERM and is killed only if it is
still running after 15 seconds, so Horizon finishes the jobs it is working on.
A service stopped with s stays down (no crash restarts, no reloads) until
you start it again.
Restart on code changes
Horizon and queue:work boot the application once and keep it in memory, so
an edited job goes on running its old code. The dashboard polls every project
once a second for changes in watch.paths and restarts the project's
services once the files settle, so a branch checkout restarts them once, not
per file. Directories are scanned for .php files only (a SQLite database or
a log next to the code never triggers a restart); files listed by name, such
as .env, are watched whatever they contain. A crashed service waiting for
its next retry starts at once, so fixing the error brings it back
immediately.
schedule:work starts a fresh schedule:run every minute, but it does keep
the variables of the .env it booted with and hands them to every run, so it
is restarted too. Put a service in watch.except to leave it running, and
set watch.paths to [] to turn watching off.
Translations
The dashboard follows the application locale and ships with 19 languages: English, Turkish, German, French, Spanish, Portuguese (Brazil), Italian, Dutch, Polish, Russian, Ukrainian, Chinese (Simplified and Traditional), Japanese, Korean, Arabic, Hindi, Indonesian and Vietnamese. Publish the language files to change or add one:
php artisan vendor:publish --tag=dev-mesh-lang
Testing
composer test # Pest
composer format # Pint
composer analyse # PHPStan
Contributing
See CONTRIBUTING.md. Commits follow Conventional Commits; releases and the changelog are generated from them.
License
MIT. See LICENSE.