Search by

aenzenith / laravel-dev-mesh

aenzenith

Run Horizon, the scheduler and other long-running artisan services of several Laravel projects from one live terminal dashboard.

Package info

github.com/aenzenith/laravel-dev-mesh

pkg:composer/aenzenith/laravel-dev-mesh

Statistics

Installs: 114

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v2.0.0 2026-10-27 18:25 UTC

This package is auto-updated.

Last update: 2026-10-27 18:25:28 UTC


README

Packagist Version Total Downloads PHP Version Laravel Tests License

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:work keep the application in memory; when a file in app, 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 with s.
  • Isolated environments: every service starts with env -i, so the variables loaded from the dashboard application's .env never 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 pcntl and posix
  • Laravel 12 or 13
  • macOS or Linux (stty and ps are 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.