laracrew 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +81 -2
- package/README.md +638 -598
- package/dist/index.js +154 -35
- package/dist/index.js.map +1 -1
- package/examples/README.md +7 -1
- package/examples/django-celery/stack.yaml +5 -0
- package/examples/node-api-and-web/stack.yaml +5 -2
- package/examples/polyglot-microservices/stack.yaml +4 -2
- package/package.json +63 -55
package/README.md
CHANGED
|
@@ -1,598 +1,638 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
|
|
3
|
-
# laracrew
|
|
4
|
-
|
|
5
|
-
**Boot every long-running process of every Laravel project you're working on — with one command.**
|
|
6
|
-
|
|
7
|
-
`php artisan serve` · `queue:work` · `horizon` · Redis stream listeners · `schedule:work` · `npm run dev`
|
|
8
|
-
— across two, three or ten projects, in the right order, supervised, in one terminal.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
cd D:/work/api && php artisan
|
|
23
|
-
cd D:/work/api && php artisan
|
|
24
|
-
cd D:/work/api &&
|
|
25
|
-
cd D:/work/
|
|
26
|
-
cd D:/work/
|
|
27
|
-
cd D:/work/portal && php artisan
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
├─
|
|
55
|
-
├─ [
|
|
56
|
-
├─ [
|
|
57
|
-
├─ [
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
└─
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
08:51:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
- **It
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
- **It
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
laracrew
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
dual --only workers
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
`
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
|
301
|
-
|
|
302
|
-
| `
|
|
303
|
-
|
|
|
304
|
-
| `
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
`
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
`
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
├──
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
ready: {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
|
430
|
-
|
|
431
|
-
| `
|
|
432
|
-
| `
|
|
433
|
-
| `
|
|
434
|
-
| `
|
|
435
|
-
| `
|
|
436
|
-
| `
|
|
437
|
-
| `
|
|
438
|
-
| `
|
|
439
|
-
| `
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
|
444
|
-
|
|
445
|
-
|
|
|
446
|
-
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
laracrew --
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
laracrew
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
`
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# laracrew
|
|
4
|
+
|
|
5
|
+
**Boot every long-running process of every Laravel project you're working on — with one command.**
|
|
6
|
+
|
|
7
|
+
`php artisan serve` · `queue:work` · `horizon` · Redis stream listeners · `schedule:work` · `npm run dev`
|
|
8
|
+
— across two, three or ten projects, in the right order, supervised, in one terminal.
|
|
9
|
+
|
|
10
|
+
[github.com/vidux/laracrew](https://github.com/vidux/laracrew)
|
|
11
|
+
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## The problem
|
|
17
|
+
|
|
18
|
+
You develop two Laravel apps that talk to each other. Starting work means opening eight to
|
|
19
|
+
fourteen terminal tabs and typing, in the right order:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
cd D:/work/api && php artisan serve
|
|
23
|
+
cd D:/work/api && php artisan queue:work redis --queue=high,default
|
|
24
|
+
cd D:/work/api && php artisan streams:listen orders
|
|
25
|
+
cd D:/work/api && php artisan schedule:work
|
|
26
|
+
cd D:/work/api && npm run dev
|
|
27
|
+
cd D:/work/portal && php artisan serve --port=8001
|
|
28
|
+
cd D:/work/portal && php artisan queue:work redis --queue=default
|
|
29
|
+
cd D:/work/portal && php artisan streams:listen inventory
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Every one is long-running. When a worker dies you don't notice. When you edit a job class you
|
|
33
|
+
have to remember PHP workers cache code. When you close the terminal, half of them survive as
|
|
34
|
+
orphans still holding port 8000.
|
|
35
|
+
|
|
36
|
+
## The fix
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
laracrew up dual
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
laracrew · dual up 00:15:27 · 6/7 running all healthy
|
|
44
|
+
────────────────────────────────────────────────────────────────────────────────────────────
|
|
45
|
+
up/down select enter inspect log 0-9 jump r restart s stop/start a all logs
|
|
46
|
+
? help q quit
|
|
47
|
+
STACK DETAILS (process tree)
|
|
48
|
+
|
|
49
|
+
DEPENDENT SERVICES (checked, not managed)
|
|
50
|
+
● redis ready tcp 127.0.0.1:6379
|
|
51
|
+
|
|
52
|
+
processes (7 managed)
|
|
53
|
+
│
|
|
54
|
+
├─ ▸ [0] ● api:serve running 15m 04s
|
|
55
|
+
├─ [1] ● api:queue running 15m 04s ⟳2
|
|
56
|
+
├─ [2] ● api:streams running 15m 04s
|
|
57
|
+
├─ [3] ● api:schedule running 15m 04s
|
|
58
|
+
├─ [4] ● portal:serve running 14m 58s
|
|
59
|
+
├─ [5] ◼ portal:queue stopped
|
|
60
|
+
└─ [6] ● portal:streams running 14m 58s
|
|
61
|
+
│
|
|
62
|
+
└─ Ready in 1.9s. Awaiting keyboard input…
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Services marked `external: true` — Redis, Postgres, anything laracrew checks but never runs —
|
|
66
|
+
sit above the tree under their own heading, with the probe they are checked with. They carry no
|
|
67
|
+
index, because there is nothing to start or stop; if one is unreachable the header says
|
|
68
|
+
`1 dependency down` before anything else.
|
|
69
|
+
|
|
70
|
+
**The default screen never streams logs.** Nine services interleaving output is unreadable — you
|
|
71
|
+
can't see the shape of the fleet and you can't follow any one process. So you get the tree, and
|
|
72
|
+
you open logs deliberately: press `2`, read `api:queue`, press `esc`.
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
api:queue running · pid 14184 · up 15m 04s
|
|
76
|
+
$ php artisan queue:work redis --queue=high,default
|
|
77
|
+
D:/work/api
|
|
78
|
+
────────────────────────────────────────────────────────────────────────────────────────────
|
|
79
|
+
08:51:30 | Processing: App\Jobs\SyncOrder
|
|
80
|
+
08:51:30 | Processed: App\Jobs\SyncOrder (412ms)
|
|
81
|
+
08:51:31 | Processing: App\Jobs\NotifyPortal
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
One Ctrl-C stops all of it, in reverse dependency order, gracefully — workers finish the job
|
|
85
|
+
they're holding before they exit, and nothing is left behind.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Why not `concurrently`, `pm2` or `docker compose`
|
|
90
|
+
|
|
91
|
+
Those run processes. laracrew knows what the processes *are*.
|
|
92
|
+
|
|
93
|
+
- **It stops workers on their own terms.** `php artisan queue:restart` for Laravel,
|
|
94
|
+
`celery control shutdown` for Celery, any command you name — run first, wait for the process to
|
|
95
|
+
finish the job it is holding, *then* terminate. Never a job killed mid-flight.
|
|
96
|
+
- **It kills whole process trees.** `php artisan serve` spawns a child PHP server; `npm run dev`
|
|
97
|
+
spawns Vite. Killing the parent orphans them and the port stays bound. Every stop is a tree kill
|
|
98
|
+
(`taskkill /T /F` on Windows, process-group kill on POSIX).
|
|
99
|
+
- **It gates on readiness, not on sleep.** `portal:serve` doesn't start until Redis answers *and*
|
|
100
|
+
`api:serve` returns HTTP 200.
|
|
101
|
+
- **It knows your `.env`.** `laracrew doctor` catches two projects quietly sharing one Redis
|
|
102
|
+
database *and* a queue name — where each app's workers silently steal the other's jobs. That
|
|
103
|
+
class of bug eats afternoons.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Install
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
npm install -g laracrew
|
|
111
|
+
laracrew init --examples
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Or from source:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
git clone https://github.com/vidux/laracrew && cd laracrew
|
|
118
|
+
npm install && npm run build && npm link
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Requires **Node 20+**. Works on Windows, macOS and Linux. PHP is only needed for the projects
|
|
122
|
+
laracrew runs, not for laracrew itself — and only if those projects are PHP.
|
|
123
|
+
|
|
124
|
+
## Quick start
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
laracrew init --examples # creates ~/.laracrew with a demo stack and a two-project template
|
|
128
|
+
laracrew up example # runs the demo — no Laravel project needed, proves it works here
|
|
129
|
+
laracrew ls # what's defined
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`laracrew init` on its own creates just the config files. Add `--examples` when you want the
|
|
133
|
+
runnable demo stack and the ready-made two-project template to start from.
|
|
134
|
+
|
|
135
|
+
Then point it at real projects. Edit `~/.laracrew/projects.yaml`:
|
|
136
|
+
|
|
137
|
+
```yaml
|
|
138
|
+
projects:
|
|
139
|
+
api:
|
|
140
|
+
path: D:/work/api
|
|
141
|
+
php: php # or an absolute path to a specific PHP build
|
|
142
|
+
envFile: .env
|
|
143
|
+
color: cyan
|
|
144
|
+
portal:
|
|
145
|
+
path: D:/work/portal
|
|
146
|
+
php: php
|
|
147
|
+
color: magenta
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`laracrew init` already wrote you a `dual` stack wired for exactly this scenario. Check it, then
|
|
151
|
+
boot it:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
laracrew doctor dual
|
|
155
|
+
laracrew up dual
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## One command per project set
|
|
161
|
+
|
|
162
|
+
Typing `laracrew up dual` every morning gets old, and you have more than one project set. Give
|
|
163
|
+
each set its own global command:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
laracrew link dual
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
created dual -> laracrew up dual
|
|
171
|
+
|
|
172
|
+
in C:\Users\you\AppData\Roaming\npm
|
|
173
|
+
|
|
174
|
+
Run it from anywhere: dual
|
|
175
|
+
Flags pass straight through: dual --only workers
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
From then on, one word boots that whole set — from any directory:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
dual # boots all 8 services of the dual stack
|
|
182
|
+
dual --only workers # every `laracrew up` flag still works
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
A stack names its own command in `stack.yaml`:
|
|
186
|
+
|
|
187
|
+
```yaml
|
|
188
|
+
name: dual
|
|
189
|
+
command: dual # what `laracrew link` installs; defaults to the stack name
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
So you end up with one command per set — `dual`, `billing`, `legacy` — each booting its own
|
|
193
|
+
fleet of projects.
|
|
194
|
+
|
|
195
|
+
| | |
|
|
196
|
+
|---|---|
|
|
197
|
+
| `laracrew link <stack>` | Install the command. Re-run any time to update it. |
|
|
198
|
+
| `laracrew link <stack> --as <name>` | Use a different name than the stack declares. |
|
|
199
|
+
| `laracrew link --all` | Install for every stack that declares `command:`. |
|
|
200
|
+
| `laracrew link <stack> --dir <path>` | Install somewhere other than the default. |
|
|
201
|
+
| `laracrew unlink <name>` | Remove it again. |
|
|
202
|
+
| `laracrew ls` | Shows which stacks have a command installed. |
|
|
203
|
+
|
|
204
|
+
**Where they go.** Into the same directory as `laracrew` itself — the npm global bin, which is
|
|
205
|
+
already on your PATH. On Windows you get three files (`dual`, `dual.cmd`, `dual.ps1`) so the
|
|
206
|
+
command behaves identically in Git Bash, cmd and PowerShell. Override with `--dir` or the
|
|
207
|
+
`LARACREW_BIN` environment variable. If laracrew can't find a directory that's on your PATH, it
|
|
208
|
+
falls back to `~/.laracrew/bin` and prints the one line you need to add it.
|
|
209
|
+
|
|
210
|
+
**It won't stomp on anything.** Every generated file carries a `laracrew-generated` marker.
|
|
211
|
+
laracrew refuses to overwrite a file it didn't write, refuses to shadow names like `npm` or
|
|
212
|
+
`git`, and `unlink` leaves foreign files alone. Pass `--force` if you genuinely mean it.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Logs survive
|
|
217
|
+
|
|
218
|
+
The live view keeps the last few thousand lines per service in memory — minutes, on a busy
|
|
219
|
+
stack. Everything is also written to disk, so the exception you watched scroll past is still
|
|
220
|
+
there tomorrow.
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
laracrew logs api:queue # last 200 lines, after the fact
|
|
224
|
+
laracrew logs api:queue -f # and keep following
|
|
225
|
+
laracrew logs --all --since 10m # every service, merged and time-ordered
|
|
226
|
+
laracrew logs --list # which services have a log
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Files land in `~/.laracrew/logs/<stack>/<service>.log`, rotated at 5 MB with one older copy
|
|
230
|
+
kept. They are plain text with a sortable local timestamp and no colour codes, so your usual
|
|
231
|
+
tools work on them directly:
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
2026-09-19 11:35:25.565 stderr PaymentFailedException: card declined
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
grep -i exception ~/.laracrew/logs/dual/api-queue.log
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
This is on by default. Turn it off per stack if you would rather not:
|
|
242
|
+
|
|
243
|
+
```yaml
|
|
244
|
+
defaults:
|
|
245
|
+
logs: { toFile: false }
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
| Setting | Default | Meaning |
|
|
249
|
+
|---|---|---|
|
|
250
|
+
| `logs.toFile` | `true` | Write every line to disk |
|
|
251
|
+
| `logs.maxLines` | `5000` | Lines kept in memory for the live view |
|
|
252
|
+
| `logs.maxFileBytes` | `5000000` | Rotate a service's log past this size |
|
|
253
|
+
| `logs.keepFiles` | `1` | Rotated copies kept beside the current file |
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Starting things on demand
|
|
258
|
+
|
|
259
|
+
Not every command should run all day. A scheduler tick, a one-off sync listener, a queue you
|
|
260
|
+
only drain occasionally — define them in the stack, but don't launch them:
|
|
261
|
+
|
|
262
|
+
```yaml
|
|
263
|
+
- name: api:queue
|
|
264
|
+
project: api
|
|
265
|
+
cmd: ["php", "artisan", "queue:work"] # starts with the stack
|
|
266
|
+
|
|
267
|
+
- name: api:streams
|
|
268
|
+
project: api
|
|
269
|
+
cmd: ["php", "artisan", "redis-stream:run", "orders_sync"]
|
|
270
|
+
autostart: false # defined, listed, not launched
|
|
271
|
+
|
|
272
|
+
- name: api:schedule
|
|
273
|
+
project: api
|
|
274
|
+
cmd: ["php", "artisan", "schedule:run"]
|
|
275
|
+
autostart: false
|
|
276
|
+
restart: never # one tick, not a daemon
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
They appear in the tree as **idle**, waiting for you:
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
├─ [1] ● api:serve running 8s
|
|
283
|
+
├─ [2] ● api:queue running 8s
|
|
284
|
+
├─ [3] ○ api:streams idle press s
|
|
285
|
+
├─ [4] ○ api:schedule idle press s
|
|
286
|
+
├─ [5] ● portal:queue running 8s
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Select one and press `s` to start it; `s` again to stop it. The header counts them
|
|
290
|
+
(`4/7 running · 2 idle`) so you can see at a glance what is dormant.
|
|
291
|
+
|
|
292
|
+
One rule the config enforces: a service that starts at launch may not `needs:` a service you
|
|
293
|
+
have to start by hand — that would leave it waiting on a gate nobody opened. laracrew refuses
|
|
294
|
+
the stack with a message naming both services rather than hanging.
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Driving it
|
|
299
|
+
|
|
300
|
+
| Key | Does |
|
|
301
|
+
|---|---|
|
|
302
|
+
| `up` `down` / `j` `k` | Move the selection |
|
|
303
|
+
| `0`-`9` | Jump to that process **and** open its log |
|
|
304
|
+
| `enter` | Inspect the selected process |
|
|
305
|
+
| `esc` | Back to the tree |
|
|
306
|
+
| `a` | Merged log across every service - the firehose, on demand |
|
|
307
|
+
| `r` | Restart the selected process (graceful: `queue:restart` first) |
|
|
308
|
+
| `s` | Stop it, or start it again if stopped |
|
|
309
|
+
| `f` / `g` / `G` | Follow-pause tailing; jump to top or bottom |
|
|
310
|
+
| `?` | Help |
|
|
311
|
+
| `q` / `ctrl-c` | Quit - stops every process first |
|
|
312
|
+
|
|
313
|
+
The view is plain ANSI on `node:readline`, no Ink and no React. It repaints on a 250 ms poll
|
|
314
|
+
rather than per log line, so a worker emitting 500 lines a second costs nothing to display.
|
|
315
|
+
`LARACREW_ASCII=1` swaps in an ASCII glyph set for terminals that mangle box drawing.
|
|
316
|
+
|
|
317
|
+
When stdout is not a TTY you get prefixed interleaved logs automatically, so
|
|
318
|
+
`laracrew up dual | tee dev.log` does the right thing with no flag.
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Examples
|
|
323
|
+
|
|
324
|
+
Five working stacks in [`examples/`](examples/) — only one of them is Laravel. Each is
|
|
325
|
+
self-contained, so copy one, fix the paths, and run it:
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
cp -r examples/node-api-and-web ~/.laracrew/stacks/
|
|
329
|
+
laracrew up node-api-and-web
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
| Example | What it is |
|
|
333
|
+
|---|---|
|
|
334
|
+
| [laravel-dual](examples/laravel-dual/stack.yaml) | Two interconnected Laravel apps: queues, stream listeners, schedulers, Vite |
|
|
335
|
+
| [node-api-and-web](examples/node-api-and-web/stack.yaml) | TypeScript API, Vite frontend, BullMQ worker, Postgres and Redis |
|
|
336
|
+
| [django-celery](examples/django-celery/stack.yaml) | Django, a Celery worker, beat, and optional extras |
|
|
337
|
+
| [polyglot-microservices](examples/polyglot-microservices/stack.yaml) | Go, Rust, Node and Python behind a gateway, with Docker Compose for infrastructure |
|
|
338
|
+
| [frontend-monorepo](examples/frontend-monorepo/stack.yaml) | tsc, Tailwind, Storybook and docs watchers in one repo — no servers at all |
|
|
339
|
+
|
|
340
|
+
[`examples/README.md`](examples/README.md) explains what each one is there to teach, plus the
|
|
341
|
+
patterns worth stealing: gating on reality instead of sleeping, keeping occasional commands in
|
|
342
|
+
the stack but idle, and letting laracrew own `docker compose` too.
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Concepts
|
|
347
|
+
|
|
348
|
+
| Concept | What it is |
|
|
349
|
+
|---|---|
|
|
350
|
+
| **Project** | One Laravel app root: path, PHP binary, `.env`. Defined once in `projects.yaml`, referenced by key. |
|
|
351
|
+
| **Service** | One long-running process: command, cwd, dependencies, readiness gate, restart policy. |
|
|
352
|
+
| **Stack** | A named set of services spanning one or more projects — the thing you `laracrew up`, and what a linked command boots. |
|
|
353
|
+
| **Group** | A tag on a service (`workers`, `http`, `assets`) for `--only` / `--except`. |
|
|
354
|
+
| **Profile** | A named filter stored in the stack (`light` = everything except assets). |
|
|
355
|
+
| **Task** | A one-shot ordered sequence across projects (`reset` = migrate:fresh + seed on both). |
|
|
356
|
+
|
|
357
|
+
Everything lives under `~/.laracrew/`, never inside your Laravel projects. laracrew only ever
|
|
358
|
+
reads your project files.
|
|
359
|
+
|
|
360
|
+
```
|
|
361
|
+
~/.laracrew/
|
|
362
|
+
├── config.yaml # theme, default stack, poll intervals
|
|
363
|
+
├── projects.yaml # your projects, referenced by key
|
|
364
|
+
├── stacks/
|
|
365
|
+
│ ├── dual/
|
|
366
|
+
│ │ ├── stack.yaml # the definition
|
|
367
|
+
│ │ └── services/ # optional: split a big stack into fragments
|
|
368
|
+
│ └── example/stack.yaml
|
|
369
|
+
├── tasks/reset.yaml
|
|
370
|
+
└── fragments/
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
One folder per stack, so a stack can carry its own fragments and notes. The whole directory is
|
|
374
|
+
safe to keep in git and sync between machines.
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## Configuring a stack
|
|
379
|
+
|
|
380
|
+
A complete two-project setup:
|
|
381
|
+
|
|
382
|
+
```yaml
|
|
383
|
+
name: dual
|
|
384
|
+
description: API + Portal with queues, streams and schedulers
|
|
385
|
+
command: dual # `laracrew link dual` installs this as a global command
|
|
386
|
+
|
|
387
|
+
use: [api, portal] # from ~/.laracrew/projects.yaml
|
|
388
|
+
|
|
389
|
+
defaults: # inherited by every service, overridable per service
|
|
390
|
+
restart: on-failure
|
|
391
|
+
backoff: { initialMs: 1000, maxMs: 30000, factor: 2, maxRestarts: 10 }
|
|
392
|
+
stop: { graceMs: 10000 }
|
|
393
|
+
|
|
394
|
+
services:
|
|
395
|
+
- name: redis
|
|
396
|
+
external: true # health-checked, never started by laracrew
|
|
397
|
+
ready: { tcp: "127.0.0.1:6379" }
|
|
398
|
+
|
|
399
|
+
- name: api:serve
|
|
400
|
+
project: api
|
|
401
|
+
cmd: php artisan serve --port=${port:8000}
|
|
402
|
+
groups: [http]
|
|
403
|
+
needs: [redis]
|
|
404
|
+
ready: { http: "http://127.0.0.1:8000/up", timeoutMs: 20000 }
|
|
405
|
+
url: http://127.0.0.1:8000
|
|
406
|
+
|
|
407
|
+
- name: api:queue
|
|
408
|
+
project: api
|
|
409
|
+
cmd: php artisan queue:work redis --queue=high,default --tries=3
|
|
410
|
+
groups: [workers]
|
|
411
|
+
needs: [redis]
|
|
412
|
+
stop: { artisan: "queue:restart", graceMs: 15000 } # finish the current job first
|
|
413
|
+
metrics: { queues: [high, default] }
|
|
414
|
+
|
|
415
|
+
- name: portal:serve
|
|
416
|
+
project: portal
|
|
417
|
+
cmd: php artisan serve --port=${port:8001}
|
|
418
|
+
groups: [http]
|
|
419
|
+
needs: [redis, api:serve] # waits for the API to actually answer
|
|
420
|
+
ready: { http: "http://127.0.0.1:8001/up" }
|
|
421
|
+
|
|
422
|
+
profiles:
|
|
423
|
+
light: { except: [assets] }
|
|
424
|
+
workers-only: { only: [workers] }
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Service fields
|
|
428
|
+
|
|
429
|
+
| Field | Notes |
|
|
430
|
+
|---|---|
|
|
431
|
+
| `name` | Required. Convention is `project:role`; used as the log prefix. |
|
|
432
|
+
| `project` | Supplies `cwd`, the PHP binary, the `.env` and the colour. |
|
|
433
|
+
| `cmd` | A shell string, or an argv array (`["php", "artisan", "queue:work"]`). The array form skips shell parsing — prefer it when arguments contain spaces. |
|
|
434
|
+
| `cwd` | Defaults to the project path. |
|
|
435
|
+
| `env` | Extra environment variables, merged over the inherited environment. |
|
|
436
|
+
| `groups` | Tags for `--only` / `--except`. |
|
|
437
|
+
| `needs` | Dependency edges. Cycles are a config error that names the members. |
|
|
438
|
+
| `ready` | `tcp`, `http`, `logMatch` (regex over output) or `delayMs`, plus `timeoutMs` (default 30000) and `intervalMs` (default 250). Without it, "spawned" means ready. |
|
|
439
|
+
| `restart` | `never` · `on-failure` (default) · `always`. |
|
|
440
|
+
| `backoff` | `initialMs`, `maxMs`, `factor`, `maxRestarts`, `resetAfterMs`. Delay is `min(initialMs × factor^n, maxMs)`; the counter resets after the service stays up for `resetAfterMs`. |
|
|
441
|
+
| `stop` | `exec` (any graceful shutdown command), `artisan` (sugar for one that runs artisan, such as `queue:restart` or `horizon:terminate`), `signal`, `graceMs`. |
|
|
442
|
+
| `url` | Recorded for the service; `laracrew open` is not built yet. |
|
|
443
|
+
| `external` | Health-checked but never spawned — Redis, MySQL, a Docker service. |
|
|
444
|
+
| `autostart` | `false` defines the service without launching it. It shows as **idle** in the tree; select it and press `s` when you need it. |
|
|
445
|
+
| `enabled` | Quick off switch without deleting the block. |
|
|
446
|
+
| `color` | Log-prefix colour; defaults to the project's. |
|
|
447
|
+
|
|
448
|
+
### Interpolation
|
|
449
|
+
|
|
450
|
+
| Token | Expands to |
|
|
451
|
+
|---|---|
|
|
452
|
+
| `${env:FOO}` / `${env:FOO:fallback}` | laracrew's own environment |
|
|
453
|
+
| `${project.path}` | the service's project root |
|
|
454
|
+
| `${project.env:REDIS_PORT}` | a value from that project's `.env` |
|
|
455
|
+
| `${stack.dir}` | the stack's own folder |
|
|
456
|
+
| `${port:8000}` | a port, recorded so `doctor` can check it for clashes |
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
## Commands
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
laracrew init [--examples] # create ~/.laracrew; --examples adds a demo stack + template
|
|
464
|
+
laracrew ls [--json] # list stacks, projects and tasks
|
|
465
|
+
laracrew doctor [stack] # check a stack before booting it
|
|
466
|
+
laracrew up [stack] [options] # boot the fleet and supervise it
|
|
467
|
+
laracrew logs [service] [--stack name] [-n 200] [-f] [--since 10m] [--all] [--list]
|
|
468
|
+
laracrew link [stack] [--as name] [--all] [--dir path] [--force]
|
|
469
|
+
laracrew unlink <name> # remove a command laracrew installed
|
|
470
|
+
laracrew --version
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
`laracrew up` options:
|
|
474
|
+
|
|
475
|
+
| Option | Effect |
|
|
476
|
+
|---|---|
|
|
477
|
+
| `--only <selector>` | Only these services or groups. Repeatable, comma-separated. |
|
|
478
|
+
| `--except <selector>` | Skip these services or groups. |
|
|
479
|
+
| `--profile <name>` | Apply a profile defined in the stack. |
|
|
480
|
+
| `--json` | Newline-delimited JSON events instead of logs — one object per line. |
|
|
481
|
+
| `--plain` | Prefixed interleaved logs instead of the full-screen view. Automatic when stdout is not a TTY. |
|
|
482
|
+
|
|
483
|
+
Omit the stack name and laracrew uses `defaultStack` from `config.yaml`, or the only stack that
|
|
484
|
+
exists, or tells you which ones it found.
|
|
485
|
+
|
|
486
|
+
```bash
|
|
487
|
+
laracrew up dual --only workers # just the queue workers and listeners
|
|
488
|
+
laracrew up dual --except assets # skip Vite
|
|
489
|
+
laracrew up dual --profile light
|
|
490
|
+
laracrew up dual --json | jq 'select(.type=="service:exit")'
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### What `doctor` checks
|
|
494
|
+
|
|
495
|
+
```
|
|
496
|
+
✔ stack "dual" is valid — 8 services
|
|
497
|
+
✔ php: PHP 8.3.11 (cli)
|
|
498
|
+
✖ api and portal share Redis 127.0.0.1:6379/0# AND queue(s): default
|
|
499
|
+
each project's workers will steal the other's jobs — set a different REDIS_DB or REDIS_PREFIX
|
|
500
|
+
✖ project "api" runs a queue worker but QUEUE_CONNECTION=sync
|
|
501
|
+
jobs run inline on dispatch, so the worker will sit idle forever — set it to redis or database
|
|
502
|
+
✖ port 8000 is already in use
|
|
503
|
+
▲ redis not reachable at 127.0.0.1:6380
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
Exit code is 1 when anything is at `✖`, so it drops straight into a pre-flight script.
|
|
507
|
+
|
|
508
|
+
**Every Laravel-specific check is skipped on a stack that isn't Laravel.** `doctor` works out
|
|
509
|
+
which projects actually run PHP — from the commands they declare and from `stop.artisan` — and
|
|
510
|
+
only those get the `php --version` check and the "no `artisan` file" warning. The same goes for
|
|
511
|
+
Redis: a project is only checked for namespace collisions and reachability if its `.env` or its
|
|
512
|
+
commands say it talks to Redis. Run `doctor` on a Django or Node stack and you get the checks
|
|
513
|
+
that apply to it, not a wall of PHP complaints:
|
|
514
|
+
|
|
515
|
+
```
|
|
516
|
+
✔ stack "django-celery" is valid — 8 services
|
|
517
|
+
✔ port 8000 is free
|
|
518
|
+
✔ postgres is reachable — tcp 127.0.0.1:5432
|
|
519
|
+
▲ redis is not reachable — tcp 127.0.0.1:6379 (ECONNREFUSED)
|
|
520
|
+
laracrew never starts an external service; anything that needs it will wait at its gate
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
Services marked `external: true` are checked through the gate they already declare, so whatever
|
|
524
|
+
your stack depends on — Postgres, RabbitMQ, an HTTP service — gets verified before boot.
|
|
525
|
+
|
|
526
|
+
---
|
|
527
|
+
|
|
528
|
+
## How shutdown works
|
|
529
|
+
|
|
530
|
+
This is where most process managers leave a mess, so it's worth stating exactly. Each step runs
|
|
531
|
+
only if the previous one timed out:
|
|
532
|
+
|
|
533
|
+
1. **Graceful** — if the service declares `stop.exec`, run it and wait up to `graceMs` for the
|
|
534
|
+
process to exit on its own, having finished whatever it was holding.
|
|
535
|
+
2. **Signal** — `SIGTERM` to the process group. Skipped on Windows, which has no equivalent.
|
|
536
|
+
3. **Tree kill** — `taskkill /pid <pid> /T /F` on Windows, `kill(-pid)` on POSIX. This is what
|
|
537
|
+
catches the child PHP server behind `artisan serve` and the Vite process behind `npm run dev`.
|
|
538
|
+
4. **Verify** — re-check the pid and warn loudly if anything survived.
|
|
539
|
+
|
|
540
|
+
Stacks come down in reverse dependency order, parallel within a level. A second Ctrl-C escalates
|
|
541
|
+
immediately and says so.
|
|
542
|
+
|
|
543
|
+
Step 1 is any command, so every worker gets the same treatment your queue workers do:
|
|
544
|
+
|
|
545
|
+
```yaml
|
|
546
|
+
stop: { exec: ["celery", "-A", "app", "control", "shutdown"], graceMs: 20000 }
|
|
547
|
+
stop: { exec: "npm run drain", graceMs: 5000 }
|
|
548
|
+
stop: { exec: ["docker", "compose", "stop"], graceMs: 30000 }
|
|
549
|
+
stop: { artisan: "queue:restart", graceMs: 15000 } # shorthand for `<php> artisan queue:restart`
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
`artisan` is the Laravel shorthand: it runs through the project's PHP binary, from the project
|
|
553
|
+
root, even when the service sets its own `cwd`. It needs a `project`; anything else uses `exec`.
|
|
554
|
+
Without either, the ladder starts at the signal.
|
|
555
|
+
|
|
556
|
+
If a readiness gate fails during boot, laracrew rolls back everything it already started before
|
|
557
|
+
exiting non-zero — you never get a half-booted stack you have to clean up by hand.
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
## Environment
|
|
562
|
+
|
|
563
|
+
| Variable | Effect |
|
|
564
|
+
|---|---|
|
|
565
|
+
| `LARACREW_HOME` | Override `~/.laracrew`. |
|
|
566
|
+
| `LARACREW_BIN` | Where `laracrew link` installs global commands. |
|
|
567
|
+
| `LARACREW_ASCII` | `1` swaps box-drawing glyphs for ASCII. |
|
|
568
|
+
| `NO_COLOR` | Disable colour, even on a TTY. |
|
|
569
|
+
| `FORCE_COLOR` | Enable colour when piping. |
|
|
570
|
+
|
|
571
|
+
Each child process is given `LARACREW=1`, `LARACREW_SERVICE=<name>` and, when it belongs to a
|
|
572
|
+
project, `LARACREW_PROJECT=<key>`.
|
|
573
|
+
|
|
574
|
+
---
|
|
575
|
+
|
|
576
|
+
## Status
|
|
577
|
+
|
|
578
|
+
**v0.2.0 — supervisor and full-screen view are built and tested.**
|
|
579
|
+
|
|
580
|
+
Working now: config pipeline, dependency-ordered boot with readiness gates, restart policies with
|
|
581
|
+
exponential backoff, the graceful stop ladder — `stop.exec` for any process, `stop.artisan` as the
|
|
582
|
+
Laravel shorthand — the full-screen process tree with per-process log inspection, logs persisted to
|
|
583
|
+
disk with `laracrew logs` to read them back, plain and JSON renderers, per-stack global commands
|
|
584
|
+
(`link` / `unlink`), `doctor`, `init`, `ls`.
|
|
585
|
+
|
|
586
|
+
See [CHANGELOG.md](CHANGELOG.md) for what changed in each release.
|
|
587
|
+
|
|
588
|
+
Accepted by the config schema but **not yet acted on** — they validate, so your stack files are
|
|
589
|
+
future-proof, but nothing happens yet:
|
|
590
|
+
|
|
591
|
+
| Key | Lands in |
|
|
592
|
+
|---|---|
|
|
593
|
+
| `watch` | M4 — file-change restarts via `queue:restart` |
|
|
594
|
+
| `metrics` | M3 — live queue depth and stream lag (today `doctor` reads it for collision checks) |
|
|
595
|
+
| `hooks.preUp` / `hooks.postDown` | not scheduled |
|
|
596
|
+
|
|
597
|
+
### Roadmap
|
|
598
|
+
|
|
599
|
+
| | |
|
|
600
|
+
|---|---|
|
|
601
|
+
| **M3** | `laracrew scan` project discovery, Redis queue depth and stream consumer lag on screen, failed-job badge |
|
|
602
|
+
| **M4** | File watching with graceful `queue:restart`, and `laracrew run <task>` |
|
|
603
|
+
| **M5** | Background daemon: `up --detach`, `attach`, `status`, `logs -f` |
|
|
604
|
+
| **M6** | Themes, JSON Schema for editor autocomplete, shell completions |
|
|
605
|
+
|
|
606
|
+
The full plan lives in [.claude/PLAN.md](.claude/PLAN.md), with the design in
|
|
607
|
+
[ARCHITECTURE.md](.claude/ARCHITECTURE.md), [CONFIG-SPEC.md](.claude/CONFIG-SPEC.md) and
|
|
608
|
+
[TUI-UX.md](.claude/TUI-UX.md).
|
|
609
|
+
|
|
610
|
+
---
|
|
611
|
+
|
|
612
|
+
## Development
|
|
613
|
+
|
|
614
|
+
```bash
|
|
615
|
+
npm install
|
|
616
|
+
npm run dev -- up example # tsx, no build step
|
|
617
|
+
npm run build # tsup -> dist/index.js
|
|
618
|
+
npm test # vitest, 244 tests
|
|
619
|
+
npm run typecheck
|
|
620
|
+
npm link # put `laracrew` on PATH while hacking on it
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
Runtime dependencies, in total: `commander`, `yaml`, `zod`. Process spawning, tree-killing and
|
|
624
|
+
colour are hand-rolled — see [ARCHITECTURE.md §9](.claude/ARCHITECTURE.md) for why `execa`,
|
|
625
|
+
`tree-kill` and `picocolors` were dropped. Startup time is a feature for a tool you run twenty
|
|
626
|
+
times a day.
|
|
627
|
+
|
|
628
|
+
The test suite spawns real child processes, binds real ports and asserts that no pid survives a
|
|
629
|
+
shutdown — including a deliberately spawned grandchild and a process that ignores `SIGTERM`. Every
|
|
630
|
+
test runs against a throwaway `LARACREW_HOME`.
|
|
631
|
+
|
|
632
|
+
Architectural rule worth knowing before you contribute: **nothing in `src/core/` may import from
|
|
633
|
+
`src/cli/`**. Core emits typed events; the plain renderer, the JSON renderer and the coming TUI are
|
|
634
|
+
all just subscribers. That's what keeps `--plain`, `--detach` and the tests honest.
|
|
635
|
+
|
|
636
|
+
## License
|
|
637
|
+
|
|
638
|
+
MIT
|