synxs-ar / laravel-worktrees
Persistent, isolated Laravel dev environments ("desks") backed by git worktrees — dynamic free-port resolution for PHP + Vite, per-desk SQLite, unique app keys and storage links.
Requires
- php: ^8.1
- illuminate/console: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
- symfony/process: ^6.4|^7.0|^8.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0|^11.0
README
Corré varias copias de tu app Laravel al mismo tiempo, cada una aislada, sin que se pisen.
Cada copia se llama desk (wt-desk-01, wt-desk-02, …). Pensalo como un
escritorio de trabajo: tiene su propio puerto, base de datos, storage,
vendor/, node_modules/ y APP_KEY. Trabajás en una rama en un desk, en otra
rama en otro desk, y ninguna toca a la otra.
Ideal para trabajar en varias features a la vez — o para que varios agentes de IA programen en paralelo sin chocarse.
En 30 segundos
php artisan wt:new # crea y deja TODO listo (te hace unas preguntas) php artisan wt:up 1 # lo prende (PHP + Vite). No pregunta nada.
Eso es todo. wt:new arma el desk completo. wt:up solo lo enciende.
Instalar
composer require --dev synxs-ar/laravel-worktrees
(Opcional) publicar la config si querés tocar valores por defecto:
php artisan vendor:publish --tag=worktrees-config
No hace falta tocar el
.gitignore: el primerwt:newagrega/worktreesy/.wt-deskssolo.
Necesitás
- PHP
^8.1 - Laravel
10,11,12o13 gitinstalado- Para una base aislada: un servidor PostgreSQL (por defecto) o MySQL andando
Los comandos
| Comando | Qué hace |
|---|---|
wt:new [label] |
Crea el desk y lo configura entero: worktree, dependencias, .env, base de datos, storage y puerto. Queda listo. |
wt:up {desk} |
Solo lo prende (PHP + Vite). No pregunta nada. |
wt:label {desk} [label] |
Ponerle, ver o sacarle un nombre al desk. |
wt:list |
Lista todos los desks y sus puertos. |
wt:rm {desk} |
Borra un desk y libera su número. |
{desk}acepta el nombre completo o solo el número:wt:up wt-desk-01ywt:up 1son lo mismo.
wt:new — crear y configurar
Primero arma el desk:
✓ Ajustando .gitignore
✓ Creando git worktree
✓ Instalando dependencias (composer)
✓ Instalando node modules (npm)
✓ Preparando .env
✓ Copiando config local (vite, …)
✓ Generando APP_KEY (único por desk — sesiones y encriptación aisladas)
Y ahí mismo te pregunta 3 cosas y las recuerda:
1. ¿Base de datos aislada o compartida?
- Aislada → te pide host / puerto / usuario / contraseña / nombre.
Si la base no existe, elegís:
- crearla vacía (le corre tus migraciones), o
- copiar una existente (te muestra las que hay y clona esquema + datos).
- Compartida → usa la base de tu proyecto. Nunca le corre migraciones.
2. ¿Storage aislado o compartido?
- Aislado → el desk tiene su propio
storage/. - Compartido → comparte los archivos subidos (
storage/app/public) con tu proyecto principal. Logs, cache y sesiones siguen separados.
3. ¿Qué puerto para PHP?
- Vacío (auto) → se busca un puerto libre solo, cada vez que prendés.
- Un número fijo (ej:
8080) → siempre usa ese. Si está ocupado, no arranca y te avisa (elegiste ese a propósito, no te lo cambio a escondidas).
Cuando termina, el desk está listo. No queda nada para decidir después.
El worktree queda detached (sin rama fija). Adentro hacés checkout de la rama que quieras — la identidad del desk (puerto, base, storage) no se mueve.
wt:up — prender
No pregunta nada. Lee lo que guardó wt:new y levanta el server:
PHP http://127.0.0.1:18001
Vite http://127.0.0.1:27501
wt-desk-01 is up. Ctrl+C to stop.
Ctrl+C corta todo limpio (PHP y Vite), sin dejar procesos colgados.
--no-vite→ prende solo PHP, sin Vite.
Ponerle un nombre (label)
Por defecto el desk se muestra como MiApp [wt-desk-01] en el navegador, logs y
mails. Ponele un nombre para reconocerlo de un vistazo:
php artisan wt:new checkout-redesign # nace como "MiApp [checkout-redesign]" php artisan wt:label 1 billing-fix # renombrar cuando quieras php artisan wt:label 1 # ver el nombre actual php artisan wt:label 1 --clear # volver a "MiApp [wt-desk-01]"
Cosas para saber
Copiar una base
- Postgres: la base que copiás no puede tener conexiones abiertas (cerrá lo que la esté usando y reintentá).
- MySQL: copia tablas y datos, pero no copia vistas, triggers ni stored procedures.
Puertos
- Los puertos automáticos siempre quedan arriba de
10001. En Windows con Hyper-V / WSL2 / Docker los rangos por debajo de 10000 están reservados y tiran error10013. - El puerto de Vite siempre es automático.
Si algo falla en wt:new
Si cancelás o hay un error (server caído, credenciales mal), wt:new deshace
todo y libera el número. No te deja desks a medio hacer. Arreglás el problema y
volvés a correr wt:new.
Config (config/worktrees.php)
| Clave | Env | Default | Para qué |
|---|---|---|---|
host |
WORKTREES_HOST |
127.0.0.1 |
Dónde se sirve. |
port_floor |
WORKTREES_PORT_FLOOR |
10001 |
Piso mínimo de puertos automáticos. |
php_base_port |
WORKTREES_PHP_BASE_PORT |
18000 |
Puerto PHP preferido = base + número. |
vite_base_port |
WORKTREES_VITE_BASE_PORT |
27500 |
Puerto Vite preferido = base + número. |
database.engine |
WORKTREES_DB_ENGINE |
pgsql |
Motor para base aislada (pgsql / mysql). |
database.name |
WORKTREES_DB_NAME |
{base}_{slug} |
Plantilla del nombre de base aislada. |
registry_path |
— | .wt-desks/registry.json |
Dónde recuerda los desks (fuera de git). |
worktrees_path |
— | worktrees |
Dónde se crean los desks. |
php / composer / npm |
WORKTREES_PHP / …COMPOSER / …NPM |
php / composer / npm |
Binarios que se usan. |
¿Por qué Postgres por defecto? Tiene soporte completo de
ALTER TABLE, así que tus migraciones corren sin cambios. SQLite tieneALTER TABLElimitado y rompe muchas suites de migraciones reales.
Notas de Windows
Anda de una en Windows:
storage:linkusa una junction (mklink /J) si el symlink necesita permisos que no tiene.wt:rmborra las junctions como links (sin seguirlas hacia el destino) y maneja rutas denode_modulesque pasan elMAX_PATH.Ctrl+Ccorta todo el árbol de procesos (taskkill /T).
Licencia
MIT © Synxs