@ninhache/probe 0.5.0 → 0.6.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/README.md
CHANGED
|
@@ -45,9 +45,12 @@ Want `make check` too? See [Makefile](#makefile): it is optional.
|
|
|
45
45
|
| `probe ls` | List services |
|
|
46
46
|
| `probe check [name...] [--json] [--timeout <seconds>]` | Check all services, or the named ones |
|
|
47
47
|
| `probe wait [name...] [--json] [--timeout <seconds>]` | Check again until every service is up, or give up after the timeout |
|
|
48
|
+
| `probe open <name>` | Open the URL of a service in the browser |
|
|
49
|
+
| `probe curl <name>` | Print the curl command that requests a service |
|
|
48
50
|
| `probe status` | CLI version, pending migration, Makefile block state, available update |
|
|
49
51
|
| `probe sync` | Add or regenerate the Makefile block (optional) |
|
|
50
52
|
| `probe migrate` | Migrate `.probe/config.yml` to the current schema |
|
|
53
|
+
| `probe completion <zsh\|bash\|fish>` | Print the shell completion script |
|
|
51
54
|
|
|
52
55
|
Exit codes: `0` all up, `1` a service is down, `2` usage or config error.
|
|
53
56
|
|
|
@@ -61,8 +64,25 @@ longest one. Unlike `check --timeout`, which is the time given to each service,
|
|
|
61
64
|
wait; each check inside it still gets 3 s, or the service's own `timeout`. Progress goes to stderr, the final
|
|
62
65
|
report to stdout; exit code `1` names the services still down.
|
|
63
66
|
|
|
67
|
+
`probe open` uses `open` on macOS and `xdg-open` elsewhere. Without it, the URL is printed and the exit code is
|
|
68
|
+
`1`. `probe curl api` prints a command to copy, or to run: `probe curl api | sh`.
|
|
69
|
+
|
|
64
70
|
`probe` works from any sub-directory: it walks up to the nearest `.probe/`.
|
|
65
71
|
|
|
72
|
+
## Completion
|
|
73
|
+
|
|
74
|
+
Tab completes the commands, their options and the names of your services (`probe check a<Tab>`).
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
eval "$(probe completion zsh)" # in ~/.zshrc, after compinit
|
|
78
|
+
eval "$(probe completion bash)" # in ~/.bashrc (macOS: ~/.bash_profile)
|
|
79
|
+
probe completion fish | source # in ~/.config/fish/config.fish
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
In zsh you can also save it once instead: `probe completion zsh > ~/.zfunc/_probe`, with `~/.zfunc` in your
|
|
83
|
+
`fpath`; in fish, `probe completion fish > ~/.config/fish/completions/probe.fish`. It works with the bash 3.2
|
|
84
|
+
of macOS.
|
|
85
|
+
|
|
66
86
|
## Dashboard
|
|
67
87
|
|
|
68
88
|
`probe` alone, on a terminal, opens a live list of your services and checks them every 2 s
|
|
@@ -142,12 +162,16 @@ outside the block is yours and is never touched. Without a block, no command cre
|
|
|
142
162
|
Once the block is there, `probe add` and `probe rm` keep it up to date. Commit the `Makefile` and
|
|
143
163
|
`.probe/lock.yml`.
|
|
144
164
|
|
|
145
|
-
Targets: `help`, `check`, `status` and one `check-<name>` per
|
|
165
|
+
Targets: `help`, `check`, `status`, `probe` (opens the [dashboard](#dashboard)) and one `check-<name>` per
|
|
166
|
+
service. The block uses `probe` when it is in
|
|
146
167
|
your `PATH`, else `npx -y @ninhache/probe@latest`. It works with GNU Make 3.81 (the macOS default).
|
|
147
168
|
|
|
148
169
|
Edited the block by hand? `probe status` says so and `probe add` / `probe rm` leave it alone. `probe sync`
|
|
149
170
|
restores it.
|
|
150
171
|
|
|
172
|
+
A newer `probe` can add targets to the block: `make probe` came with 0.6.0. A block written before that is
|
|
173
|
+
reported as `outdated` by `probe status`. Run `probe sync` once to get the new targets.
|
|
174
|
+
|
|
151
175
|
## Migrations
|
|
152
176
|
|
|
153
177
|
`config.yml` carries a `schemaVersion`. When a newer `probe` needs a newer schema, the first command you run:
|
|
@@ -27,6 +27,7 @@ var REGISTRY_TIMEOUT_MS = 5e3;
|
|
|
27
27
|
var UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
28
28
|
var UPDATE_CACHE_FILE = "latest.json";
|
|
29
29
|
var REFRESH_COMMAND = "__refresh-update-cache";
|
|
30
|
+
var COMPLETE_COMMAND = "__complete";
|
|
30
31
|
var EXIT_DOWN = 1;
|
|
31
32
|
var EXIT_ERROR = 2;
|
|
32
33
|
|
|
@@ -164,6 +165,12 @@ function targetUrl(service) {
|
|
|
164
165
|
const host = bareIpv6 ? `[${service.host}]` : service.host;
|
|
165
166
|
return `http://${host}:${service.port}${service.path}`;
|
|
166
167
|
}
|
|
168
|
+
var shellQuoted = (text) => `'${text.replaceAll("'", "'\\''")}'`;
|
|
169
|
+
function curlCommand(service) {
|
|
170
|
+
const follow = expectsRedirect(service.expect) ? "" : " -L";
|
|
171
|
+
const seconds = service.timeout ?? DEFAULT_TIMEOUT_SECONDS;
|
|
172
|
+
return `curl -i${follow} --max-time ${seconds} ${shellQuoted(targetUrl(service))}`;
|
|
173
|
+
}
|
|
167
174
|
function isExpected(expect, status) {
|
|
168
175
|
if (expect === void 0) return status >= 200 && status < 300;
|
|
169
176
|
return Array.isArray(expect) ? expect.includes(status) : expect === status;
|
|
@@ -349,6 +356,7 @@ export {
|
|
|
349
356
|
UPDATE_CHECK_TTL_MS,
|
|
350
357
|
UPDATE_CACHE_FILE,
|
|
351
358
|
REFRESH_COMMAND,
|
|
359
|
+
COMPLETE_COMMAND,
|
|
352
360
|
EXIT_DOWN,
|
|
353
361
|
EXIT_ERROR,
|
|
354
362
|
ProbeError,
|
|
@@ -357,6 +365,8 @@ export {
|
|
|
357
365
|
isInteractive,
|
|
358
366
|
truncate,
|
|
359
367
|
stripStyles,
|
|
368
|
+
targetUrl,
|
|
369
|
+
curlCommand,
|
|
360
370
|
runChecks,
|
|
361
371
|
formatCheckResults,
|
|
362
372
|
formatServices
|
package/dist/cli.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createRequire as __createRequire } from "node:module";
|
|
|
3
3
|
const require = __createRequire(import.meta.url);
|
|
4
4
|
import {
|
|
5
5
|
BACKUP_GLOB,
|
|
6
|
+
COMPLETE_COMMAND,
|
|
6
7
|
CONFIG_FILE,
|
|
7
8
|
DASHBOARD_INTERVAL_SECONDS,
|
|
8
9
|
DEFAULT_MAKEFILE,
|
|
@@ -28,18 +29,20 @@ import {
|
|
|
28
29
|
WAIT_BACKOFF_FACTOR,
|
|
29
30
|
WAIT_LAST_PASS_MS,
|
|
30
31
|
createColors,
|
|
32
|
+
curlCommand,
|
|
31
33
|
detectTerminal,
|
|
32
34
|
formatCheckResults,
|
|
33
35
|
formatServices,
|
|
34
36
|
isInteractive,
|
|
35
37
|
runChecks,
|
|
36
|
-
stripStyles
|
|
37
|
-
|
|
38
|
+
stripStyles,
|
|
39
|
+
targetUrl
|
|
40
|
+
} from "./chunk-YTTYVEYF.js";
|
|
38
41
|
import "./chunk-M3ASEG5F.js";
|
|
39
42
|
|
|
40
43
|
// src/cli.ts
|
|
41
44
|
import {
|
|
42
|
-
defineCommand as
|
|
45
|
+
defineCommand as defineCommand13,
|
|
43
46
|
renderUsage,
|
|
44
47
|
runCommand,
|
|
45
48
|
runMain
|
|
@@ -316,7 +319,7 @@ async function writeLock(paths, lock) {
|
|
|
316
319
|
}
|
|
317
320
|
|
|
318
321
|
// src/version.ts
|
|
319
|
-
var CLI_VERSION = true ? "0.
|
|
322
|
+
var CLI_VERSION = true ? "0.6.0" : "0.0.0-dev";
|
|
320
323
|
|
|
321
324
|
// src/makefile/generator.ts
|
|
322
325
|
import { createHash } from "crypto";
|
|
@@ -331,13 +334,15 @@ function renderBlock(serviceNames) {
|
|
|
331
334
|
return [
|
|
332
335
|
BLOCK_START,
|
|
333
336
|
`PROBE ?= $(shell command -v probe >/dev/null 2>&1 && echo probe || echo "${fallback}")`,
|
|
334
|
-
`.PHONY: ${["help", "check", "status", ...targets].join(" ")}`,
|
|
337
|
+
`.PHONY: ${["help", "check", "status", "probe", ...targets].join(" ")}`,
|
|
335
338
|
"help: ## List available targets",
|
|
336
339
|
` ${HELP_RECIPE}`,
|
|
337
340
|
"check: ## Check that every service responds",
|
|
338
341
|
" @$(PROBE) check",
|
|
339
342
|
"status: ## Show probe status",
|
|
340
343
|
" @$(PROBE) status",
|
|
344
|
+
"probe: ## Open the dashboard",
|
|
345
|
+
" @$(PROBE)",
|
|
341
346
|
...serviceNames.flatMap((name) => [
|
|
342
347
|
`check-${name}: ## Check ${name}`,
|
|
343
348
|
` @$(PROBE) check ${name}`
|
|
@@ -426,6 +431,11 @@ function secondsToMs(option, value) {
|
|
|
426
431
|
}
|
|
427
432
|
return Math.ceil(seconds2 * 1e3);
|
|
428
433
|
}
|
|
434
|
+
function namedService(config, name) {
|
|
435
|
+
const service = Object.hasOwn(config.services, name) ? config.services[name] : void 0;
|
|
436
|
+
if (!service) throw new ProbeError("SERVICE_UNKNOWN", `unknown service "${name}". See: probe ls`);
|
|
437
|
+
return service;
|
|
438
|
+
}
|
|
429
439
|
function rawServices(loaded) {
|
|
430
440
|
return loaded.raw.services ?? {};
|
|
431
441
|
}
|
|
@@ -466,25 +476,25 @@ var add_default = defineCommand({
|
|
|
466
476
|
description: `Seconds to wait for this service (default: ${DEFAULT_TIMEOUT_SECONDS})`
|
|
467
477
|
}
|
|
468
478
|
},
|
|
469
|
-
async run({ args }) {
|
|
479
|
+
async run({ args: args2 }) {
|
|
470
480
|
const loaded = await openProject();
|
|
471
481
|
const services = rawServices(loaded);
|
|
472
|
-
if (Object.hasOwn(services,
|
|
482
|
+
if (Object.hasOwn(services, args2.name)) {
|
|
473
483
|
throw new ProbeError(
|
|
474
484
|
"SERVICE_EXISTS",
|
|
475
|
-
`service "${
|
|
485
|
+
`service "${args2.name}" already exists. Remove it first: probe rm ${args2.name}`
|
|
476
486
|
);
|
|
477
487
|
}
|
|
478
|
-
const service = { port: parseInteger(String(
|
|
479
|
-
if (
|
|
480
|
-
if (
|
|
481
|
-
if (
|
|
482
|
-
if (
|
|
483
|
-
secondsToMs("--timeout", String(
|
|
484
|
-
service.timeout = Number(
|
|
488
|
+
const service = { port: parseInteger(String(args2.port), "--port") };
|
|
489
|
+
if (args2.host) service.host = args2.host;
|
|
490
|
+
if (args2.path) service.path = args2.path;
|
|
491
|
+
if (args2.expect) service.expect = parseExpect(String(args2.expect));
|
|
492
|
+
if (args2.timeout !== void 0) {
|
|
493
|
+
secondsToMs("--timeout", String(args2.timeout));
|
|
494
|
+
service.timeout = Number(args2.timeout);
|
|
485
495
|
}
|
|
486
|
-
await saveServices(loaded, { ...services, [
|
|
487
|
-
console.log(`Added ${
|
|
496
|
+
await saveServices(loaded, { ...services, [args2.name]: service });
|
|
497
|
+
console.log(`Added ${args2.name}. Try: probe check ${args2.name}`);
|
|
488
498
|
}
|
|
489
499
|
});
|
|
490
500
|
|
|
@@ -606,13 +616,13 @@ var check_default = defineCommand2({
|
|
|
606
616
|
description: `Seconds to wait for each service (default: its own timeout, else ${DEFAULT_TIMEOUT_SECONDS})`
|
|
607
617
|
}
|
|
608
618
|
},
|
|
609
|
-
async run({ args }) {
|
|
610
|
-
const forceTimeout =
|
|
611
|
-
const timeoutMs = secondsToMs("--timeout",
|
|
619
|
+
async run({ args: args2 }) {
|
|
620
|
+
const forceTimeout = args2.timeout !== void 0;
|
|
621
|
+
const timeoutMs = secondsToMs("--timeout", args2.timeout ?? String(DEFAULT_TIMEOUT_SECONDS));
|
|
612
622
|
const { config, paths } = await openProject();
|
|
613
623
|
const skipped = await readSkippedServices(paths);
|
|
614
|
-
const results = await runChecks(config,
|
|
615
|
-
if (
|
|
624
|
+
const results = await runChecks(config, args2._, { timeoutMs, forceTimeout }, skipped);
|
|
625
|
+
if (args2.json) {
|
|
616
626
|
console.log(JSON.stringify(results, null, 2));
|
|
617
627
|
} else if (results.length === 0) {
|
|
618
628
|
console.log("No services yet. Add one: probe add api -p 3000");
|
|
@@ -623,11 +633,190 @@ var check_default = defineCommand2({
|
|
|
623
633
|
}
|
|
624
634
|
});
|
|
625
635
|
|
|
636
|
+
// src/commands/completion.ts
|
|
637
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
638
|
+
|
|
639
|
+
// src/completion/spec.ts
|
|
640
|
+
var SHELLS = ["zsh", "bash", "fish"];
|
|
641
|
+
var COMPLETION_COMMAND = "completion";
|
|
642
|
+
var SERVICE_COMMANDS = ["rm", "check", "wait", "open", "curl"];
|
|
643
|
+
var ROOT_OPTIONS = ["--interval", "--help", "--version"];
|
|
644
|
+
var SERVICES_QUERY = `probe ${COMPLETE_COMMAND} services 2>/dev/null`;
|
|
645
|
+
var HELP_OPTION = "--help";
|
|
646
|
+
function completionSpec(commands2) {
|
|
647
|
+
return Object.entries(commands2).map(([name, command]) => {
|
|
648
|
+
const meta2 = command.meta ?? {};
|
|
649
|
+
const args2 = command.args ?? {};
|
|
650
|
+
const options = Object.entries(args2).filter(([, arg]) => arg.type !== "positional").map(([option]) => `--${option}`);
|
|
651
|
+
return {
|
|
652
|
+
name,
|
|
653
|
+
description: meta2.description ?? "",
|
|
654
|
+
options: [...options, HELP_OPTION],
|
|
655
|
+
words: name === COMPLETION_COMMAND ? [...SHELLS] : [],
|
|
656
|
+
services: SERVICE_COMMANDS.includes(name)
|
|
657
|
+
};
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// src/completion/bash.ts
|
|
662
|
+
function bashScript(commands2) {
|
|
663
|
+
const cases = (pick) => commands2.filter((command) => pick(command).length > 0).map((command) => ` ${command.name}) words="${pick(command).join(" ")}" ;;`);
|
|
664
|
+
const withServices = commands2.filter((command) => command.services).map(({ name }) => name);
|
|
665
|
+
return [
|
|
666
|
+
'# bash completion for probe. Load it with: eval "$(probe completion bash)"',
|
|
667
|
+
"_probe() {",
|
|
668
|
+
` local cur="\${COMP_WORDS[COMP_CWORD]}" cmd="" words="" i`,
|
|
669
|
+
" for ((i = 1; i < COMP_CWORD; i++)); do",
|
|
670
|
+
` case "\${COMP_WORDS[i]}" in`,
|
|
671
|
+
" -*) ;;",
|
|
672
|
+
` *) cmd="\${COMP_WORDS[i]}"; break ;;`,
|
|
673
|
+
" esac",
|
|
674
|
+
" done",
|
|
675
|
+
' if [ -z "$cmd" ]; then',
|
|
676
|
+
' case "$cur" in',
|
|
677
|
+
` -*) words="${ROOT_OPTIONS.join(" ")}" ;;`,
|
|
678
|
+
` *) words="${commands2.map(({ name }) => name).join(" ")}" ;;`,
|
|
679
|
+
" esac",
|
|
680
|
+
` elif [ "\${cur#-}" != "$cur" ]; then`,
|
|
681
|
+
' case "$cmd" in',
|
|
682
|
+
...cases((command) => command.options),
|
|
683
|
+
" esac",
|
|
684
|
+
" else",
|
|
685
|
+
' case "$cmd" in',
|
|
686
|
+
` ${withServices.join("|")}) words="$(${SERVICES_QUERY})" ;;`,
|
|
687
|
+
...cases((command) => command.words),
|
|
688
|
+
" esac",
|
|
689
|
+
" fi",
|
|
690
|
+
' COMPREPLY=($(compgen -W "$words" -- "$cur"))',
|
|
691
|
+
"}",
|
|
692
|
+
"complete -F _probe probe",
|
|
693
|
+
""
|
|
694
|
+
].join("\n");
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// src/completion/fish.ts
|
|
698
|
+
var quoted = (text) => `'${text.replaceAll("\\", "\\\\").replaceAll("'", "\\'")}'`;
|
|
699
|
+
var longOption = (option) => `-l ${option.replace(/^--/, "")}`;
|
|
700
|
+
function fishScript(commands2) {
|
|
701
|
+
const inCommand = (names) => `-n '__fish_seen_subcommand_from ${names.join(" ")}'`;
|
|
702
|
+
const withServices = commands2.filter((command) => command.services).map(({ name }) => name);
|
|
703
|
+
return [
|
|
704
|
+
"# fish completion for probe. Load it with: probe completion fish | source",
|
|
705
|
+
"function __probe_services",
|
|
706
|
+
` ${SERVICES_QUERY}`,
|
|
707
|
+
"end",
|
|
708
|
+
// No file names: probe takes none.
|
|
709
|
+
"complete -c probe -f",
|
|
710
|
+
...ROOT_OPTIONS.map(
|
|
711
|
+
(option) => `complete -c probe -n __fish_use_subcommand ${longOption(option)}`
|
|
712
|
+
),
|
|
713
|
+
...commands2.map(
|
|
714
|
+
({ name, description }) => `complete -c probe -n __fish_use_subcommand -a ${name} -d ${quoted(description)}`
|
|
715
|
+
),
|
|
716
|
+
`complete -c probe ${inCommand(withServices)} -a '(__probe_services)'`,
|
|
717
|
+
...commands2.flatMap((command) => [
|
|
718
|
+
...command.options.map(
|
|
719
|
+
(option) => `complete -c probe ${inCommand([command.name])} ${longOption(option)}`
|
|
720
|
+
),
|
|
721
|
+
...command.words.length > 0 ? [`complete -c probe ${inCommand([command.name])} -a ${quoted(command.words.join(" "))}`] : []
|
|
722
|
+
]),
|
|
723
|
+
""
|
|
724
|
+
].join("\n");
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// src/completion/zsh.ts
|
|
728
|
+
function described({ name, description }) {
|
|
729
|
+
const text = `${name}:${description.replaceAll(":", "\\:")}`;
|
|
730
|
+
return `'${text.replaceAll("'", "'\\''")}'`;
|
|
731
|
+
}
|
|
732
|
+
function zshScript(commands2) {
|
|
733
|
+
const cases = (pick) => commands2.filter((command) => pick(command).length > 0).map((command) => ` ${command.name}) compadd -- ${pick(command).join(" ")} ;;`);
|
|
734
|
+
const withServices = commands2.filter((command) => command.services).map(({ name }) => name);
|
|
735
|
+
return [
|
|
736
|
+
"#compdef probe",
|
|
737
|
+
'# zsh completion for probe. Load it after compinit with: eval "$(probe completion zsh)"',
|
|
738
|
+
"_probe() {",
|
|
739
|
+
" local -a commands services",
|
|
740
|
+
" commands=(",
|
|
741
|
+
...commands2.map((command) => ` ${described(command)}`),
|
|
742
|
+
" )",
|
|
743
|
+
" if (( CURRENT == 2 )); then",
|
|
744
|
+
" if [[ $words[CURRENT] == -* ]]; then",
|
|
745
|
+
` compadd -- ${ROOT_OPTIONS.join(" ")}`,
|
|
746
|
+
" else",
|
|
747
|
+
" _describe 'command' commands",
|
|
748
|
+
" fi",
|
|
749
|
+
" return",
|
|
750
|
+
" fi",
|
|
751
|
+
" if [[ $words[CURRENT] == -* ]]; then",
|
|
752
|
+
" case $words[2] in",
|
|
753
|
+
...cases((command) => command.options).map((line) => ` ${line}`),
|
|
754
|
+
" esac",
|
|
755
|
+
" return",
|
|
756
|
+
" fi",
|
|
757
|
+
" case $words[2] in",
|
|
758
|
+
` ${withServices.join("|")})`,
|
|
759
|
+
` services=(\${(f)"$(${SERVICES_QUERY})"})`,
|
|
760
|
+
" compadd -a services ;;",
|
|
761
|
+
...cases((command) => command.words),
|
|
762
|
+
" esac",
|
|
763
|
+
"}",
|
|
764
|
+
"if (( $+functions[compdef] )); then compdef _probe probe; fi",
|
|
765
|
+
"# Saved as _probe in $fpath, this file is the function itself: run it.",
|
|
766
|
+
'if [[ $funcstack[1] == _probe ]]; then _probe "$@"; fi',
|
|
767
|
+
""
|
|
768
|
+
].join("\n");
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// src/commands/completion.ts
|
|
772
|
+
var SCRIPTS = {
|
|
773
|
+
zsh: zshScript,
|
|
774
|
+
bash: bashScript,
|
|
775
|
+
fish: fishScript
|
|
776
|
+
};
|
|
777
|
+
var meta = {
|
|
778
|
+
name: COMPLETION_COMMAND,
|
|
779
|
+
description: `Print the shell completion script (${SHELLS.join(", ")})`
|
|
780
|
+
};
|
|
781
|
+
var args = {
|
|
782
|
+
shell: { type: "positional", description: SHELLS.join(", "), required: true }
|
|
783
|
+
};
|
|
784
|
+
function completion(others) {
|
|
785
|
+
const commands2 = { ...others, [COMPLETION_COMMAND]: { meta, args } };
|
|
786
|
+
return defineCommand3({
|
|
787
|
+
meta,
|
|
788
|
+
args,
|
|
789
|
+
run({ args: { shell } }) {
|
|
790
|
+
const script = Object.hasOwn(SCRIPTS, shell) ? SCRIPTS[shell] : void 0;
|
|
791
|
+
if (!script) {
|
|
792
|
+
throw new ProbeError(
|
|
793
|
+
"USAGE",
|
|
794
|
+
`no completion for the shell "${shell}". Supported: ${SHELLS.join(", ")}.`
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
process.stdout.write(script(completionSpec(commands2)));
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// src/commands/curl.ts
|
|
803
|
+
import { defineCommand as defineCommand4 } from "citty";
|
|
804
|
+
var curl_default = defineCommand4({
|
|
805
|
+
meta: { name: "curl", description: "Print the curl command that requests a service" },
|
|
806
|
+
args: {
|
|
807
|
+
name: { type: "positional", description: "Service name", required: true }
|
|
808
|
+
},
|
|
809
|
+
async run({ args: args2 }) {
|
|
810
|
+
const { config } = await openProject();
|
|
811
|
+
console.log(curlCommand(namedService(config, args2.name)));
|
|
812
|
+
}
|
|
813
|
+
});
|
|
814
|
+
|
|
626
815
|
// src/commands/init.ts
|
|
627
816
|
import { existsSync } from "fs";
|
|
628
817
|
import { mkdir, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
629
818
|
import { join as join5 } from "path";
|
|
630
|
-
import { defineCommand as
|
|
819
|
+
import { defineCommand as defineCommand5 } from "citty";
|
|
631
820
|
var IGNORED = [`${PROBE_DIR}/${LOCAL_FILE}`, `${PROBE_DIR}/${BACKUP_GLOB}`];
|
|
632
821
|
var INITIAL_CONFIG = `# probe - local services to health-check. Edit by hand or with \`probe add\`.
|
|
633
822
|
schemaVersion: ${CURRENT_SCHEMA_VERSION}
|
|
@@ -643,7 +832,7 @@ async function ignoreLocalFiles(root) {
|
|
|
643
832
|
await writeFile5(file, `${current}${separator}${missing.join("\n")}
|
|
644
833
|
`);
|
|
645
834
|
}
|
|
646
|
-
var init_default =
|
|
835
|
+
var init_default = defineCommand5({
|
|
647
836
|
meta: {
|
|
648
837
|
name: "init",
|
|
649
838
|
description: "Create .probe/ and the .gitignore entries"
|
|
@@ -661,8 +850,8 @@ var init_default = defineCommand3({
|
|
|
661
850
|
});
|
|
662
851
|
|
|
663
852
|
// src/commands/ls.ts
|
|
664
|
-
import { defineCommand as
|
|
665
|
-
var ls_default =
|
|
853
|
+
import { defineCommand as defineCommand6 } from "citty";
|
|
854
|
+
var ls_default = defineCommand6({
|
|
666
855
|
meta: { name: "ls", description: "List services" },
|
|
667
856
|
async run() {
|
|
668
857
|
const { config, paths } = await openProject();
|
|
@@ -675,8 +864,8 @@ var ls_default = defineCommand4({
|
|
|
675
864
|
});
|
|
676
865
|
|
|
677
866
|
// src/commands/migrate.ts
|
|
678
|
-
import { defineCommand as
|
|
679
|
-
var migrate_default =
|
|
867
|
+
import { defineCommand as defineCommand7 } from "citty";
|
|
868
|
+
var migrate_default = defineCommand7({
|
|
680
869
|
meta: { name: "migrate", description: "Migrate the config to the current schema version" },
|
|
681
870
|
async run() {
|
|
682
871
|
const paths = requireProbeDir(process.cwd());
|
|
@@ -688,27 +877,65 @@ var migrate_default = defineCommand5({
|
|
|
688
877
|
}
|
|
689
878
|
});
|
|
690
879
|
|
|
880
|
+
// src/commands/open.ts
|
|
881
|
+
import { defineCommand as defineCommand8 } from "citty";
|
|
882
|
+
|
|
883
|
+
// src/open-url.ts
|
|
884
|
+
import { spawn as nodeSpawn } from "child_process";
|
|
885
|
+
function openerCommand(platform) {
|
|
886
|
+
return platform === "darwin" ? "open" : "xdg-open";
|
|
887
|
+
}
|
|
888
|
+
function openUrl(url, platform = process.platform, spawn2 = nodeSpawn) {
|
|
889
|
+
return new Promise((resolve2) => {
|
|
890
|
+
const child = spawn2(openerCommand(platform), [url], { detached: true, stdio: "ignore" });
|
|
891
|
+
child.once("error", () => resolve2(false));
|
|
892
|
+
child.once("spawn", () => {
|
|
893
|
+
child.unref();
|
|
894
|
+
resolve2(true);
|
|
895
|
+
});
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// src/commands/open.ts
|
|
900
|
+
var open_default = defineCommand8({
|
|
901
|
+
meta: { name: "open", description: "Open the URL of a service in the browser" },
|
|
902
|
+
args: {
|
|
903
|
+
name: { type: "positional", description: "Service name", required: true }
|
|
904
|
+
},
|
|
905
|
+
async run({ args: args2 }) {
|
|
906
|
+
const { config } = await openProject();
|
|
907
|
+
const url = targetUrl(namedService(config, args2.name));
|
|
908
|
+
if (await openUrl(url)) {
|
|
909
|
+
console.log(`Opened ${url}`);
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
console.log(url);
|
|
913
|
+
console.error(`probe: ${openerCommand(process.platform)} not found, open the URL yourself.`);
|
|
914
|
+
process.exitCode = EXIT_DOWN;
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
|
|
691
918
|
// src/commands/rm.ts
|
|
692
|
-
import { defineCommand as
|
|
693
|
-
var rm_default =
|
|
919
|
+
import { defineCommand as defineCommand9 } from "citty";
|
|
920
|
+
var rm_default = defineCommand9({
|
|
694
921
|
meta: { name: "rm", description: "Remove a service" },
|
|
695
922
|
args: {
|
|
696
923
|
name: { type: "positional", description: "Service name", required: true }
|
|
697
924
|
},
|
|
698
|
-
async run({ args }) {
|
|
925
|
+
async run({ args: args2 }) {
|
|
699
926
|
const loaded = await openProject();
|
|
700
927
|
const services = rawServices(loaded);
|
|
701
|
-
if (!Object.hasOwn(services,
|
|
702
|
-
throw new ProbeError("SERVICE_UNKNOWN", `unknown service "${
|
|
928
|
+
if (!Object.hasOwn(services, args2.name)) {
|
|
929
|
+
throw new ProbeError("SERVICE_UNKNOWN", `unknown service "${args2.name}". See: probe ls`);
|
|
703
930
|
}
|
|
704
|
-
const { [
|
|
931
|
+
const { [args2.name]: _removed, ...rest } = services;
|
|
705
932
|
await saveServices(loaded, rest);
|
|
706
|
-
console.log(`Removed ${
|
|
933
|
+
console.log(`Removed ${args2.name}.`);
|
|
707
934
|
}
|
|
708
935
|
});
|
|
709
936
|
|
|
710
937
|
// src/commands/status.ts
|
|
711
|
-
import { defineCommand as
|
|
938
|
+
import { defineCommand as defineCommand10 } from "citty";
|
|
712
939
|
|
|
713
940
|
// src/update/notifier.ts
|
|
714
941
|
import { spawn } from "child_process";
|
|
@@ -734,8 +961,8 @@ function writeCache(dir, cache) {
|
|
|
734
961
|
writeFileSync(join6(dir, UPDATE_CACHE_FILE), JSON.stringify(cache));
|
|
735
962
|
}
|
|
736
963
|
function isNewer(latest, current) {
|
|
737
|
-
const
|
|
738
|
-
const [a, b] = [
|
|
964
|
+
const parse3 = (version) => (version.split("-")[0] ?? "").split(".").map(Number);
|
|
965
|
+
const [a, b] = [parse3(latest), parse3(current)];
|
|
739
966
|
for (let index = 0; index < 3; index++) {
|
|
740
967
|
const difference = (a[index] || 0) - (b[index] || 0);
|
|
741
968
|
if (difference !== 0) return difference > 0;
|
|
@@ -797,7 +1024,7 @@ function describeUpdate(colors) {
|
|
|
797
1024
|
if (!readCache(cacheDir())?.latest) return colors.dim(`not checked yet (${CLI_VERSION})`);
|
|
798
1025
|
return colors.green(`up to date (${CLI_VERSION})`);
|
|
799
1026
|
}
|
|
800
|
-
var status_default =
|
|
1027
|
+
var status_default = defineCommand10({
|
|
801
1028
|
meta: { name: "status", description: "Show pending migration, Makefile state and updates" },
|
|
802
1029
|
async run() {
|
|
803
1030
|
const colors = stdoutColors();
|
|
@@ -819,8 +1046,8 @@ var status_default = defineCommand7({
|
|
|
819
1046
|
|
|
820
1047
|
// src/commands/sync.ts
|
|
821
1048
|
import { relative as relative2 } from "path";
|
|
822
|
-
import { defineCommand as
|
|
823
|
-
var sync_default =
|
|
1049
|
+
import { defineCommand as defineCommand11 } from "citty";
|
|
1050
|
+
var sync_default = defineCommand11({
|
|
824
1051
|
meta: {
|
|
825
1052
|
name: "sync",
|
|
826
1053
|
description: "Add or regenerate the probe block of the Makefile (optional)"
|
|
@@ -836,7 +1063,7 @@ var sync_default = defineCommand8({
|
|
|
836
1063
|
});
|
|
837
1064
|
|
|
838
1065
|
// src/commands/wait.ts
|
|
839
|
-
import { defineCommand as
|
|
1066
|
+
import { defineCommand as defineCommand12 } from "citty";
|
|
840
1067
|
|
|
841
1068
|
// src/checkers/poll.ts
|
|
842
1069
|
import { setTimeout as delay } from "timers/promises";
|
|
@@ -885,7 +1112,7 @@ function progressReporter() {
|
|
|
885
1112
|
return lines;
|
|
886
1113
|
};
|
|
887
1114
|
}
|
|
888
|
-
var wait_default =
|
|
1115
|
+
var wait_default = defineCommand12({
|
|
889
1116
|
meta: {
|
|
890
1117
|
name: "wait",
|
|
891
1118
|
description: "Check again and again until services are up (all of them by default)"
|
|
@@ -908,14 +1135,14 @@ var wait_default = defineCommand9({
|
|
|
908
1135
|
default: String(DEFAULT_WAIT_MAX_INTERVAL_SECONDS)
|
|
909
1136
|
}
|
|
910
1137
|
},
|
|
911
|
-
async run({ args }) {
|
|
912
|
-
const deadlineMs = secondsToMs("--timeout",
|
|
913
|
-
const intervalMs = secondsToMs("--interval",
|
|
914
|
-
const maxIntervalMs = secondsToMs("--max-interval",
|
|
1138
|
+
async run({ args: args2 }) {
|
|
1139
|
+
const deadlineMs = secondsToMs("--timeout", args2.timeout);
|
|
1140
|
+
const intervalMs = secondsToMs("--interval", args2.interval);
|
|
1141
|
+
const maxIntervalMs = secondsToMs("--max-interval", args2["max-interval"]);
|
|
915
1142
|
if (intervalMs > maxIntervalMs) {
|
|
916
1143
|
throw new ProbeError(
|
|
917
1144
|
"USAGE",
|
|
918
|
-
`--interval (${
|
|
1145
|
+
`--interval (${args2.interval}s) cannot be longer than --max-interval (${args2["max-interval"]}s).`
|
|
919
1146
|
);
|
|
920
1147
|
}
|
|
921
1148
|
const { config, paths } = await openProject();
|
|
@@ -925,7 +1152,7 @@ var wait_default = defineCommand9({
|
|
|
925
1152
|
// A service with its own timeout keeps it here, within what is left of the total.
|
|
926
1153
|
(_passMs, remainingMs) => runChecks(
|
|
927
1154
|
config,
|
|
928
|
-
|
|
1155
|
+
args2._,
|
|
929
1156
|
{ timeoutMs: DEFAULT_TIMEOUT_SECONDS * 1e3, maxTimeoutMs: remainingMs },
|
|
930
1157
|
skipped
|
|
931
1158
|
),
|
|
@@ -939,7 +1166,7 @@ var wait_default = defineCommand9({
|
|
|
939
1166
|
}
|
|
940
1167
|
}
|
|
941
1168
|
);
|
|
942
|
-
if (
|
|
1169
|
+
if (args2.json) {
|
|
943
1170
|
console.log(JSON.stringify(results, null, 2));
|
|
944
1171
|
} else if (results.length === 0) {
|
|
945
1172
|
console.log("No services yet. Add one: probe add api -p 3000");
|
|
@@ -954,6 +1181,22 @@ var wait_default = defineCommand9({
|
|
|
954
1181
|
}
|
|
955
1182
|
});
|
|
956
1183
|
|
|
1184
|
+
// src/completion/services.ts
|
|
1185
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
1186
|
+
import { parse as parse2 } from "yaml";
|
|
1187
|
+
async function completableServices(cwd) {
|
|
1188
|
+
try {
|
|
1189
|
+
const paths = findProbeDir(cwd);
|
|
1190
|
+
if (!paths) return [];
|
|
1191
|
+
const raw = parse2(await readFile6(paths.config, "utf8"));
|
|
1192
|
+
const services = raw?.services;
|
|
1193
|
+
if (typeof services !== "object" || services === null) return [];
|
|
1194
|
+
return Object.keys(services).filter((name) => SERVICE_NAME_PATTERN.test(name));
|
|
1195
|
+
} catch {
|
|
1196
|
+
return [];
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
957
1200
|
// src/dashboard/saves.ts
|
|
958
1201
|
function describe(file, error) {
|
|
959
1202
|
const code = error?.code;
|
|
@@ -984,7 +1227,7 @@ function createSaveQueue() {
|
|
|
984
1227
|
}
|
|
985
1228
|
|
|
986
1229
|
// src/suggest.ts
|
|
987
|
-
var SHORT_WORD =
|
|
1230
|
+
var SHORT_WORD = 4;
|
|
988
1231
|
var TYPOS_SHORT = 1;
|
|
989
1232
|
var TYPOS_LONG = 2;
|
|
990
1233
|
function typoDistance(a, b) {
|
|
@@ -1049,9 +1292,19 @@ var EXAMPLES = {
|
|
|
1049
1292
|
{ command: "probe wait api --timeout 120", note: "Wait for api, 2 minutes at most" },
|
|
1050
1293
|
{ command: "probe wait --interval 1 --max-interval 10", note: "Check less often" }
|
|
1051
1294
|
],
|
|
1295
|
+
open: [{ command: "probe open front", note: "Open the URL of front in the browser" }],
|
|
1296
|
+
curl: [
|
|
1297
|
+
{ command: "probe curl api", note: "Print the curl command for api" },
|
|
1298
|
+
{ command: "probe curl api | sh", note: "Run it" }
|
|
1299
|
+
],
|
|
1052
1300
|
status: [{ command: "probe status", note: "Version, migration, Makefile block" }],
|
|
1053
1301
|
sync: [{ command: "probe sync", note: "Write the probe block of the Makefile" }],
|
|
1054
|
-
migrate: [{ command: "probe migrate", note: "Bring .probe/config.yml up to date" }]
|
|
1302
|
+
migrate: [{ command: "probe migrate", note: "Bring .probe/config.yml up to date" }],
|
|
1303
|
+
completion: [
|
|
1304
|
+
{ command: "probe completion zsh", note: 'In ~/.zshrc: eval "$(probe completion zsh)"' },
|
|
1305
|
+
{ command: "probe completion bash", note: 'In ~/.bashrc: eval "$(probe completion bash)"' },
|
|
1306
|
+
{ command: "probe completion fish | source", note: "In ~/.config/fish/config.fish" }
|
|
1307
|
+
]
|
|
1055
1308
|
};
|
|
1056
1309
|
function formatExamples(name, color) {
|
|
1057
1310
|
const examples = EXAMPLES[name ?? ROOT] ?? [];
|
|
@@ -1067,7 +1320,8 @@ function formatExamples(name, color) {
|
|
|
1067
1320
|
}
|
|
1068
1321
|
|
|
1069
1322
|
// src/cli.ts
|
|
1070
|
-
var
|
|
1323
|
+
var commands = { init: init_default, add: add_default, rm: rm_default, ls: ls_default, check: check_default, wait: wait_default, open: open_default, curl: curl_default, status: status_default, sync: sync_default, migrate: migrate_default };
|
|
1324
|
+
var main = defineCommand13({
|
|
1071
1325
|
meta: {
|
|
1072
1326
|
name: "probe",
|
|
1073
1327
|
version: CLI_VERSION,
|
|
@@ -1081,13 +1335,13 @@ var main = defineCommand10({
|
|
|
1081
1335
|
default: String(DASHBOARD_INTERVAL_SECONDS)
|
|
1082
1336
|
}
|
|
1083
1337
|
},
|
|
1084
|
-
subCommands: {
|
|
1338
|
+
subCommands: { ...commands, completion: completion(commands) }
|
|
1085
1339
|
});
|
|
1086
1340
|
async function printUsage(command, parent) {
|
|
1087
1341
|
const usage = await renderUsage(command, parent);
|
|
1088
1342
|
const { color } = stdoutTerminal();
|
|
1089
|
-
const
|
|
1090
|
-
const examples = formatExamples(command === main ? void 0 :
|
|
1343
|
+
const meta2 = await (typeof command.meta === "function" ? command.meta() : command.meta);
|
|
1344
|
+
const examples = formatExamples(command === main ? void 0 : meta2?.name, color);
|
|
1091
1345
|
console.log(`${color ? usage : stripStyles(usage)}
|
|
1092
1346
|
${examples ? `
|
|
1093
1347
|
${examples}
|
|
@@ -1095,7 +1349,7 @@ ${examples}
|
|
|
1095
1349
|
}
|
|
1096
1350
|
function notifyUpdate(rawArgs) {
|
|
1097
1351
|
const cliPath = process.argv[1];
|
|
1098
|
-
if (updateCheckDisabled() || !cliPath) return;
|
|
1352
|
+
if (updateCheckDisabled() || !cliPath || rawArgs[0] === COMPLETION_COMMAND) return;
|
|
1099
1353
|
scheduleRefresh(cacheDir(), cliPath);
|
|
1100
1354
|
if (!detectTerminal(process.stderr, process.env).isTTY || rawArgs.includes("--json") || rawArgs[0] === "status")
|
|
1101
1355
|
return;
|
|
@@ -1131,7 +1385,7 @@ async function openedDashboard(rawArgs) {
|
|
|
1131
1385
|
if (!asked) return false;
|
|
1132
1386
|
throw new ProbeError("USAGE", "No services yet. Add one: probe add api -p 3000");
|
|
1133
1387
|
}
|
|
1134
|
-
const { openDashboard } = await import("./open-
|
|
1388
|
+
const { openDashboard } = await import("./open-T3OQH2O4.js");
|
|
1135
1389
|
let settings = initialDashboardSettings(askedMs, await readDashboardPreferences(paths));
|
|
1136
1390
|
const saves = createSaveQueue();
|
|
1137
1391
|
const local = `${PROBE_DIR}/${LOCAL_FILE}`;
|
|
@@ -1167,8 +1421,13 @@ function flushed(stream) {
|
|
|
1167
1421
|
stream.write("", () => done());
|
|
1168
1422
|
});
|
|
1169
1423
|
}
|
|
1424
|
+
async function printCompletions(kind) {
|
|
1425
|
+
if (kind !== "services") return;
|
|
1426
|
+
for (const name of await completableServices(process.cwd())) console.log(name);
|
|
1427
|
+
}
|
|
1170
1428
|
async function run(rawArgs) {
|
|
1171
1429
|
if (rawArgs[0] === REFRESH_COMMAND) return refreshCache(cacheDir());
|
|
1430
|
+
if (rawArgs[0] === COMPLETE_COMMAND) return printCompletions(rawArgs[1]);
|
|
1172
1431
|
const wantsHelp = rawArgs.includes("--help") || rawArgs.includes("-h");
|
|
1173
1432
|
const wantsVersion = rawArgs.length === 1 && ["--version", "-v"].includes(rawArgs[0]);
|
|
1174
1433
|
if (wantsHelp || wantsVersion) return runMain(main, { rawArgs, showUsage: printUsage });
|