laracrew 0.2.2 → 0.3.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 +30 -1
- package/README.md +80 -7
- package/dist/index.js +886 -463
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/program.ts
|
|
4
|
-
import { readFileSync as
|
|
5
|
-
import
|
|
4
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
5
|
+
import path12 from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
|
|
@@ -315,6 +315,7 @@ var mergeServiceFragments = (document, dir) => {
|
|
|
315
315
|
const existing = Array.isArray(document.services) ? document.services : [];
|
|
316
316
|
return { ...document, services: [...existing, ...extra] };
|
|
317
317
|
};
|
|
318
|
+
var validateStack = (document, file) => validate(stackSchema, document, file);
|
|
318
319
|
var loadStack = (name, env = process.env) => {
|
|
319
320
|
const dir = stackDir(name, env);
|
|
320
321
|
const file = stackFile(name, env);
|
|
@@ -345,41 +346,192 @@ var listTasks = (env = process.env) => {
|
|
|
345
346
|
};
|
|
346
347
|
|
|
347
348
|
// src/cli/render/colors.ts
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
349
|
+
import { Chalk, supportsColor as chalkDetected } from "chalk";
|
|
350
|
+
|
|
351
|
+
// src/core/logs/sanitize.ts
|
|
352
|
+
var ESC = String.fromCharCode(27);
|
|
353
|
+
var BEL = String.fromCharCode(7);
|
|
354
|
+
var CSI = new RegExp(ESC + "\\[[0-?]*[ -/]*[@-~]", "g");
|
|
355
|
+
var OSC = new RegExp(ESC + "\\][^" + BEL + ESC + "]*(?:" + BEL + "|" + ESC + "\\\\)", "g");
|
|
356
|
+
var SINGLE = new RegExp(ESC + "[=>78MD]", "g");
|
|
357
|
+
var SGR = new RegExp(ESC + "\\[[0-9;]*m", "g");
|
|
358
|
+
var stripAnsi = (text) => text.replace(SGR, "");
|
|
359
|
+
var TAB = 9;
|
|
360
|
+
var ESC_CODE = 27;
|
|
361
|
+
var sanitize = (input) => {
|
|
362
|
+
let out = input.replace(OSC, "").replace(SINGLE, "");
|
|
363
|
+
out = out.replace(CSI, (seq) => seq.endsWith("m") ? seq : "");
|
|
364
|
+
if (out.includes("\r")) {
|
|
365
|
+
const segments = out.split("\r").filter((segment) => segment !== "");
|
|
366
|
+
out = segments.length > 0 ? segments[segments.length - 1] : "";
|
|
367
|
+
}
|
|
368
|
+
return out.replace(new RegExp("\\p{Cc}", "gu"), (character) => {
|
|
369
|
+
const code = character.charCodeAt(0);
|
|
370
|
+
return code === TAB || code === ESC_CODE ? character : "";
|
|
371
|
+
});
|
|
372
|
+
};
|
|
373
|
+
var LineSplitter = class {
|
|
374
|
+
#buffer = "";
|
|
375
|
+
#maxLineLength;
|
|
376
|
+
constructor(maxLineLength = 16384) {
|
|
377
|
+
this.#maxLineLength = maxLineLength;
|
|
378
|
+
}
|
|
379
|
+
push(chunk) {
|
|
380
|
+
this.#buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
381
|
+
const lines = [];
|
|
382
|
+
let index = this.#buffer.indexOf("\n");
|
|
383
|
+
while (index !== -1) {
|
|
384
|
+
const line = this.#buffer.slice(0, index).replace(/\r$/, "");
|
|
385
|
+
lines.push(sanitize(line));
|
|
386
|
+
this.#buffer = this.#buffer.slice(index + 1);
|
|
387
|
+
index = this.#buffer.indexOf("\n");
|
|
388
|
+
}
|
|
389
|
+
if (this.#buffer.length > this.#maxLineLength) {
|
|
390
|
+
lines.push(sanitize(this.#buffer));
|
|
391
|
+
this.#buffer = "";
|
|
392
|
+
}
|
|
393
|
+
return lines;
|
|
394
|
+
}
|
|
395
|
+
/** Emits whatever is left when the stream closes. */
|
|
396
|
+
flush() {
|
|
397
|
+
if (this.#buffer === "") return [];
|
|
398
|
+
const line = sanitize(this.#buffer.replace(/\r$/, ""));
|
|
399
|
+
this.#buffer = "";
|
|
400
|
+
return line === "" ? [] : [line];
|
|
401
|
+
}
|
|
363
402
|
};
|
|
403
|
+
|
|
404
|
+
// src/cli/render/colors.ts
|
|
405
|
+
var NAMES = [
|
|
406
|
+
"reset",
|
|
407
|
+
"bold",
|
|
408
|
+
"dim",
|
|
409
|
+
"italic",
|
|
410
|
+
"underline",
|
|
411
|
+
"black",
|
|
412
|
+
"red",
|
|
413
|
+
"green",
|
|
414
|
+
"yellow",
|
|
415
|
+
"blue",
|
|
416
|
+
"magenta",
|
|
417
|
+
"cyan",
|
|
418
|
+
"white",
|
|
419
|
+
"gray"
|
|
420
|
+
];
|
|
364
421
|
var supportsColor = (stream = process.stdout, env = process.env) => {
|
|
365
422
|
if (env.NO_COLOR !== void 0 && env.NO_COLOR !== "") return false;
|
|
366
|
-
if (env.FORCE_COLOR !== void 0
|
|
423
|
+
if (env.FORCE_COLOR !== void 0) return env.FORCE_COLOR !== "0" && env.FORCE_COLOR !== "false";
|
|
367
424
|
if (env.TERM === "dumb") return false;
|
|
368
425
|
return Boolean(stream.isTTY);
|
|
369
426
|
};
|
|
427
|
+
var levelFor = (enabled) => enabled ? Math.max(1, chalkDetected ? chalkDetected.level : 1) : 0;
|
|
428
|
+
var createColor = (enabled) => new Chalk({ level: levelFor(enabled) });
|
|
429
|
+
var color = createColor(supportsColor());
|
|
370
430
|
var createPainter = (enabled) => {
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
return paint2;
|
|
431
|
+
const chalk = createColor(enabled);
|
|
432
|
+
return Object.assign((name, text) => chalk[name](text), { enabled });
|
|
374
433
|
};
|
|
375
434
|
var paint = createPainter(supportsColor());
|
|
376
|
-
var asColorName = (value) => value && value
|
|
377
|
-
var stripAnsi = (text) => text.replace(/\[[0-9;]*m/g, "");
|
|
435
|
+
var asColorName = (value) => value && NAMES.includes(value) ? value : "cyan";
|
|
378
436
|
var padEnd = (text, width) => {
|
|
379
437
|
const visible = stripAnsi(text).length;
|
|
380
438
|
return visible >= width ? text : text + " ".repeat(width - visible);
|
|
381
439
|
};
|
|
382
440
|
|
|
441
|
+
// src/tui/theme.ts
|
|
442
|
+
var asciiOnly = (env = process.env) => env.LARACREW_ASCII === "1" || env.TERM === "dumb";
|
|
443
|
+
var UNICODE = {
|
|
444
|
+
running: "\u25CF",
|
|
445
|
+
ready: "\u25CF",
|
|
446
|
+
starting: "\u25D0",
|
|
447
|
+
stopped: "\u25FC",
|
|
448
|
+
failed: "\u2716",
|
|
449
|
+
queued: "\u25CB",
|
|
450
|
+
branch: "\u251C\u2500",
|
|
451
|
+
lastBranch: "\u2514\u2500",
|
|
452
|
+
pipe: "\u2502",
|
|
453
|
+
pointer: "\u25B8",
|
|
454
|
+
restart: "\u27F3",
|
|
455
|
+
rule: "\u2500",
|
|
456
|
+
dot: "\xB7",
|
|
457
|
+
tick: "\u2714",
|
|
458
|
+
warn: "\u25B2",
|
|
459
|
+
arrow: "\u2192"
|
|
460
|
+
};
|
|
461
|
+
var ASCII = {
|
|
462
|
+
running: "*",
|
|
463
|
+
ready: "*",
|
|
464
|
+
starting: "o",
|
|
465
|
+
stopped: "#",
|
|
466
|
+
failed: "X",
|
|
467
|
+
queued: ".",
|
|
468
|
+
branch: "|-",
|
|
469
|
+
lastBranch: "`-",
|
|
470
|
+
pipe: "|",
|
|
471
|
+
pointer: ">",
|
|
472
|
+
restart: "r",
|
|
473
|
+
rule: "-",
|
|
474
|
+
dot: ".",
|
|
475
|
+
tick: "v",
|
|
476
|
+
warn: "!",
|
|
477
|
+
arrow: "->"
|
|
478
|
+
};
|
|
479
|
+
var glyphs = (env = process.env) => asciiOnly(env) ? ASCII : UNICODE;
|
|
480
|
+
var STATE_STYLE = {
|
|
481
|
+
queued: { glyph: "queued", color: "gray", label: "QUEUED" },
|
|
482
|
+
starting: { glyph: "starting", color: "yellow", label: "STARTING" },
|
|
483
|
+
ready: { glyph: "ready", color: "cyan", label: "READY" },
|
|
484
|
+
running: { glyph: "running", color: "green", label: "RUNNING" },
|
|
485
|
+
stopping: { glyph: "starting", color: "yellow", label: "STOPPING" },
|
|
486
|
+
stopped: { glyph: "stopped", color: "gray", label: "STOPPED" },
|
|
487
|
+
crashed: { glyph: "failed", color: "red", label: "CRASHED" },
|
|
488
|
+
backoff: { glyph: "starting", color: "yellow", label: "RESTARTING" },
|
|
489
|
+
failed: { glyph: "failed", color: "red", label: "FAILED" }
|
|
490
|
+
};
|
|
491
|
+
var formatUptime = (ms) => {
|
|
492
|
+
if (ms === void 0 || ms < 0) return "--:--:--";
|
|
493
|
+
const total = Math.floor(ms / 1e3);
|
|
494
|
+
const hours = Math.floor(total / 3600);
|
|
495
|
+
const minutes = Math.floor(total % 3600 / 60);
|
|
496
|
+
const seconds = total % 60;
|
|
497
|
+
return [hours, minutes, seconds].map((part) => String(part).padStart(2, "0")).join(":");
|
|
498
|
+
};
|
|
499
|
+
var formatDuration = (ms) => {
|
|
500
|
+
if (ms === void 0 || ms < 0) return "\u2014";
|
|
501
|
+
const total = Math.floor(ms / 1e3);
|
|
502
|
+
if (total < 60) return `${total}s`;
|
|
503
|
+
const minutes = Math.floor(total / 60);
|
|
504
|
+
const seconds = total % 60;
|
|
505
|
+
if (minutes < 60) return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
|
|
506
|
+
const hours = Math.floor(minutes / 60);
|
|
507
|
+
return `${hours}h ${String(minutes % 60).padStart(2, "0")}m`;
|
|
508
|
+
};
|
|
509
|
+
var formatClock = (at) => {
|
|
510
|
+
const date = new Date(at);
|
|
511
|
+
return [date.getHours(), date.getMinutes(), date.getSeconds()].map((part) => String(part).padStart(2, "0")).join(":");
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
// src/cli/render/marks.ts
|
|
515
|
+
var marks = (env = process.env) => {
|
|
516
|
+
const glyph = glyphs(env);
|
|
517
|
+
return {
|
|
518
|
+
ok: color.green(glyph.tick),
|
|
519
|
+
fail: color.red(glyph.failed),
|
|
520
|
+
warn: color.yellow(glyph.warn),
|
|
521
|
+
skip: color.gray(glyph.dot),
|
|
522
|
+
hint: color.cyan(glyph.pointer),
|
|
523
|
+
arrow: color.gray(glyph.arrow)
|
|
524
|
+
};
|
|
525
|
+
};
|
|
526
|
+
var nextSteps = (rows, env = process.env) => {
|
|
527
|
+
const mark = marks(env);
|
|
528
|
+
const width = Math.max(...rows.map(([command]) => command.length));
|
|
529
|
+
const lines = rows.map(
|
|
530
|
+
([command, why]) => command ? ` ${mark.hint} ${color.cyan(padEnd(command, width))} ${why}` : `${" ".repeat(width + 6)}${why}`
|
|
531
|
+
);
|
|
532
|
+
return [color.bold("Next"), ...lines].join("\n");
|
|
533
|
+
};
|
|
534
|
+
|
|
383
535
|
// src/cli/commands/doctor.ts
|
|
384
536
|
import { existsSync as existsSync4 } from "fs";
|
|
385
537
|
import path7 from "path";
|
|
@@ -1063,57 +1215,6 @@ var RingBuffer = class {
|
|
|
1063
1215
|
}
|
|
1064
1216
|
};
|
|
1065
1217
|
|
|
1066
|
-
// src/core/logs/sanitize.ts
|
|
1067
|
-
var ESC = String.fromCharCode(27);
|
|
1068
|
-
var BEL = String.fromCharCode(7);
|
|
1069
|
-
var CSI = new RegExp(ESC + "\\[[0-?]*[ -/]*[@-~]", "g");
|
|
1070
|
-
var OSC = new RegExp(ESC + "\\][^" + BEL + ESC + "]*(?:" + BEL + "|" + ESC + "\\\\)", "g");
|
|
1071
|
-
var SINGLE = new RegExp(ESC + "[=>78MD]", "g");
|
|
1072
|
-
var TAB = 9;
|
|
1073
|
-
var ESC_CODE = 27;
|
|
1074
|
-
var sanitize = (input) => {
|
|
1075
|
-
let out = input.replace(OSC, "").replace(SINGLE, "");
|
|
1076
|
-
out = out.replace(CSI, (seq) => seq.endsWith("m") ? seq : "");
|
|
1077
|
-
if (out.includes("\r")) {
|
|
1078
|
-
const segments = out.split("\r").filter((segment) => segment !== "");
|
|
1079
|
-
out = segments.length > 0 ? segments[segments.length - 1] : "";
|
|
1080
|
-
}
|
|
1081
|
-
return out.replace(new RegExp("\\p{Cc}", "gu"), (character) => {
|
|
1082
|
-
const code = character.charCodeAt(0);
|
|
1083
|
-
return code === TAB || code === ESC_CODE ? character : "";
|
|
1084
|
-
});
|
|
1085
|
-
};
|
|
1086
|
-
var LineSplitter = class {
|
|
1087
|
-
#buffer = "";
|
|
1088
|
-
#maxLineLength;
|
|
1089
|
-
constructor(maxLineLength = 16384) {
|
|
1090
|
-
this.#maxLineLength = maxLineLength;
|
|
1091
|
-
}
|
|
1092
|
-
push(chunk) {
|
|
1093
|
-
this.#buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
1094
|
-
const lines = [];
|
|
1095
|
-
let index = this.#buffer.indexOf("\n");
|
|
1096
|
-
while (index !== -1) {
|
|
1097
|
-
const line = this.#buffer.slice(0, index).replace(/\r$/, "");
|
|
1098
|
-
lines.push(sanitize(line));
|
|
1099
|
-
this.#buffer = this.#buffer.slice(index + 1);
|
|
1100
|
-
index = this.#buffer.indexOf("\n");
|
|
1101
|
-
}
|
|
1102
|
-
if (this.#buffer.length > this.#maxLineLength) {
|
|
1103
|
-
lines.push(sanitize(this.#buffer));
|
|
1104
|
-
this.#buffer = "";
|
|
1105
|
-
}
|
|
1106
|
-
return lines;
|
|
1107
|
-
}
|
|
1108
|
-
/** Emits whatever is left when the stream closes. */
|
|
1109
|
-
flush() {
|
|
1110
|
-
if (this.#buffer === "") return [];
|
|
1111
|
-
const line = sanitize(this.#buffer.replace(/\r$/, ""));
|
|
1112
|
-
this.#buffer = "";
|
|
1113
|
-
return line === "" ? [] : [line];
|
|
1114
|
-
}
|
|
1115
|
-
};
|
|
1116
|
-
|
|
1117
1218
|
// src/core/process/stop.ts
|
|
1118
1219
|
import { spawn as spawn2 } from "child_process";
|
|
1119
1220
|
var IS_WINDOWS2 = process.platform === "win32";
|
|
@@ -1721,6 +1822,7 @@ var time = (date) => `${String(date.getHours()).padStart(2, "0")}:${String(date.
|
|
|
1721
1822
|
var attachPlainRenderer = (bus, options) => {
|
|
1722
1823
|
const write2 = options.write ?? ((text) => write(text));
|
|
1723
1824
|
const painter = options.painter ?? paint;
|
|
1825
|
+
const glyph = options.glyph ?? glyphs();
|
|
1724
1826
|
const showTime = options.timestamps ?? true;
|
|
1725
1827
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
1726
1828
|
const colorOf = new Map(options.stack.services.map((service) => [service.name, asColorName(service.color)]));
|
|
@@ -1731,10 +1833,19 @@ var attachPlainRenderer = (bus, options) => {
|
|
|
1731
1833
|
write2(`${stamp2}${prefix} ${painter("gray", "|")} ${text}
|
|
1732
1834
|
`);
|
|
1733
1835
|
};
|
|
1734
|
-
const
|
|
1735
|
-
|
|
1736
|
-
|
|
1836
|
+
const note = (tone, text) => {
|
|
1837
|
+
switch (tone) {
|
|
1838
|
+
case "ok":
|
|
1839
|
+
return `${painter("green", glyph.tick)} ${painter("green", text)}`;
|
|
1840
|
+
case "warn":
|
|
1841
|
+
return `${painter("yellow", glyph.warn)} ${painter("yellow", text)}`;
|
|
1842
|
+
case "error":
|
|
1843
|
+
return `${painter("red", glyph.failed)} ${painter("red", text)}`;
|
|
1844
|
+
default:
|
|
1845
|
+
return painter("gray", text);
|
|
1846
|
+
}
|
|
1737
1847
|
};
|
|
1848
|
+
const system = (text, tone = "info") => line("laracrew", note(tone, text), "bold");
|
|
1738
1849
|
const handle = (event) => {
|
|
1739
1850
|
switch (event.type) {
|
|
1740
1851
|
case "service:log":
|
|
@@ -1749,7 +1860,7 @@ var attachPlainRenderer = (bus, options) => {
|
|
|
1749
1860
|
break;
|
|
1750
1861
|
}
|
|
1751
1862
|
case "stack:ready":
|
|
1752
|
-
system(`${event.stack} ready in ${(event.durationMs / 1e3).toFixed(1)}s
|
|
1863
|
+
system(`${event.stack} ready in ${(event.durationMs / 1e3).toFixed(1)}s`, "ok");
|
|
1753
1864
|
break;
|
|
1754
1865
|
case "stack:stopping":
|
|
1755
1866
|
system(`stopping ${event.stack}\u2026`);
|
|
@@ -1761,30 +1872,32 @@ var attachPlainRenderer = (bus, options) => {
|
|
|
1761
1872
|
line(event.service, painter("gray", `$ ${event.command}`));
|
|
1762
1873
|
break;
|
|
1763
1874
|
case "service:ready":
|
|
1764
|
-
line(event.service,
|
|
1875
|
+
line(event.service, note("ok", `ready (${event.probe}) in ${event.durationMs}ms`));
|
|
1765
1876
|
break;
|
|
1766
1877
|
case "service:exit":
|
|
1767
1878
|
if (!event.intentional) {
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
);
|
|
1879
|
+
const clean = event.code === 0 && !event.signal;
|
|
1880
|
+
const why = event.signal ? `signal ${event.signal}` : `code ${event.code}`;
|
|
1881
|
+
line(event.service, note(clean ? "warn" : "error", `exited (${why})`));
|
|
1772
1882
|
}
|
|
1773
1883
|
break;
|
|
1774
1884
|
case "service:restart":
|
|
1775
1885
|
line(
|
|
1776
1886
|
event.service,
|
|
1777
|
-
painter("yellow",
|
|
1887
|
+
`${painter("yellow", glyph.restart)} ${painter(
|
|
1888
|
+
"yellow",
|
|
1889
|
+
`restarting in ${event.delayMs}ms (attempt ${event.attempt}/${event.maxAttempts})`
|
|
1890
|
+
)}`
|
|
1778
1891
|
);
|
|
1779
1892
|
break;
|
|
1780
1893
|
case "service:state":
|
|
1781
1894
|
if (event.to === "failed") {
|
|
1782
|
-
line(event.service,
|
|
1895
|
+
line(event.service, note("error", `failed${event.detail ? `: ${event.detail}` : ""}`));
|
|
1783
1896
|
}
|
|
1784
1897
|
break;
|
|
1785
1898
|
case "notice":
|
|
1786
1899
|
if (event.service) {
|
|
1787
|
-
line(event.service,
|
|
1900
|
+
line(event.service, note(event.level, event.message));
|
|
1788
1901
|
} else {
|
|
1789
1902
|
system(event.message, event.level);
|
|
1790
1903
|
}
|
|
@@ -1925,75 +2038,6 @@ var handleKey = (ui, key, rowCount) => {
|
|
|
1925
2038
|
}
|
|
1926
2039
|
};
|
|
1927
2040
|
|
|
1928
|
-
// src/tui/theme.ts
|
|
1929
|
-
var asciiOnly = (env = process.env) => env.LARACREW_ASCII === "1" || env.TERM === "dumb";
|
|
1930
|
-
var UNICODE = {
|
|
1931
|
-
running: "\u25CF",
|
|
1932
|
-
ready: "\u25CF",
|
|
1933
|
-
starting: "\u25D0",
|
|
1934
|
-
stopped: "\u25FC",
|
|
1935
|
-
failed: "\u2716",
|
|
1936
|
-
queued: "\u25CB",
|
|
1937
|
-
branch: "\u251C\u2500",
|
|
1938
|
-
lastBranch: "\u2514\u2500",
|
|
1939
|
-
pipe: "\u2502",
|
|
1940
|
-
pointer: "\u25B8",
|
|
1941
|
-
restart: "\u27F3",
|
|
1942
|
-
rule: "\u2500",
|
|
1943
|
-
dot: "\xB7",
|
|
1944
|
-
tick: "\u2714"
|
|
1945
|
-
};
|
|
1946
|
-
var ASCII = {
|
|
1947
|
-
running: "*",
|
|
1948
|
-
ready: "*",
|
|
1949
|
-
starting: "o",
|
|
1950
|
-
stopped: "#",
|
|
1951
|
-
failed: "X",
|
|
1952
|
-
queued: ".",
|
|
1953
|
-
branch: "|-",
|
|
1954
|
-
lastBranch: "`-",
|
|
1955
|
-
pipe: "|",
|
|
1956
|
-
pointer: ">",
|
|
1957
|
-
restart: "r",
|
|
1958
|
-
rule: "-",
|
|
1959
|
-
dot: ".",
|
|
1960
|
-
tick: "v"
|
|
1961
|
-
};
|
|
1962
|
-
var glyphs = (env = process.env) => asciiOnly(env) ? ASCII : UNICODE;
|
|
1963
|
-
var STATE_STYLE = {
|
|
1964
|
-
queued: { glyph: "queued", color: "gray", label: "QUEUED" },
|
|
1965
|
-
starting: { glyph: "starting", color: "yellow", label: "STARTING" },
|
|
1966
|
-
ready: { glyph: "ready", color: "cyan", label: "READY" },
|
|
1967
|
-
running: { glyph: "running", color: "green", label: "RUNNING" },
|
|
1968
|
-
stopping: { glyph: "starting", color: "yellow", label: "STOPPING" },
|
|
1969
|
-
stopped: { glyph: "stopped", color: "gray", label: "STOPPED" },
|
|
1970
|
-
crashed: { glyph: "failed", color: "red", label: "CRASHED" },
|
|
1971
|
-
backoff: { glyph: "starting", color: "yellow", label: "RESTARTING" },
|
|
1972
|
-
failed: { glyph: "failed", color: "red", label: "FAILED" }
|
|
1973
|
-
};
|
|
1974
|
-
var formatUptime = (ms) => {
|
|
1975
|
-
if (ms === void 0 || ms < 0) return "--:--:--";
|
|
1976
|
-
const total = Math.floor(ms / 1e3);
|
|
1977
|
-
const hours = Math.floor(total / 3600);
|
|
1978
|
-
const minutes = Math.floor(total % 3600 / 60);
|
|
1979
|
-
const seconds = total % 60;
|
|
1980
|
-
return [hours, minutes, seconds].map((part) => String(part).padStart(2, "0")).join(":");
|
|
1981
|
-
};
|
|
1982
|
-
var formatDuration = (ms) => {
|
|
1983
|
-
if (ms === void 0 || ms < 0) return "\u2014";
|
|
1984
|
-
const total = Math.floor(ms / 1e3);
|
|
1985
|
-
if (total < 60) return `${total}s`;
|
|
1986
|
-
const minutes = Math.floor(total / 60);
|
|
1987
|
-
const seconds = total % 60;
|
|
1988
|
-
if (minutes < 60) return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
|
|
1989
|
-
const hours = Math.floor(minutes / 60);
|
|
1990
|
-
return `${hours}h ${String(minutes % 60).padStart(2, "0")}m`;
|
|
1991
|
-
};
|
|
1992
|
-
var formatClock = (at) => {
|
|
1993
|
-
const date = new Date(at);
|
|
1994
|
-
return [date.getHours(), date.getMinutes(), date.getSeconds()].map((part) => String(part).padStart(2, "0")).join(":");
|
|
1995
|
-
};
|
|
1996
|
-
|
|
1997
2041
|
// src/tui/screen.ts
|
|
1998
2042
|
var initialUiState = () => ({
|
|
1999
2043
|
view: "overview",
|
|
@@ -2096,8 +2140,8 @@ var processLine = (row, isSelected, isLast, nameWidth, { paint: paint2, glyph, s
|
|
|
2096
2140
|
var dependencyLine = (row, nameWidth, { paint: paint2, glyph, size }) => {
|
|
2097
2141
|
const style = STATE_STYLE[row.state];
|
|
2098
2142
|
const label = row.state === "ready" || row.state === "running" ? "ready" : style.label.toLowerCase();
|
|
2099
|
-
const
|
|
2100
|
-
const line = ` ${paint2(
|
|
2143
|
+
const color2 = row.state === "ready" || row.state === "running" ? "green" : style.color;
|
|
2144
|
+
const line = ` ${paint2(color2, glyph[style.glyph])} ${pad(row.name, nameWidth)}${paint2(color2, pad(label, 11))}${paint2("gray", row.probe ?? "")}`;
|
|
2101
2145
|
return truncate(line, size.columns + (line.length - stripAnsi(line).length));
|
|
2102
2146
|
};
|
|
2103
2147
|
var dependencies = (input, nameWidth) => {
|
|
@@ -2355,7 +2399,7 @@ var runTui = async (supervisor, bus, options = {}) => {
|
|
|
2355
2399
|
});
|
|
2356
2400
|
const basePainter = createPainter(supportsColor(stdout, env));
|
|
2357
2401
|
const paint2 = Object.assign(
|
|
2358
|
-
(
|
|
2402
|
+
(color2, text) => basePainter(color2, text),
|
|
2359
2403
|
{ enabled: basePainter.enabled }
|
|
2360
2404
|
);
|
|
2361
2405
|
const glyph = glyphs(env);
|
|
@@ -2523,6 +2567,7 @@ var shouldUseTui = (options, stdout = process.stdout) => !options.plain && !opti
|
|
|
2523
2567
|
var upCommand = async (name, options = {}) => {
|
|
2524
2568
|
const stack = prepareStack(name, options);
|
|
2525
2569
|
const bus = new EventBus();
|
|
2570
|
+
const mark = marks(options.env);
|
|
2526
2571
|
if (shouldUseTui(options)) {
|
|
2527
2572
|
const supervisor2 = new Supervisor(stack, bus, options.env ? { env: options.env } : {});
|
|
2528
2573
|
return runTui(supervisor2, bus, options.env ? { env: options.env } : {});
|
|
@@ -2538,7 +2583,8 @@ var upCommand = async (name, options = {}) => {
|
|
|
2538
2583
|
};
|
|
2539
2584
|
const onSignal = (signal) => {
|
|
2540
2585
|
if (stopping) {
|
|
2541
|
-
console.error(
|
|
2586
|
+
console.error(`
|
|
2587
|
+
${mark.fail} ${color.red.bold("forced exit \u2014 some children may survive")}`);
|
|
2542
2588
|
process.exit(130);
|
|
2543
2589
|
}
|
|
2544
2590
|
void shutdown(`received ${signal}`);
|
|
@@ -2552,7 +2598,7 @@ var upCommand = async (name, options = {}) => {
|
|
|
2552
2598
|
await supervisor.up();
|
|
2553
2599
|
await waitUntilIdle(supervisor, () => stopping);
|
|
2554
2600
|
} catch (error) {
|
|
2555
|
-
console.error(
|
|
2601
|
+
console.error(`${mark.fail} ${color.red(error instanceof Error ? error.message : String(error))}`);
|
|
2556
2602
|
exitCode = 1;
|
|
2557
2603
|
} finally {
|
|
2558
2604
|
await shutdown("done");
|
|
@@ -2868,27 +2914,577 @@ var runDoctor = async (stackName, env = process.env) => {
|
|
|
2868
2914
|
};
|
|
2869
2915
|
var doctorCommand = async (stackName, env = process.env) => {
|
|
2870
2916
|
const findings = await runDoctor(stackName, env);
|
|
2871
|
-
const
|
|
2917
|
+
const mark = marks(env);
|
|
2918
|
+
console.log(`${color.bold("doctor")} ${color.gray(glyphs(env).dot)} ${color.cyan.bold(stackName)}
|
|
2919
|
+
`);
|
|
2920
|
+
const lead = { ok: mark.ok, warn: mark.warn, error: mark.fail };
|
|
2921
|
+
const tone = { ok: (text) => text, warn: color.yellow, error: color.red };
|
|
2872
2922
|
for (const finding of findings) {
|
|
2873
|
-
console.log(`${
|
|
2874
|
-
if (finding.hint) console.log(` ${
|
|
2923
|
+
console.log(`${lead[finding.level]} ${tone[finding.level](finding.message)}`);
|
|
2924
|
+
if (finding.hint) console.log(` ${color.gray(finding.hint)}`);
|
|
2875
2925
|
}
|
|
2876
2926
|
const errors = findings.filter((finding) => finding.level === "error").length;
|
|
2877
2927
|
const warnings = findings.filter((finding) => finding.level === "warn").length;
|
|
2928
|
+
const count = (n, noun) => `${n} ${noun}${n === 1 ? "" : "s"}`;
|
|
2929
|
+
const tail = warnings > 0 ? color.yellow(`, ${count(warnings, "warning")}`) : "";
|
|
2878
2930
|
console.log(
|
|
2879
|
-
`
|
|
2880
|
-
${
|
|
2931
|
+
errors === 0 ? `
|
|
2932
|
+
${mark.ok} ${color.green.bold("no blocking problems")}${tail}` : `
|
|
2933
|
+
${mark.fail} ${color.red.bold(count(errors, "problem"))}${tail}`
|
|
2881
2934
|
);
|
|
2882
2935
|
return errors > 0 ? 1 : 0;
|
|
2883
2936
|
};
|
|
2884
2937
|
|
|
2885
|
-
// src/cli/commands/
|
|
2886
|
-
import { mkdirSync as
|
|
2887
|
-
import
|
|
2938
|
+
// src/cli/commands/draft.ts
|
|
2939
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync4, readdirSync as readdirSync3, realpathSync, writeFileSync as writeFileSync2 } from "fs";
|
|
2940
|
+
import path9 from "path";
|
|
2941
|
+
import { isMap, isSeq, parseDocument } from "yaml";
|
|
2888
2942
|
|
|
2889
|
-
// src/cli/commands/
|
|
2890
|
-
|
|
2891
|
-
|
|
2943
|
+
// src/cli/commands/link.ts
|
|
2944
|
+
import { chmodSync, existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync3, readdirSync as readdirSync2, unlinkSync, writeFileSync } from "fs";
|
|
2945
|
+
import path8 from "path";
|
|
2946
|
+
import { fileURLToPath } from "url";
|
|
2947
|
+
var IS_WINDOWS3 = process.platform === "win32";
|
|
2948
|
+
var MARKER = "laracrew-generated";
|
|
2949
|
+
var RESERVED = /* @__PURE__ */ new Set(["laracrew", "node", "npm", "npx", "php", "git", "cd", "ls", "rm", "sh", "bash"]);
|
|
2950
|
+
var VALID_NAME = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
|
|
2951
|
+
var entryScript = (argv = process.argv) => {
|
|
2952
|
+
const main = argv[1];
|
|
2953
|
+
if (!main) return fileURLToPath(import.meta.url);
|
|
2954
|
+
const resolved = path8.resolve(main);
|
|
2955
|
+
if (!resolved.endsWith(".ts")) return resolved;
|
|
2956
|
+
const dist = path8.join(path8.dirname(path8.dirname(resolved)), "dist", "index.js");
|
|
2957
|
+
if (existsSync5(dist)) return dist;
|
|
2958
|
+
throw new ConfigError("run `npm run build` first \u2014 a generated command has to point at dist/index.js");
|
|
2959
|
+
};
|
|
2960
|
+
var currentTarget = (stack) => ({
|
|
2961
|
+
node: process.execPath,
|
|
2962
|
+
entry: entryScript(),
|
|
2963
|
+
stack
|
|
2964
|
+
});
|
|
2965
|
+
var forward = (value) => value.replace(/\\/g, "/");
|
|
2966
|
+
var shShim = ({ node, entry, stack }) => `#!/bin/sh
|
|
2967
|
+
# ${MARKER}: boots the "${stack}" stack. Regenerate with \`laracrew link ${stack}\`.
|
|
2968
|
+
exec "${forward(node)}" "${forward(entry)}" up ${stack} "$@"
|
|
2969
|
+
`;
|
|
2970
|
+
var cmdShim = ({ node, entry, stack }) => `@ECHO OFF\r
|
|
2971
|
+
REM ${MARKER}: boots the "${stack}" stack. Regenerate with \`laracrew link ${stack}\`.\r
|
|
2972
|
+
"${node}" "${entry}" up ${stack} %*\r
|
|
2973
|
+
`;
|
|
2974
|
+
var ps1Shim = ({ node, entry, stack }) => `#!/usr/bin/env pwsh
|
|
2975
|
+
# ${MARKER}: boots the "${stack}" stack. Regenerate with \`laracrew link ${stack}\`.
|
|
2976
|
+
& "${node}" "${entry}" up ${stack} @args
|
|
2977
|
+
exit $LASTEXITCODE
|
|
2978
|
+
`;
|
|
2979
|
+
var shimFiles = (name, target) => {
|
|
2980
|
+
const posix = { file: name, contents: shShim(target), exec: true };
|
|
2981
|
+
if (!IS_WINDOWS3) return [posix];
|
|
2982
|
+
return [
|
|
2983
|
+
posix,
|
|
2984
|
+
// Git Bash reads the extensionless file
|
|
2985
|
+
{ file: `${name}.cmd`, contents: cmdShim(target), exec: false },
|
|
2986
|
+
{ file: `${name}.ps1`, contents: ps1Shim(target), exec: true }
|
|
2987
|
+
];
|
|
2988
|
+
};
|
|
2989
|
+
var pathDirs = (env) => (env.PATH ?? env.Path ?? "").split(path8.delimiter).filter(Boolean);
|
|
2990
|
+
var samePath = (a, b) => IS_WINDOWS3 ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
2991
|
+
var ownBinDir = (env) => {
|
|
2992
|
+
for (const dir of pathDirs(env)) {
|
|
2993
|
+
for (const candidate of IS_WINDOWS3 ? ["laracrew.cmd", "laracrew"] : ["laracrew"]) {
|
|
2994
|
+
try {
|
|
2995
|
+
if (existsSync5(path8.join(dir, candidate))) return dir;
|
|
2996
|
+
} catch {
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
return void 0;
|
|
3001
|
+
};
|
|
3002
|
+
var resolveBinDir = (env = process.env) => {
|
|
3003
|
+
if (env.LARACREW_BIN) return { dir: path8.resolve(env.LARACREW_BIN), onPath: true };
|
|
3004
|
+
const own = ownBinDir(env);
|
|
3005
|
+
if (own) return { dir: own, onPath: true };
|
|
3006
|
+
const conventional = IS_WINDOWS3 ? env.APPDATA ? path8.join(env.APPDATA, "npm") : void 0 : path8.dirname(process.execPath);
|
|
3007
|
+
if (conventional && existsSync5(conventional) && pathDirs(env).some((dir) => samePath(dir, conventional))) {
|
|
3008
|
+
return { dir: conventional, onPath: true };
|
|
3009
|
+
}
|
|
3010
|
+
return { dir: path8.join(paths(env).root, "bin"), onPath: false };
|
|
3011
|
+
};
|
|
3012
|
+
var isOurs = (file) => {
|
|
3013
|
+
try {
|
|
3014
|
+
return readFileSync3(file, "utf8").includes(MARKER);
|
|
3015
|
+
} catch {
|
|
3016
|
+
return false;
|
|
3017
|
+
}
|
|
3018
|
+
};
|
|
3019
|
+
var linkStack = (stackName, options = {}) => {
|
|
3020
|
+
const env = options.env ?? process.env;
|
|
3021
|
+
const { config } = loadStack(stackName, env);
|
|
3022
|
+
const name = options.as ?? config.command ?? stackName;
|
|
3023
|
+
if (!VALID_NAME.test(name)) {
|
|
3024
|
+
throw new ConfigError(
|
|
3025
|
+
`"${name}" is not a usable command name \u2014 letters, digits, dot, dash and underscore only`
|
|
3026
|
+
);
|
|
3027
|
+
}
|
|
3028
|
+
if (RESERVED.has(name.toLowerCase()) && !options.force) {
|
|
3029
|
+
throw new ConfigError(`"${name}" would shadow an important command`, {
|
|
3030
|
+
details: ["pick another with `--as <name>`, or pass --force if you really mean it"]
|
|
3031
|
+
});
|
|
3032
|
+
}
|
|
3033
|
+
const resolved = options.dir ? { dir: path8.resolve(options.dir), onPath: true } : resolveBinDir(env);
|
|
3034
|
+
mkdirSync2(resolved.dir, { recursive: true });
|
|
3035
|
+
const target = currentTarget(stackName);
|
|
3036
|
+
const planned = shimFiles(name, target);
|
|
3037
|
+
let replaced = false;
|
|
3038
|
+
for (const { file } of planned) {
|
|
3039
|
+
const full = path8.join(resolved.dir, file);
|
|
3040
|
+
if (!existsSync5(full)) continue;
|
|
3041
|
+
if (isOurs(full)) {
|
|
3042
|
+
replaced = true;
|
|
3043
|
+
continue;
|
|
3044
|
+
}
|
|
3045
|
+
if (!options.force) {
|
|
3046
|
+
throw new ConfigError(`"${full}" already exists and was not created by laracrew`, {
|
|
3047
|
+
details: ["choose another name with `--as <name>`, or pass --force to overwrite"]
|
|
3048
|
+
});
|
|
3049
|
+
}
|
|
3050
|
+
replaced = true;
|
|
3051
|
+
}
|
|
3052
|
+
const written = [];
|
|
3053
|
+
for (const { file, contents, exec } of planned) {
|
|
3054
|
+
const full = path8.join(resolved.dir, file);
|
|
3055
|
+
writeFileSync(full, contents, "utf8");
|
|
3056
|
+
if (exec && !IS_WINDOWS3) chmodSync(full, 493);
|
|
3057
|
+
else if (exec) {
|
|
3058
|
+
try {
|
|
3059
|
+
chmodSync(full, 493);
|
|
3060
|
+
} catch {
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
written.push(full);
|
|
3064
|
+
}
|
|
3065
|
+
return { name, stack: stackName, dir: resolved.dir, onPath: resolved.onPath, files: written, replaced };
|
|
3066
|
+
};
|
|
3067
|
+
var unlinkCommandName = (name, options = {}) => {
|
|
3068
|
+
const env = options.env ?? process.env;
|
|
3069
|
+
const dir = options.dir ? path8.resolve(options.dir) : resolveBinDir(env).dir;
|
|
3070
|
+
const removed = [];
|
|
3071
|
+
for (const candidate of [name, `${name}.cmd`, `${name}.ps1`]) {
|
|
3072
|
+
const full = path8.join(dir, candidate);
|
|
3073
|
+
if (!existsSync5(full)) continue;
|
|
3074
|
+
if (!isOurs(full)) {
|
|
3075
|
+
throw new ConfigError(`"${full}" was not created by laracrew \u2014 leaving it alone`);
|
|
3076
|
+
}
|
|
3077
|
+
unlinkSync(full);
|
|
3078
|
+
removed.push(full);
|
|
3079
|
+
}
|
|
3080
|
+
if (removed.length === 0) throw new ConfigError(`no laracrew command named "${name}" in ${dir}`);
|
|
3081
|
+
return removed;
|
|
3082
|
+
};
|
|
3083
|
+
var listLinks = (options = {}) => {
|
|
3084
|
+
const env = options.env ?? process.env;
|
|
3085
|
+
const dir = options.dir ? path8.resolve(options.dir) : resolveBinDir(env).dir;
|
|
3086
|
+
if (!existsSync5(dir)) return [];
|
|
3087
|
+
const found = [];
|
|
3088
|
+
for (const entry of readdirSync2(dir).sort()) {
|
|
3089
|
+
if (entry.endsWith(".cmd") || entry.endsWith(".ps1")) continue;
|
|
3090
|
+
const full = path8.join(dir, entry);
|
|
3091
|
+
let contents;
|
|
3092
|
+
try {
|
|
3093
|
+
contents = readFileSync3(full, "utf8");
|
|
3094
|
+
} catch {
|
|
3095
|
+
continue;
|
|
3096
|
+
}
|
|
3097
|
+
if (!contents.includes(MARKER)) continue;
|
|
3098
|
+
const stack = /\bup ([^\s"]+)/.exec(contents)?.[1];
|
|
3099
|
+
if (stack) found.push({ name: entry, stack, file: full });
|
|
3100
|
+
}
|
|
3101
|
+
return found;
|
|
3102
|
+
};
|
|
3103
|
+
var linkCommand = (stackName, options, env = process.env) => {
|
|
3104
|
+
const stacks = options.all ? listStacks(env) : stackName ? [stackName] : [];
|
|
3105
|
+
if (stacks.length === 0) {
|
|
3106
|
+
throw new ConfigError("name a stack to link, or pass --all");
|
|
3107
|
+
}
|
|
3108
|
+
const results = [];
|
|
3109
|
+
for (const stack of stacks) {
|
|
3110
|
+
if (options.all) {
|
|
3111
|
+
const { config } = loadStack(stack, env);
|
|
3112
|
+
if (!config.command) continue;
|
|
3113
|
+
}
|
|
3114
|
+
results.push(
|
|
3115
|
+
linkStack(stack, {
|
|
3116
|
+
...options.as ? { as: options.as } : {},
|
|
3117
|
+
...options.dir ? { dir: options.dir } : {},
|
|
3118
|
+
...options.force ? { force: options.force } : {},
|
|
3119
|
+
env
|
|
3120
|
+
})
|
|
3121
|
+
);
|
|
3122
|
+
}
|
|
3123
|
+
const mark = marks(env);
|
|
3124
|
+
if (results.length === 0) {
|
|
3125
|
+
console.log(
|
|
3126
|
+
`${mark.warn} ${color.yellow("nothing to link \u2014 add `command: <name>` to the stacks you want a command for")}`
|
|
3127
|
+
);
|
|
3128
|
+
return 0;
|
|
3129
|
+
}
|
|
3130
|
+
for (const result of results) {
|
|
3131
|
+
const verb = result.replaced ? "updated" : "created";
|
|
3132
|
+
console.log(
|
|
3133
|
+
`${mark.ok} ${color.green(verb)} ${color.bold(result.name)} ${mark.arrow} ${color.gray(`laracrew up ${result.stack}`)}`
|
|
3134
|
+
);
|
|
3135
|
+
}
|
|
3136
|
+
const first = results[0];
|
|
3137
|
+
console.log(`
|
|
3138
|
+
${color.gray(`in ${first.dir}`)}`);
|
|
3139
|
+
if (!first.onPath) {
|
|
3140
|
+
console.log(
|
|
3141
|
+
`
|
|
3142
|
+
${mark.warn} ${color.yellow("that directory is not on your PATH.")} Add it once:
|
|
3143
|
+
` + (IS_WINDOWS3 ? ` ${color.cyan(`setx PATH "%PATH%;${first.dir}"`)} ${color.gray("(then reopen the terminal)")}` : ` ${color.cyan(`echo 'export PATH="$PATH:${first.dir}"' >> ~/.bashrc`)}`)
|
|
3144
|
+
);
|
|
3145
|
+
} else {
|
|
3146
|
+
console.log(`
|
|
3147
|
+
Run it from anywhere: ${color.cyan.bold(first.name)}`);
|
|
3148
|
+
console.log(color.gray(`Flags pass straight through: ${first.name} --only workers`));
|
|
3149
|
+
}
|
|
3150
|
+
return 0;
|
|
3151
|
+
};
|
|
3152
|
+
var unlinkCommandAction = (name, env = process.env) => {
|
|
3153
|
+
const removed = unlinkCommandName(name, { env });
|
|
3154
|
+
const mark = marks(env);
|
|
3155
|
+
for (const file of removed) console.log(`${mark.ok} removed ${color.gray(file)}`);
|
|
3156
|
+
return 0;
|
|
3157
|
+
};
|
|
3158
|
+
|
|
3159
|
+
// src/cli/commands/draft.ts
|
|
3160
|
+
var SUFFIX = ".laracrew.yaml";
|
|
3161
|
+
var DRAFT_FILE = /^(.+)\.laracrew\.ya?ml$/i;
|
|
3162
|
+
var NAME = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
|
|
3163
|
+
var SERVICE_NAME = /^[A-Za-z0-9][A-Za-z0-9_.:-]*$/;
|
|
3164
|
+
var RUNNERS = /* @__PURE__ */ new Set(["node", "npm", "npx", "yarn", "pnpm", "bun", "deno", "python", "python3", "php", "run", "exec", "poetry"]);
|
|
3165
|
+
var forward2 = (value) => value.replace(/\\/g, "/");
|
|
3166
|
+
var canonical = (value) => {
|
|
3167
|
+
try {
|
|
3168
|
+
return realpathSync.native(value);
|
|
3169
|
+
} catch {
|
|
3170
|
+
return path9.resolve(value);
|
|
3171
|
+
}
|
|
3172
|
+
};
|
|
3173
|
+
var samePath2 = (a, b) => {
|
|
3174
|
+
const [x, y] = [canonical(a), canonical(b)];
|
|
3175
|
+
return process.platform === "win32" ? x.toLowerCase() === y.toLowerCase() : x === y;
|
|
3176
|
+
};
|
|
3177
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3178
|
+
var draftFile = (name, cwd = process.cwd()) => path9.join(cwd, `${name}${SUFFIX}`);
|
|
3179
|
+
var listDrafts = (cwd = process.cwd()) => readdirSync3(cwd).map((entry) => ({ entry, match: DRAFT_FILE.exec(entry) })).filter((item) => item.match !== null).map(({ entry, match }) => ({ name: match[1], file: path9.join(cwd, entry) })).sort((a, b) => a.name.localeCompare(b.name));
|
|
3180
|
+
var resolveDraft = (cwd = process.cwd(), name, pick = "--draft <name>") => {
|
|
3181
|
+
if (name) {
|
|
3182
|
+
const file = draftFile(name, cwd);
|
|
3183
|
+
if (!existsSync6(file)) {
|
|
3184
|
+
throw new ConfigError(`no draft named "${name}" in ${cwd}`, { details: ["start one with `laracrew draft <name>`"] });
|
|
3185
|
+
}
|
|
3186
|
+
return { name, file };
|
|
3187
|
+
}
|
|
3188
|
+
const drafts = listDrafts(cwd);
|
|
3189
|
+
if (drafts.length === 1) return drafts[0];
|
|
3190
|
+
if (drafts.length === 0) {
|
|
3191
|
+
throw new ConfigError(`no draft in ${cwd}`, { details: ["start one with `laracrew draft <name>`"] });
|
|
3192
|
+
}
|
|
3193
|
+
throw new ConfigError(`several drafts here: ${drafts.map((draft) => draft.name).join(", ")} \u2014 pick one with ${pick}`);
|
|
3194
|
+
};
|
|
3195
|
+
var template = (name) => `# Built with \`laracrew draft\`. Add services with \`laracrew draft add\`, install the stack with
|
|
3196
|
+
# \`laracrew draft publish\`, or edit by hand \u2014 this is an ordinary stack.yaml.
|
|
3197
|
+
#
|
|
3198
|
+
# laracrew draft add --project api --path C:/work/api --command "php artisan queue:work"
|
|
3199
|
+
# laracrew draft add --project api --command "php artisan schedule:work"
|
|
3200
|
+
# laracrew draft add --command "npm run dev" --default-state stopped
|
|
3201
|
+
name: ${name}
|
|
3202
|
+
command: ${name} # \`laracrew link ${name}\` installs this as a global command
|
|
3203
|
+
projects: {}
|
|
3204
|
+
services: []
|
|
3205
|
+
`;
|
|
3206
|
+
var createDraft = (name, options = {}) => {
|
|
3207
|
+
const cwd = options.cwd ?? process.cwd();
|
|
3208
|
+
if (!NAME.test(name)) {
|
|
3209
|
+
throw new ConfigError(`"${name}" is not a usable stack name \u2014 letters, digits, dot, dash and underscore only`);
|
|
3210
|
+
}
|
|
3211
|
+
if (name === "add" || name === "publish") {
|
|
3212
|
+
throw new ConfigError(`"${name}" would clash with \`laracrew draft ${name}\` \u2014 pick another name`);
|
|
3213
|
+
}
|
|
3214
|
+
const file = draftFile(name, cwd);
|
|
3215
|
+
if (existsSync6(file) && !options.force) {
|
|
3216
|
+
throw new ConfigError(`${path9.basename(file)} already exists here`, {
|
|
3217
|
+
details: ["add to it with `laracrew draft add`, or pass --force to start over"]
|
|
3218
|
+
});
|
|
3219
|
+
}
|
|
3220
|
+
writeFileSync2(file, template(name), "utf8");
|
|
3221
|
+
return file;
|
|
3222
|
+
};
|
|
3223
|
+
var readDraft = (file) => {
|
|
3224
|
+
const doc = parseDocument(readFileSync4(file, "utf8"), { prettyErrors: true });
|
|
3225
|
+
const [problem] = doc.errors;
|
|
3226
|
+
if (problem) throw new ConfigError(`invalid YAML: ${problem.message}`, { file });
|
|
3227
|
+
if (!isMap(doc.contents)) throw new ConfigError("the draft should be a YAML mapping (name:, services: \u2026)", { file });
|
|
3228
|
+
return doc;
|
|
3229
|
+
};
|
|
3230
|
+
var mapAt = (doc, key, file) => {
|
|
3231
|
+
let node = doc.get(key);
|
|
3232
|
+
if (node === void 0 || node === null) {
|
|
3233
|
+
node = doc.createNode({});
|
|
3234
|
+
doc.set(key, node);
|
|
3235
|
+
}
|
|
3236
|
+
if (!isMap(node)) throw new ConfigError(`\`${key}:\` should be a map`, { file });
|
|
3237
|
+
return node;
|
|
3238
|
+
};
|
|
3239
|
+
var seqAt = (doc, key, file) => {
|
|
3240
|
+
let node = doc.get(key);
|
|
3241
|
+
if (node === void 0 || node === null) {
|
|
3242
|
+
node = doc.createNode([]);
|
|
3243
|
+
doc.set(key, node);
|
|
3244
|
+
}
|
|
3245
|
+
if (!isSeq(node)) throw new ConfigError(`\`${key}:\` should be a list`, { file });
|
|
3246
|
+
return node;
|
|
3247
|
+
};
|
|
3248
|
+
var deriveServiceName = (command, project) => {
|
|
3249
|
+
const artisan = /\bartisan\s+([A-Za-z0-9:_.-]+)/.exec(command);
|
|
3250
|
+
let verb = artisan?.[1];
|
|
3251
|
+
if (!verb) {
|
|
3252
|
+
const words = command.trim().split(/\s+/).filter((word2) => word2 && !word2.startsWith("-")).map((word2) => word2.split(/[\\/]/).pop() ?? word2);
|
|
3253
|
+
const word = words.find((candidate) => !RUNNERS.has(candidate.toLowerCase())) ?? words[0] ?? "service";
|
|
3254
|
+
verb = word.replace(/\.[A-Za-z0-9]+$/, "");
|
|
3255
|
+
}
|
|
3256
|
+
const clean = verb.toLowerCase().replace(/[^a-z0-9:_.-]+/g, "-").replace(/^[-.:]+|[-.:]+$/g, "") || "service";
|
|
3257
|
+
return project ? `${project}:${clean}` : clean;
|
|
3258
|
+
};
|
|
3259
|
+
var uniqueName = (base, taken) => {
|
|
3260
|
+
if (!taken.has(base)) return base;
|
|
3261
|
+
for (let n = 2; ; n += 1) {
|
|
3262
|
+
const candidate = `${base}-${n}`;
|
|
3263
|
+
if (!taken.has(candidate)) return candidate;
|
|
3264
|
+
}
|
|
3265
|
+
};
|
|
3266
|
+
var parseState = (value) => {
|
|
3267
|
+
if (value === void 0 || value === "running") return true;
|
|
3268
|
+
if (value === "stopped") return false;
|
|
3269
|
+
throw new ConfigError(`--default-state is "running" or "stopped", not "${value}"`);
|
|
3270
|
+
};
|
|
3271
|
+
var addToDraft = (options) => {
|
|
3272
|
+
const cwd = options.cwd ?? process.cwd();
|
|
3273
|
+
const command = options.command.trim();
|
|
3274
|
+
if (!command) throw new ConfigError('--command needs something to run, e.g. "php artisan queue:work"');
|
|
3275
|
+
const autostart = parseState(options.state);
|
|
3276
|
+
if (options.project !== void 0 && !NAME.test(options.project)) {
|
|
3277
|
+
throw new ConfigError(`"${options.project}" is not a usable project key \u2014 letters, digits, dot, dash and underscore only`);
|
|
3278
|
+
}
|
|
3279
|
+
const { name: draftName, file } = resolveDraft(cwd, options.draft);
|
|
3280
|
+
const doc = readDraft(file);
|
|
3281
|
+
let project;
|
|
3282
|
+
if (options.project) {
|
|
3283
|
+
const projects = mapAt(doc, "projects", file);
|
|
3284
|
+
const wanted = options.path ? forward2(path9.resolve(cwd, options.path)) : void 0;
|
|
3285
|
+
const existing = projects.get(options.project);
|
|
3286
|
+
if (existing !== void 0 && existing !== null) {
|
|
3287
|
+
const current = isMap(existing) ? String(existing.get("path") ?? "") : "";
|
|
3288
|
+
if (wanted && !samePath2(current, wanted)) {
|
|
3289
|
+
throw new ConfigError(`project "${options.project}" already points at ${current}`, {
|
|
3290
|
+
file,
|
|
3291
|
+
details: ["a draft keeps one path per project \u2014 leave --path off to reuse it, or pick another key"]
|
|
3292
|
+
});
|
|
3293
|
+
}
|
|
3294
|
+
project = { key: options.project, path: current, created: false };
|
|
3295
|
+
} else {
|
|
3296
|
+
const dir = wanted ?? forward2(cwd);
|
|
3297
|
+
projects.set(options.project, doc.createNode({ path: dir }));
|
|
3298
|
+
projects.flow = false;
|
|
3299
|
+
project = { key: options.project, path: dir, created: true };
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
const services = seqAt(doc, "services", file);
|
|
3303
|
+
const taken = new Set(services.items.map((item) => isMap(item) ? String(item.get("name") ?? "") : ""));
|
|
3304
|
+
let name;
|
|
3305
|
+
if (options.name) {
|
|
3306
|
+
if (!SERVICE_NAME.test(options.name)) {
|
|
3307
|
+
throw new ConfigError(`"${options.name}" is not a usable service name \u2014 letters, digits, colon, dot, dash and underscore only`);
|
|
3308
|
+
}
|
|
3309
|
+
if (taken.has(options.name)) throw new ConfigError(`a service named "${options.name}" is already in the draft`, { file });
|
|
3310
|
+
name = options.name;
|
|
3311
|
+
} else {
|
|
3312
|
+
name = uniqueName(deriveServiceName(command, options.project), taken);
|
|
3313
|
+
}
|
|
3314
|
+
const service = { name };
|
|
3315
|
+
if (options.project) service.project = options.project;
|
|
3316
|
+
service.cmd = command;
|
|
3317
|
+
const serviceCwd = options.project ? void 0 : forward2(path9.resolve(cwd, options.path ?? "."));
|
|
3318
|
+
if (serviceCwd) service.cwd = serviceCwd;
|
|
3319
|
+
if (!autostart) service.autostart = false;
|
|
3320
|
+
services.add(doc.createNode(service));
|
|
3321
|
+
services.flow = false;
|
|
3322
|
+
writeFileSync2(file, doc.toString(), "utf8");
|
|
3323
|
+
return {
|
|
3324
|
+
draft: draftName,
|
|
3325
|
+
file,
|
|
3326
|
+
service: { name, cmd: command, autostart, ...serviceCwd ? { cwd: serviceCwd } : {} },
|
|
3327
|
+
...project ? { project } : {},
|
|
3328
|
+
services: services.items.length
|
|
3329
|
+
};
|
|
3330
|
+
};
|
|
3331
|
+
var describeDraft = (cwd = process.cwd(), name) => {
|
|
3332
|
+
const ref = resolveDraft(cwd, name, "laracrew draft <name>");
|
|
3333
|
+
const raw = parseYaml(readFileSync4(ref.file, "utf8"), ref.file);
|
|
3334
|
+
const record = isRecord2(raw) ? raw : {};
|
|
3335
|
+
const stackName = typeof record.name === "string" ? record.name : ref.name;
|
|
3336
|
+
const projects = isRecord2(record.projects) ? Object.entries(record.projects).map(([key, value]) => ({
|
|
3337
|
+
key,
|
|
3338
|
+
path: isRecord2(value) && typeof value.path === "string" ? value.path : ""
|
|
3339
|
+
})) : [];
|
|
3340
|
+
const services = Array.isArray(record.services) ? record.services.filter(isRecord2).map((entry) => ({
|
|
3341
|
+
name: typeof entry.name === "string" ? entry.name : "?",
|
|
3342
|
+
cmd: Array.isArray(entry.cmd) ? entry.cmd.map(String).join(" ") : typeof entry.cmd === "string" ? entry.cmd : "",
|
|
3343
|
+
...typeof entry.project === "string" ? { project: entry.project } : {},
|
|
3344
|
+
autostart: entry.autostart !== false
|
|
3345
|
+
})) : [];
|
|
3346
|
+
return {
|
|
3347
|
+
name: ref.name,
|
|
3348
|
+
file: ref.file,
|
|
3349
|
+
stackName,
|
|
3350
|
+
command: typeof record.command === "string" ? record.command : stackName,
|
|
3351
|
+
projects,
|
|
3352
|
+
services
|
|
3353
|
+
};
|
|
3354
|
+
};
|
|
3355
|
+
var publishDraft = (options = {}) => {
|
|
3356
|
+
const cwd = options.cwd ?? process.cwd();
|
|
3357
|
+
const env = options.env ?? process.env;
|
|
3358
|
+
const { name: draftName, file } = resolveDraft(cwd, options.draft);
|
|
3359
|
+
const source = readFileSync4(file, "utf8");
|
|
3360
|
+
const raw = parseYaml(source, file);
|
|
3361
|
+
if (isRecord2(raw) && Array.isArray(raw.services) && raw.services.length === 0) {
|
|
3362
|
+
throw new ConfigError("the draft has no services yet", {
|
|
3363
|
+
file,
|
|
3364
|
+
details: ['add one with `laracrew draft add --command "\u2026"`']
|
|
3365
|
+
});
|
|
3366
|
+
}
|
|
3367
|
+
const config = validateStack(raw, file);
|
|
3368
|
+
const target = stackFile(config.name, env);
|
|
3369
|
+
const replaced = existsSync6(target);
|
|
3370
|
+
if (replaced && !options.force) {
|
|
3371
|
+
throw new ConfigError(`a stack named "${config.name}" already exists`, {
|
|
3372
|
+
file: target,
|
|
3373
|
+
details: ["pass --force to replace it, or change `name:` in the draft"]
|
|
3374
|
+
});
|
|
3375
|
+
}
|
|
3376
|
+
mkdirSync3(stackDir(config.name, env), { recursive: true });
|
|
3377
|
+
writeFileSync2(target, source, "utf8");
|
|
3378
|
+
return { name: config.name, draft: draftName, file, target, replaced, config };
|
|
3379
|
+
};
|
|
3380
|
+
var plural = (count, noun) => `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
3381
|
+
var draftCommand = (name, options = {}, cwd = process.cwd(), env = process.env) => {
|
|
3382
|
+
const mark = marks(env);
|
|
3383
|
+
if (name && (options.force || !existsSync6(draftFile(name, cwd)))) {
|
|
3384
|
+
const file = createDraft(name, { cwd, ...options.force ? { force: true } : {} });
|
|
3385
|
+
console.log(`${mark.ok} started draft ${color.bold(name)} ${mark.arrow} ${color.gray(path9.basename(file))}`);
|
|
3386
|
+
console.log(
|
|
3387
|
+
`
|
|
3388
|
+
${nextSteps(
|
|
3389
|
+
[
|
|
3390
|
+
["laracrew draft add", `--project api --path C:/work/api --command ${color.green('"php artisan queue:work"')}`],
|
|
3391
|
+
["", `--project api --command ${color.green('"php artisan schedule:work"')} ${color.gray("the path is remembered")}`],
|
|
3392
|
+
["", `--command ${color.green('"npm run dev"')} --default-state stopped ${color.gray("idle until you press s")}`],
|
|
3393
|
+
["laracrew draft", "show what the draft holds so far"],
|
|
3394
|
+
["laracrew draft publish", `install it as the ${color.cyan(name)} stack in ~/.laracrew`]
|
|
3395
|
+
],
|
|
3396
|
+
env
|
|
3397
|
+
)}
|
|
3398
|
+
`
|
|
3399
|
+
);
|
|
3400
|
+
return 0;
|
|
3401
|
+
}
|
|
3402
|
+
const draft = describeDraft(cwd, name);
|
|
3403
|
+
console.log(`${color.bold("draft")} ${color.gray("\xB7")} ${color.cyan.bold(draft.stackName)} ${color.gray(path9.basename(draft.file))}`);
|
|
3404
|
+
if (draft.command !== draft.stackName) console.log(` ${color.gray(`command: ${draft.command}`)}`);
|
|
3405
|
+
console.log(`
|
|
3406
|
+
${color.bold("PROJECTS")} ${color.gray(`(${draft.projects.length})`)}`);
|
|
3407
|
+
if (draft.projects.length === 0) console.log(color.gray(" none \u2014 `--project <key> --path <dir>` on the next add"));
|
|
3408
|
+
const keyWidth = Math.max(4, ...draft.projects.map((project) => project.key.length));
|
|
3409
|
+
for (const project of draft.projects) {
|
|
3410
|
+
console.log(` ${color.magenta.bold(padEnd(project.key, keyWidth))} ${project.path}`);
|
|
3411
|
+
}
|
|
3412
|
+
console.log(`
|
|
3413
|
+
${color.bold("SERVICES")} ${color.gray(`(${draft.services.length})`)}`);
|
|
3414
|
+
if (draft.services.length === 0) console.log(color.gray(' none yet \u2014 `laracrew draft add --command "\u2026"`'));
|
|
3415
|
+
const nameWidth = Math.max(4, ...draft.services.map((service) => service.name.length));
|
|
3416
|
+
for (const service of draft.services) {
|
|
3417
|
+
const idle = service.autostart ? "" : ` ${color.yellow("idle until started")}`;
|
|
3418
|
+
console.log(` ${color.cyan.bold(padEnd(service.name, nameWidth))} ${service.cmd}${idle}`);
|
|
3419
|
+
}
|
|
3420
|
+
console.log(
|
|
3421
|
+
`
|
|
3422
|
+
${nextSteps(
|
|
3423
|
+
[
|
|
3424
|
+
["laracrew draft add", `--command ${color.green('"\u2026"')} to add another`],
|
|
3425
|
+
["laracrew draft publish", `install it as the ${color.cyan(draft.stackName)} stack`]
|
|
3426
|
+
],
|
|
3427
|
+
env
|
|
3428
|
+
)}
|
|
3429
|
+
`
|
|
3430
|
+
);
|
|
3431
|
+
return 0;
|
|
3432
|
+
};
|
|
3433
|
+
var draftAddCommand = (options, cwd = process.cwd(), env = process.env) => {
|
|
3434
|
+
const result = addToDraft({ ...options, cwd });
|
|
3435
|
+
const mark = marks(env);
|
|
3436
|
+
const idle = result.service.autostart ? "" : ` ${color.yellow("idle until started")}`;
|
|
3437
|
+
console.log(`${mark.ok} added ${color.bold(result.service.name)} ${mark.arrow} ${color.gray(result.service.cmd)}${idle}`);
|
|
3438
|
+
if (result.project?.created) {
|
|
3439
|
+
const guessed = options.path ? "" : ` ${color.yellow("this folder \u2014 pass --path if that is wrong")}`;
|
|
3440
|
+
console.log(` ${mark.ok} project ${color.magenta.bold(result.project.key)} ${mark.arrow} ${color.gray(result.project.path)}${guessed}`);
|
|
3441
|
+
}
|
|
3442
|
+
if (result.service.cwd) console.log(` ${color.gray(`runs in ${result.service.cwd}`)}`);
|
|
3443
|
+
console.log(
|
|
3444
|
+
` ${color.gray(`${plural(result.services, "service")} in ${path9.basename(result.file)} \u2014 \`laracrew draft publish\` when done`)}`
|
|
3445
|
+
);
|
|
3446
|
+
return 0;
|
|
3447
|
+
};
|
|
3448
|
+
var draftPublishCommand = (options = {}, cwd = process.cwd(), env = process.env) => {
|
|
3449
|
+
const result = publishDraft({
|
|
3450
|
+
cwd,
|
|
3451
|
+
env,
|
|
3452
|
+
...options.draft ? { draft: options.draft } : {},
|
|
3453
|
+
...options.force ? { force: true } : {}
|
|
3454
|
+
});
|
|
3455
|
+
const mark = marks(env);
|
|
3456
|
+
const command = result.config.command ?? result.name;
|
|
3457
|
+
console.log(
|
|
3458
|
+
`${mark.ok} ${color.green(result.replaced ? "replaced" : "published")} ${color.bold(result.name)} ${mark.arrow} ${color.gray(result.target)}`
|
|
3459
|
+
);
|
|
3460
|
+
console.log(
|
|
3461
|
+
` ${color.gray(
|
|
3462
|
+
`${plural(Object.keys(result.config.projects).length, "project")}, ${plural(result.config.services.length, "service")}; the draft stays at ${path9.basename(result.file)}`
|
|
3463
|
+
)}`
|
|
3464
|
+
);
|
|
3465
|
+
if (options.link) {
|
|
3466
|
+
console.log("");
|
|
3467
|
+
const code = linkCommand(result.name, {}, env);
|
|
3468
|
+
if (code !== 0) return code;
|
|
3469
|
+
}
|
|
3470
|
+
const steps = [
|
|
3471
|
+
[`laracrew doctor ${result.name}`, "check paths, ports and PHP before the first boot"],
|
|
3472
|
+
[`laracrew up ${result.name}`, "boot it"]
|
|
3473
|
+
];
|
|
3474
|
+
if (!options.link) steps.push([`laracrew link ${result.name}`, `type ${color.cyan(command)} from anywhere to boot it`]);
|
|
3475
|
+
console.log(`
|
|
3476
|
+
${nextSteps(steps, env)}
|
|
3477
|
+
`);
|
|
3478
|
+
return 0;
|
|
3479
|
+
};
|
|
3480
|
+
|
|
3481
|
+
// src/cli/commands/init.ts
|
|
3482
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3, existsSync as existsSync7 } from "fs";
|
|
3483
|
+
import path10 from "path";
|
|
3484
|
+
|
|
3485
|
+
// src/cli/commands/templates.ts
|
|
3486
|
+
var CONFIG_YAML = `# laracrew global preferences
|
|
3487
|
+
# theme: auto | dark | light | mono | high-contrast
|
|
2892
3488
|
theme: auto
|
|
2893
3489
|
|
|
2894
3490
|
# Polling interval for queue depth and stream lag, in milliseconds.
|
|
@@ -3068,6 +3664,7 @@ secrets in it.
|
|
|
3068
3664
|
Start here:
|
|
3069
3665
|
|
|
3070
3666
|
laracrew init --examples # add a demo stack and a two-project template
|
|
3667
|
+
laracrew draft <name> # build a stack in a project folder, one command at a time
|
|
3071
3668
|
laracrew ls # what is defined
|
|
3072
3669
|
laracrew doctor <stack> # check a stack before booting it
|
|
3073
3670
|
laracrew link <stack> # give a stack its own global command
|
|
@@ -3079,278 +3676,65 @@ var runInit = (env = process.env, options = {}) => {
|
|
|
3079
3676
|
const created = [];
|
|
3080
3677
|
const skipped = [];
|
|
3081
3678
|
for (const dir of [home2.root, home2.stacksDir, home2.tasksDir, home2.fragmentsDir, home2.logsDir, home2.runDir]) {
|
|
3082
|
-
|
|
3679
|
+
mkdirSync4(dir, { recursive: true });
|
|
3083
3680
|
}
|
|
3084
3681
|
const files = [
|
|
3085
3682
|
[home2.configFile, CONFIG_YAML],
|
|
3086
3683
|
[home2.projectsFile, PROJECTS_YAML],
|
|
3087
|
-
[
|
|
3684
|
+
[path10.join(home2.root, "README.md"), HOME_README]
|
|
3088
3685
|
];
|
|
3089
3686
|
if (options.examples) {
|
|
3090
|
-
|
|
3091
|
-
|
|
3687
|
+
mkdirSync4(path10.join(home2.stacksDir, "example"), { recursive: true });
|
|
3688
|
+
mkdirSync4(path10.join(home2.stacksDir, "dual"), { recursive: true });
|
|
3092
3689
|
files.push(
|
|
3093
|
-
[
|
|
3094
|
-
[
|
|
3095
|
-
[
|
|
3690
|
+
[path10.join(home2.stacksDir, "example", "stack.yaml"), EXAMPLE_STACK_YAML],
|
|
3691
|
+
[path10.join(home2.stacksDir, "dual", "stack.yaml"), REAL_STACK_TEMPLATE],
|
|
3692
|
+
[path10.join(home2.tasksDir, "reset.yaml"), RESET_TASK_YAML]
|
|
3096
3693
|
);
|
|
3097
3694
|
}
|
|
3098
3695
|
for (const [file, contents] of files) {
|
|
3099
|
-
if (
|
|
3696
|
+
if (existsSync7(file)) {
|
|
3100
3697
|
skipped.push(file);
|
|
3101
3698
|
continue;
|
|
3102
3699
|
}
|
|
3103
|
-
|
|
3700
|
+
writeFileSync3(file, contents, "utf8");
|
|
3104
3701
|
created.push(file);
|
|
3105
3702
|
}
|
|
3106
3703
|
return { root: home2.root, created, skipped, examples: options.examples ?? false };
|
|
3107
3704
|
};
|
|
3108
3705
|
var initCommand = (options = {}, env = process.env) => {
|
|
3109
3706
|
const result = runInit(env, options);
|
|
3110
|
-
|
|
3111
|
-
|
|
3707
|
+
const mark = marks(env);
|
|
3708
|
+
console.log(`${color.bold("laracrew home")} ${color.gray(result.root)}`);
|
|
3709
|
+
for (const file of result.created) console.log(` ${mark.ok} ${path10.relative(result.root, file)}`);
|
|
3112
3710
|
for (const file of result.skipped) {
|
|
3113
|
-
console.log(` ${
|
|
3114
|
-
}
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
`)
|
|
3123
|
-
|
|
3124
|
-
|
|
3711
|
+
console.log(` ${mark.skip} ${color.gray(`${path10.relative(result.root, file)} (kept)`)}`);
|
|
3712
|
+
}
|
|
3713
|
+
const steps = result.examples ? [
|
|
3714
|
+
["laracrew up example", "run the demo stack \u2014 proves it works, no Laravel needed"],
|
|
3715
|
+
["laracrew link example", "install it as a global command you can type from anywhere"],
|
|
3716
|
+
["laracrew doctor dual", `check the real stack once ${color.gray(path10.join(result.root, "projects.yaml"))}`],
|
|
3717
|
+
["", `has your project paths, then ${color.cyan("laracrew up dual")}`]
|
|
3718
|
+
] : [
|
|
3719
|
+
["laracrew init --examples", "add a runnable demo stack and a two-project template,"],
|
|
3720
|
+
["", `or write your own at ${color.gray(path10.join(result.root, "stacks", "<name>", "stack.yaml"))}`],
|
|
3721
|
+
["laracrew draft <name>", "or build one in a project folder, one command at a time"],
|
|
3722
|
+
["laracrew up <name>", `boot it \u2014 ${color.cyan("laracrew ls")} shows what is defined`]
|
|
3723
|
+
];
|
|
3125
3724
|
console.log(`
|
|
3126
|
-
${
|
|
3127
|
-
${paint("cyan", "laracrew init --examples")} add a runnable demo stack and a two-project template
|
|
3128
|
-
or write your own at ${paint("gray", path8.join(result.root, "stacks", "<name>", "stack.yaml"))}
|
|
3129
|
-
then ${paint("cyan", "laracrew ls")} and ${paint("cyan", "laracrew up <name>")}
|
|
3725
|
+
${nextSteps(steps, env)}
|
|
3130
3726
|
`);
|
|
3131
3727
|
};
|
|
3132
3728
|
|
|
3133
|
-
// src/cli/commands/link.ts
|
|
3134
|
-
import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
3135
|
-
import path9 from "path";
|
|
3136
|
-
import { fileURLToPath } from "url";
|
|
3137
|
-
var IS_WINDOWS3 = process.platform === "win32";
|
|
3138
|
-
var MARKER = "laracrew-generated";
|
|
3139
|
-
var RESERVED = /* @__PURE__ */ new Set(["laracrew", "node", "npm", "npx", "php", "git", "cd", "ls", "rm", "sh", "bash"]);
|
|
3140
|
-
var VALID_NAME = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
|
|
3141
|
-
var entryScript = (argv = process.argv) => {
|
|
3142
|
-
const main = argv[1];
|
|
3143
|
-
if (!main) return fileURLToPath(import.meta.url);
|
|
3144
|
-
const resolved = path9.resolve(main);
|
|
3145
|
-
if (!resolved.endsWith(".ts")) return resolved;
|
|
3146
|
-
const dist = path9.join(path9.dirname(path9.dirname(resolved)), "dist", "index.js");
|
|
3147
|
-
if (existsSync6(dist)) return dist;
|
|
3148
|
-
throw new ConfigError("run `npm run build` first \u2014 a generated command has to point at dist/index.js");
|
|
3149
|
-
};
|
|
3150
|
-
var currentTarget = (stack) => ({
|
|
3151
|
-
node: process.execPath,
|
|
3152
|
-
entry: entryScript(),
|
|
3153
|
-
stack
|
|
3154
|
-
});
|
|
3155
|
-
var forward = (value) => value.replace(/\\/g, "/");
|
|
3156
|
-
var shShim = ({ node, entry, stack }) => `#!/bin/sh
|
|
3157
|
-
# ${MARKER}: boots the "${stack}" stack. Regenerate with \`laracrew link ${stack}\`.
|
|
3158
|
-
exec "${forward(node)}" "${forward(entry)}" up ${stack} "$@"
|
|
3159
|
-
`;
|
|
3160
|
-
var cmdShim = ({ node, entry, stack }) => `@ECHO OFF\r
|
|
3161
|
-
REM ${MARKER}: boots the "${stack}" stack. Regenerate with \`laracrew link ${stack}\`.\r
|
|
3162
|
-
"${node}" "${entry}" up ${stack} %*\r
|
|
3163
|
-
`;
|
|
3164
|
-
var ps1Shim = ({ node, entry, stack }) => `#!/usr/bin/env pwsh
|
|
3165
|
-
# ${MARKER}: boots the "${stack}" stack. Regenerate with \`laracrew link ${stack}\`.
|
|
3166
|
-
& "${node}" "${entry}" up ${stack} @args
|
|
3167
|
-
exit $LASTEXITCODE
|
|
3168
|
-
`;
|
|
3169
|
-
var shimFiles = (name, target) => {
|
|
3170
|
-
const posix = { file: name, contents: shShim(target), exec: true };
|
|
3171
|
-
if (!IS_WINDOWS3) return [posix];
|
|
3172
|
-
return [
|
|
3173
|
-
posix,
|
|
3174
|
-
// Git Bash reads the extensionless file
|
|
3175
|
-
{ file: `${name}.cmd`, contents: cmdShim(target), exec: false },
|
|
3176
|
-
{ file: `${name}.ps1`, contents: ps1Shim(target), exec: true }
|
|
3177
|
-
];
|
|
3178
|
-
};
|
|
3179
|
-
var pathDirs = (env) => (env.PATH ?? env.Path ?? "").split(path9.delimiter).filter(Boolean);
|
|
3180
|
-
var samePath = (a, b) => IS_WINDOWS3 ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
3181
|
-
var ownBinDir = (env) => {
|
|
3182
|
-
for (const dir of pathDirs(env)) {
|
|
3183
|
-
for (const candidate of IS_WINDOWS3 ? ["laracrew.cmd", "laracrew"] : ["laracrew"]) {
|
|
3184
|
-
try {
|
|
3185
|
-
if (existsSync6(path9.join(dir, candidate))) return dir;
|
|
3186
|
-
} catch {
|
|
3187
|
-
}
|
|
3188
|
-
}
|
|
3189
|
-
}
|
|
3190
|
-
return void 0;
|
|
3191
|
-
};
|
|
3192
|
-
var resolveBinDir = (env = process.env) => {
|
|
3193
|
-
if (env.LARACREW_BIN) return { dir: path9.resolve(env.LARACREW_BIN), onPath: true };
|
|
3194
|
-
const own = ownBinDir(env);
|
|
3195
|
-
if (own) return { dir: own, onPath: true };
|
|
3196
|
-
const conventional = IS_WINDOWS3 ? env.APPDATA ? path9.join(env.APPDATA, "npm") : void 0 : path9.dirname(process.execPath);
|
|
3197
|
-
if (conventional && existsSync6(conventional) && pathDirs(env).some((dir) => samePath(dir, conventional))) {
|
|
3198
|
-
return { dir: conventional, onPath: true };
|
|
3199
|
-
}
|
|
3200
|
-
return { dir: path9.join(paths(env).root, "bin"), onPath: false };
|
|
3201
|
-
};
|
|
3202
|
-
var isOurs = (file) => {
|
|
3203
|
-
try {
|
|
3204
|
-
return readFileSync3(file, "utf8").includes(MARKER);
|
|
3205
|
-
} catch {
|
|
3206
|
-
return false;
|
|
3207
|
-
}
|
|
3208
|
-
};
|
|
3209
|
-
var linkStack = (stackName, options = {}) => {
|
|
3210
|
-
const env = options.env ?? process.env;
|
|
3211
|
-
const { config } = loadStack(stackName, env);
|
|
3212
|
-
const name = options.as ?? config.command ?? stackName;
|
|
3213
|
-
if (!VALID_NAME.test(name)) {
|
|
3214
|
-
throw new ConfigError(
|
|
3215
|
-
`"${name}" is not a usable command name \u2014 letters, digits, dot, dash and underscore only`
|
|
3216
|
-
);
|
|
3217
|
-
}
|
|
3218
|
-
if (RESERVED.has(name.toLowerCase()) && !options.force) {
|
|
3219
|
-
throw new ConfigError(`"${name}" would shadow an important command`, {
|
|
3220
|
-
details: ["pick another with `--as <name>`, or pass --force if you really mean it"]
|
|
3221
|
-
});
|
|
3222
|
-
}
|
|
3223
|
-
const resolved = options.dir ? { dir: path9.resolve(options.dir), onPath: true } : resolveBinDir(env);
|
|
3224
|
-
mkdirSync3(resolved.dir, { recursive: true });
|
|
3225
|
-
const target = currentTarget(stackName);
|
|
3226
|
-
const planned = shimFiles(name, target);
|
|
3227
|
-
let replaced = false;
|
|
3228
|
-
for (const { file } of planned) {
|
|
3229
|
-
const full = path9.join(resolved.dir, file);
|
|
3230
|
-
if (!existsSync6(full)) continue;
|
|
3231
|
-
if (isOurs(full)) {
|
|
3232
|
-
replaced = true;
|
|
3233
|
-
continue;
|
|
3234
|
-
}
|
|
3235
|
-
if (!options.force) {
|
|
3236
|
-
throw new ConfigError(`"${full}" already exists and was not created by laracrew`, {
|
|
3237
|
-
details: ["choose another name with `--as <name>`, or pass --force to overwrite"]
|
|
3238
|
-
});
|
|
3239
|
-
}
|
|
3240
|
-
replaced = true;
|
|
3241
|
-
}
|
|
3242
|
-
const written = [];
|
|
3243
|
-
for (const { file, contents, exec } of planned) {
|
|
3244
|
-
const full = path9.join(resolved.dir, file);
|
|
3245
|
-
writeFileSync2(full, contents, "utf8");
|
|
3246
|
-
if (exec && !IS_WINDOWS3) chmodSync(full, 493);
|
|
3247
|
-
else if (exec) {
|
|
3248
|
-
try {
|
|
3249
|
-
chmodSync(full, 493);
|
|
3250
|
-
} catch {
|
|
3251
|
-
}
|
|
3252
|
-
}
|
|
3253
|
-
written.push(full);
|
|
3254
|
-
}
|
|
3255
|
-
return { name, stack: stackName, dir: resolved.dir, onPath: resolved.onPath, files: written, replaced };
|
|
3256
|
-
};
|
|
3257
|
-
var unlinkCommandName = (name, options = {}) => {
|
|
3258
|
-
const env = options.env ?? process.env;
|
|
3259
|
-
const dir = options.dir ? path9.resolve(options.dir) : resolveBinDir(env).dir;
|
|
3260
|
-
const removed = [];
|
|
3261
|
-
for (const candidate of [name, `${name}.cmd`, `${name}.ps1`]) {
|
|
3262
|
-
const full = path9.join(dir, candidate);
|
|
3263
|
-
if (!existsSync6(full)) continue;
|
|
3264
|
-
if (!isOurs(full)) {
|
|
3265
|
-
throw new ConfigError(`"${full}" was not created by laracrew \u2014 leaving it alone`);
|
|
3266
|
-
}
|
|
3267
|
-
unlinkSync(full);
|
|
3268
|
-
removed.push(full);
|
|
3269
|
-
}
|
|
3270
|
-
if (removed.length === 0) throw new ConfigError(`no laracrew command named "${name}" in ${dir}`);
|
|
3271
|
-
return removed;
|
|
3272
|
-
};
|
|
3273
|
-
var listLinks = (options = {}) => {
|
|
3274
|
-
const env = options.env ?? process.env;
|
|
3275
|
-
const dir = options.dir ? path9.resolve(options.dir) : resolveBinDir(env).dir;
|
|
3276
|
-
if (!existsSync6(dir)) return [];
|
|
3277
|
-
const found = [];
|
|
3278
|
-
for (const entry of readdirSync2(dir).sort()) {
|
|
3279
|
-
if (entry.endsWith(".cmd") || entry.endsWith(".ps1")) continue;
|
|
3280
|
-
const full = path9.join(dir, entry);
|
|
3281
|
-
let contents;
|
|
3282
|
-
try {
|
|
3283
|
-
contents = readFileSync3(full, "utf8");
|
|
3284
|
-
} catch {
|
|
3285
|
-
continue;
|
|
3286
|
-
}
|
|
3287
|
-
if (!contents.includes(MARKER)) continue;
|
|
3288
|
-
const stack = /\bup ([^\s"]+)/.exec(contents)?.[1];
|
|
3289
|
-
if (stack) found.push({ name: entry, stack, file: full });
|
|
3290
|
-
}
|
|
3291
|
-
return found;
|
|
3292
|
-
};
|
|
3293
|
-
var linkCommand = (stackName, options, env = process.env) => {
|
|
3294
|
-
const stacks = options.all ? listStacks(env) : stackName ? [stackName] : [];
|
|
3295
|
-
if (stacks.length === 0) {
|
|
3296
|
-
throw new ConfigError("name a stack to link, or pass --all");
|
|
3297
|
-
}
|
|
3298
|
-
const results = [];
|
|
3299
|
-
for (const stack of stacks) {
|
|
3300
|
-
if (options.all) {
|
|
3301
|
-
const { config } = loadStack(stack, env);
|
|
3302
|
-
if (!config.command) continue;
|
|
3303
|
-
}
|
|
3304
|
-
results.push(
|
|
3305
|
-
linkStack(stack, {
|
|
3306
|
-
...options.as ? { as: options.as } : {},
|
|
3307
|
-
...options.dir ? { dir: options.dir } : {},
|
|
3308
|
-
...options.force ? { force: options.force } : {},
|
|
3309
|
-
env
|
|
3310
|
-
})
|
|
3311
|
-
);
|
|
3312
|
-
}
|
|
3313
|
-
if (results.length === 0) {
|
|
3314
|
-
console.log(paint("yellow", "nothing to link \u2014 add `command: <name>` to the stacks you want a command for"));
|
|
3315
|
-
return 0;
|
|
3316
|
-
}
|
|
3317
|
-
for (const result of results) {
|
|
3318
|
-
const verb = result.replaced ? "updated" : "created";
|
|
3319
|
-
console.log(
|
|
3320
|
-
`${paint("green", verb)} ${paint("bold", result.name)} ${paint("gray", `-> laracrew up ${result.stack}`)}`
|
|
3321
|
-
);
|
|
3322
|
-
}
|
|
3323
|
-
const first = results[0];
|
|
3324
|
-
console.log(paint("gray", `
|
|
3325
|
-
in ${first.dir}`));
|
|
3326
|
-
if (!first.onPath) {
|
|
3327
|
-
console.log(
|
|
3328
|
-
`
|
|
3329
|
-
${paint("yellow", "that directory is not on your PATH.")} Add it once:
|
|
3330
|
-
` + (IS_WINDOWS3 ? ` ${paint("cyan", `setx PATH "%PATH%;${first.dir}"`)} ${paint("gray", "(then reopen the terminal)")}` : ` ${paint("cyan", `echo 'export PATH="$PATH:${first.dir}"' >> ~/.bashrc`)}`)
|
|
3331
|
-
);
|
|
3332
|
-
} else {
|
|
3333
|
-
console.log(`
|
|
3334
|
-
Run it from anywhere: ${paint("cyan", first.name)}`);
|
|
3335
|
-
console.log(paint("gray", `Flags pass straight through: ${first.name} --only workers`));
|
|
3336
|
-
}
|
|
3337
|
-
return 0;
|
|
3338
|
-
};
|
|
3339
|
-
var unlinkCommandAction = (name, env = process.env) => {
|
|
3340
|
-
const removed = unlinkCommandName(name, { env });
|
|
3341
|
-
for (const file of removed) console.log(`${paint("red", "removed")} ${file}`);
|
|
3342
|
-
return 0;
|
|
3343
|
-
};
|
|
3344
|
-
|
|
3345
3729
|
// src/cli/commands/logs.ts
|
|
3346
|
-
import { existsSync as
|
|
3347
|
-
import
|
|
3730
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4 } from "fs";
|
|
3731
|
+
import path11 from "path";
|
|
3348
3732
|
|
|
3349
3733
|
// src/core/logs/read.ts
|
|
3350
|
-
import { openSync as openSync2, readSync, closeSync as closeSync2, statSync as statSync3, existsSync as
|
|
3734
|
+
import { openSync as openSync2, readSync, closeSync as closeSync2, statSync as statSync3, existsSync as existsSync8 } from "fs";
|
|
3351
3735
|
var CHUNK = 64 * 1024;
|
|
3352
3736
|
var tailFile = (file, limit) => {
|
|
3353
|
-
if (!
|
|
3737
|
+
if (!existsSync8(file) || limit <= 0) return [];
|
|
3354
3738
|
const size = statSync3(file).size;
|
|
3355
3739
|
if (size === 0) return [];
|
|
3356
3740
|
const handle = openSync2(file, "r");
|
|
@@ -3397,10 +3781,10 @@ var parseSince = (input) => {
|
|
|
3397
3781
|
return Number.isNaN(absolute) ? void 0 : absolute;
|
|
3398
3782
|
};
|
|
3399
3783
|
var followFile = (file, options) => {
|
|
3400
|
-
let position =
|
|
3784
|
+
let position = existsSync8(file) ? statSync3(file).size : 0;
|
|
3401
3785
|
let carry = "";
|
|
3402
3786
|
const read = () => {
|
|
3403
|
-
if (!
|
|
3787
|
+
if (!existsSync8(file)) return;
|
|
3404
3788
|
const size = statSync3(file).size;
|
|
3405
3789
|
if (size === position) return;
|
|
3406
3790
|
if (size < position) {
|
|
@@ -3429,23 +3813,23 @@ var followFile = (file, options) => {
|
|
|
3429
3813
|
};
|
|
3430
3814
|
|
|
3431
3815
|
// src/cli/commands/logs.ts
|
|
3432
|
-
var logDirFor = (stackName, env = process.env) =>
|
|
3816
|
+
var logDirFor = (stackName, env = process.env) => path11.join(paths(env).logsDir, stackName);
|
|
3433
3817
|
var targetsFor = (stackName, env = process.env) => {
|
|
3434
3818
|
const stack = prepareStack(stackName, { env });
|
|
3435
3819
|
const dir = logDirFor(stackName, env);
|
|
3436
3820
|
const names = fileNamesFor(stack.services.map((service) => service.name));
|
|
3437
3821
|
return stack.services.map((service) => ({
|
|
3438
3822
|
service: service.name,
|
|
3439
|
-
file:
|
|
3823
|
+
file: path11.join(dir, names.get(service.name)),
|
|
3440
3824
|
color: service.color
|
|
3441
|
-
})).filter((target) =>
|
|
3825
|
+
})).filter((target) => existsSync9(target.file));
|
|
3442
3826
|
};
|
|
3443
3827
|
var formatEntry = (entry, width, withName) => {
|
|
3444
3828
|
const time2 = new Date(entry.at || Date.now());
|
|
3445
3829
|
const clock = [time2.getHours(), time2.getMinutes(), time2.getSeconds()].map((part) => String(part).padStart(2, "0")).join(":");
|
|
3446
|
-
const who = withName ? `${
|
|
3447
|
-
const body = entry.stream === "stderr" ?
|
|
3448
|
-
return `${
|
|
3830
|
+
const who = withName ? `${color.cyan(padEnd(entry.service, width))} ` : "";
|
|
3831
|
+
const body = entry.stream === "stderr" ? color.red(entry.line) : entry.line;
|
|
3832
|
+
return `${color.gray(clock)} ${who}${color.gray("|")} ${body}
|
|
3449
3833
|
`;
|
|
3450
3834
|
};
|
|
3451
3835
|
var logsCommand = async (service, options = {}) => {
|
|
@@ -3454,25 +3838,24 @@ var logsCommand = async (service, options = {}) => {
|
|
|
3454
3838
|
if (!stackName) throw new ConfigError("name a stack with --stack, or run `laracrew logs` from a known stack");
|
|
3455
3839
|
const dir = logDirFor(stackName, env);
|
|
3456
3840
|
const available = targetsFor(stackName, env);
|
|
3841
|
+
const mark = marks(env);
|
|
3457
3842
|
if (options.list) {
|
|
3458
3843
|
if (available.length === 0) {
|
|
3459
|
-
console.log(
|
|
3460
|
-
console.log(
|
|
3844
|
+
console.log(`${mark.warn} ${color.yellow(`no log files yet in ${dir}`)}`);
|
|
3845
|
+
console.log(` ${color.gray("they appear once the stack has run at least once")}`);
|
|
3461
3846
|
return 0;
|
|
3462
3847
|
}
|
|
3463
|
-
console.log(
|
|
3848
|
+
console.log(`${color.bold("logs")} ${color.gray(glyphs(env).dot)} ${color.cyan.bold(stackName)}`);
|
|
3849
|
+
console.log(` ${color.gray(dir)}`);
|
|
3464
3850
|
for (const target of available) {
|
|
3465
|
-
console.log(` ${
|
|
3851
|
+
console.log(` ${color.cyan(target.service)} ${color.gray(path11.basename(target.file))}`);
|
|
3466
3852
|
}
|
|
3467
3853
|
return 0;
|
|
3468
3854
|
}
|
|
3469
3855
|
if (available.length === 0) {
|
|
3470
|
-
console.error(
|
|
3856
|
+
console.error(`${mark.warn} ${color.yellow(`no log files in ${dir}`)}`);
|
|
3471
3857
|
console.error(
|
|
3472
|
-
|
|
3473
|
-
"gray",
|
|
3474
|
-
"run the stack at least once, and check `defaults.logs.toFile` is not set to false"
|
|
3475
|
-
)
|
|
3858
|
+
` ${color.gray("run the stack at least once, and check `defaults.logs.toFile` is not set to false")}`
|
|
3476
3859
|
);
|
|
3477
3860
|
return 1;
|
|
3478
3861
|
}
|
|
@@ -3534,11 +3917,12 @@ var logsCommand = async (service, options = {}) => {
|
|
|
3534
3917
|
};
|
|
3535
3918
|
|
|
3536
3919
|
// src/cli/commands/ls.ts
|
|
3537
|
-
import { existsSync as
|
|
3920
|
+
import { existsSync as existsSync10 } from "fs";
|
|
3538
3921
|
var lsCommand = (env = process.env, json = false) => {
|
|
3539
3922
|
const home2 = paths(env);
|
|
3540
|
-
|
|
3541
|
-
|
|
3923
|
+
const mark = marks(env);
|
|
3924
|
+
if (!existsSync10(home2.root)) {
|
|
3925
|
+
console.error(`${mark.warn} ${color.yellow("no laracrew home yet")} \u2014 run ${color.cyan("laracrew init")}`);
|
|
3542
3926
|
return 1;
|
|
3543
3927
|
}
|
|
3544
3928
|
const stackNames = listStacks(env);
|
|
@@ -3571,43 +3955,46 @@ var lsCommand = (env = process.env, json = false) => {
|
|
|
3571
3955
|
console.log(JSON.stringify({ root: home2.root, stacks, tasks: taskNames, projects }, null, 2));
|
|
3572
3956
|
return 0;
|
|
3573
3957
|
}
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3958
|
+
const heading = (label, count) => `
|
|
3959
|
+
${color.bold(label)} ${color.gray(`(${count})`)}`;
|
|
3960
|
+
console.log(`${color.bold("laracrew")} ${color.gray(home2.root)}`);
|
|
3961
|
+
console.log(heading("STACKS", stacks.length));
|
|
3962
|
+
if (stacks.length === 0) {
|
|
3963
|
+
console.log(color.gray(" none \u2014 run `laracrew init --examples`, or write stacks/<name>/stack.yaml"));
|
|
3964
|
+
}
|
|
3578
3965
|
const width = Math.max(4, ...stacks.map((stack) => stack.name.length));
|
|
3966
|
+
const indent = " ".repeat(width + 4);
|
|
3579
3967
|
for (const stack of stacks) {
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
console.log(
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
console.log(` ${" ".repeat(width)} ${paint("gray", `profiles: ${stack.profiles.join(", ")}`)}`);
|
|
3968
|
+
if (stack.valid) {
|
|
3969
|
+
const meta = padEnd(`${stack.services} service${stack.services === 1 ? "" : "s"}`, 11);
|
|
3970
|
+
console.log(` ${color.cyan.bold(padEnd(stack.name, width))} ${color.gray(meta)} ${stack.description}`.trimEnd());
|
|
3971
|
+
} else {
|
|
3972
|
+
console.log(
|
|
3973
|
+
`${mark.fail} ${color.red.bold(padEnd(stack.name, width))} ${color.red(padEnd("invalid", 11))} ${color.red(stack.description)}`
|
|
3974
|
+
);
|
|
3588
3975
|
}
|
|
3976
|
+
if (stack.command) console.log(`${indent}${color.green(`$ ${stack.command}`)} ${color.gray("global command")}`);
|
|
3977
|
+
if (stack.profiles.length > 0) console.log(`${indent}${color.gray(`profiles: ${stack.profiles.join(", ")}`)}`);
|
|
3589
3978
|
}
|
|
3590
|
-
console.log(`
|
|
3591
|
-
${paint("bold", "PROJECTS")}`);
|
|
3592
3979
|
const projectKeys = Object.keys(projects);
|
|
3593
|
-
|
|
3980
|
+
console.log(heading("PROJECTS", projectKeys.length));
|
|
3981
|
+
if (projectKeys.length === 0) console.log(color.gray(" none \u2014 add them to projects.yaml"));
|
|
3594
3982
|
const projectWidth = Math.max(4, ...projectKeys.map((key) => key.length));
|
|
3595
3983
|
for (const [key, project] of Object.entries(projects)) {
|
|
3596
|
-
const exists =
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
);
|
|
3600
|
-
}
|
|
3601
|
-
console.log(
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
for (const task of taskNames) console.log(` ${paint("green", task)}`);
|
|
3984
|
+
const exists = existsSync10(project.path);
|
|
3985
|
+
const lead = exists ? " " : `${mark.fail} `;
|
|
3986
|
+
const where = exists ? project.path : color.red(`${project.path} (missing)`);
|
|
3987
|
+
console.log(`${lead}${color.magenta.bold(padEnd(key, projectWidth))} ${where}`);
|
|
3988
|
+
}
|
|
3989
|
+
console.log(heading("TASKS", taskNames.length));
|
|
3990
|
+
if (taskNames.length === 0) console.log(color.gray(" none"));
|
|
3991
|
+
for (const task of taskNames) console.log(` ${color.green(task)}`);
|
|
3605
3992
|
console.log("");
|
|
3606
3993
|
return 0;
|
|
3607
3994
|
};
|
|
3608
3995
|
|
|
3609
3996
|
// src/cli/program.ts
|
|
3610
|
-
var VERSION = true ? "0.
|
|
3997
|
+
var VERSION = true ? "0.3.0" : versionFromPackageJson();
|
|
3611
3998
|
var resolveStackName = (explicit, env = process.env) => {
|
|
3612
3999
|
if (explicit) return explicit;
|
|
3613
4000
|
const configured = loadGlobalConfig(env).defaultStack;
|
|
@@ -3625,10 +4012,38 @@ var resolveStackName = (explicit, env = process.env) => {
|
|
|
3625
4012
|
var collect = (value, previous) => [...previous, value];
|
|
3626
4013
|
var buildProgram = () => {
|
|
3627
4014
|
const program = new Command();
|
|
3628
|
-
program.name("laracrew").description("Boot and supervise all the long-running processes of your projects with one command.").version(VERSION, "-v, --version").showHelpAfterError()
|
|
4015
|
+
program.name("laracrew").description("Boot and supervise all the long-running processes of your projects with one command.").version(VERSION, "-v, --version").showHelpAfterError().configureHelp({
|
|
4016
|
+
styleTitle: (text) => color.bold(text),
|
|
4017
|
+
styleCommandText: (text) => color.cyan(text),
|
|
4018
|
+
styleSubcommandText: (text) => color.cyan(text),
|
|
4019
|
+
styleOptionText: (text) => color.green(text),
|
|
4020
|
+
styleArgumentText: (text) => color.yellow(text)
|
|
4021
|
+
}).configureOutput({
|
|
4022
|
+
// One colour decision for the whole program — ours, so NO_COLOR is honoured here too.
|
|
4023
|
+
getOutHasColors: () => color.level > 0,
|
|
4024
|
+
getErrHasColors: () => color.level > 0,
|
|
4025
|
+
outputError: (message, write2) => write2(`${marks().fail} ${color.red(message.trimEnd())}
|
|
4026
|
+
`)
|
|
4027
|
+
});
|
|
3629
4028
|
program.command("init").description("create ~/.laracrew").option("--examples", "also write a runnable demo stack and a two-project template").action((options) => {
|
|
3630
4029
|
initCommand({ examples: options.examples ?? false });
|
|
3631
4030
|
});
|
|
4031
|
+
const draft = program.command("draft").description("build a stack step by step in the current folder, then publish it").argument("[name]", "start a draft with this stack name, or show it if it exists; omit to show the only draft here").option("--force", "start over when a draft of that name already exists here").action((name, options) => {
|
|
4032
|
+
process.exitCode = draftCommand(name, options);
|
|
4033
|
+
});
|
|
4034
|
+
draft.command("add").description("append a service to the draft in this folder").requiredOption("--command, --cmd <line>", 'what to run, e.g. "php artisan queue:work"').option("--project <key>", "the project it runs in \u2014 any short key, e.g. api").option("--path <dir>", "the project's folder, or where the command runs when there is no --project; defaults to this folder").option("--name <service>", "service name (default: project + command, e.g. api:queue:work)").option("--default-state <state>", '"running" (default) or "stopped": defined, but idle until you start it').option("--draft <name>", "which draft, when this folder holds several").action((options) => {
|
|
4035
|
+
process.exitCode = draftAddCommand({
|
|
4036
|
+
command: options.command ?? options.cmd,
|
|
4037
|
+
project: options.project,
|
|
4038
|
+
path: options.path,
|
|
4039
|
+
name: options.name,
|
|
4040
|
+
state: options.defaultState,
|
|
4041
|
+
draft: options.draft
|
|
4042
|
+
});
|
|
4043
|
+
});
|
|
4044
|
+
draft.command("publish").description("validate the draft and install it as a stack in ~/.laracrew").option("--force", "replace a stack of the same name").option("--link", "also install the global command, as `laracrew link` would").option("--draft <name>", "which draft, when this folder holds several").action((options) => {
|
|
4045
|
+
process.exitCode = draftPublishCommand(options);
|
|
4046
|
+
});
|
|
3632
4047
|
program.command("ls").alias("list").description("list stacks, projects and tasks").option("--json", "machine-readable output").action((options) => {
|
|
3633
4048
|
process.exitCode = lsCommand(process.env, options.json ?? false);
|
|
3634
4049
|
});
|
|
@@ -3672,13 +4087,19 @@ var buildProgram = () => {
|
|
|
3672
4087
|
});
|
|
3673
4088
|
return program;
|
|
3674
4089
|
};
|
|
4090
|
+
var renderConfigError = (error) => {
|
|
4091
|
+
const lines = [`${marks().fail} ${color.red.bold("config error")}`];
|
|
4092
|
+
if (error.file) lines.push(` ${color.gray(error.file)}`);
|
|
4093
|
+
lines.push(` ${error.message}`);
|
|
4094
|
+
for (const detail of error.details) lines.push(` ${color.gray("-")} ${detail}`);
|
|
4095
|
+
return lines.join("\n");
|
|
4096
|
+
};
|
|
3675
4097
|
var run = async (argv = process.argv) => {
|
|
3676
4098
|
try {
|
|
3677
4099
|
await buildProgram().parseAsync(argv);
|
|
3678
4100
|
} catch (error) {
|
|
3679
4101
|
if (error instanceof ConfigError) {
|
|
3680
|
-
console.error(
|
|
3681
|
-
${error.format()}`);
|
|
4102
|
+
console.error(renderConfigError(error));
|
|
3682
4103
|
process.exitCode = 1;
|
|
3683
4104
|
return;
|
|
3684
4105
|
}
|
|
@@ -3689,7 +4110,9 @@ ${error.format()}`);
|
|
|
3689
4110
|
// src/index.ts
|
|
3690
4111
|
installPipeGuards();
|
|
3691
4112
|
run().catch((error) => {
|
|
3692
|
-
|
|
4113
|
+
const [head, ...trace] = (error instanceof Error ? error.stack ?? error.message : String(error)).split("\n");
|
|
4114
|
+
console.error(`${marks().fail} ${color.red.bold(head ?? "unexpected error")}`);
|
|
4115
|
+
if (trace.length > 0) console.error(color.gray(trace.join("\n")));
|
|
3693
4116
|
process.exitCode = 1;
|
|
3694
4117
|
});
|
|
3695
4118
|
//# sourceMappingURL=index.js.map
|