@ninhache/probe 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -4
- package/dist/chunk-M3ASEG5F.js +48 -0
- package/dist/chunk-ZCAUPLQS.js +305 -0
- package/dist/cli.js +363 -176
- package/dist/devtools-QAEP2P5I.js +90 -0
- package/dist/open-TQ6NPJ7V.js +20479 -0
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# probe
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@ninhache/probe)
|
|
4
|
+
[](https://github.com/Ninhache/homebrew-tap)
|
|
5
|
+
[](https://github.com/Ninhache/probe/actions/workflows/ci.yml)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
3
8
|
Declare your project's local services once. Check they are up with one command, or with `make check`.
|
|
4
9
|
|
|
5
10
|
```console
|
|
@@ -14,7 +19,7 @@ $ echo $?
|
|
|
14
19
|
|
|
15
20
|
```sh
|
|
16
21
|
brew install ninhache/tap/probe # macOS, Linux
|
|
17
|
-
npm install -g @ninhache/probe # anywhere with Node >=
|
|
22
|
+
npm install -g @ninhache/probe # anywhere with Node >= 22
|
|
18
23
|
npx @ninhache/probe check # no install
|
|
19
24
|
```
|
|
20
25
|
|
|
@@ -36,14 +41,44 @@ Use the full name with `npx`: `npx probe` is another package.
|
|
|
36
41
|
| `probe rm <name>` | Remove a service |
|
|
37
42
|
| `probe ls` | List services |
|
|
38
43
|
| `probe check [name...] [--json] [--timeout <seconds>]` | Check all services, or the named ones |
|
|
44
|
+
| `probe wait [name...] [--json] [--timeout <seconds>]` | Check again until every service is up, or give up after the timeout |
|
|
39
45
|
| `probe status` | CLI version, pending migration, Makefile block state, available update |
|
|
40
46
|
| `probe sync` | Regenerate the Makefile block |
|
|
41
47
|
| `probe migrate` | Migrate `.probe/config.yml` to the current schema |
|
|
42
48
|
|
|
43
49
|
Exit codes: `0` all up, `1` a service is down, `2` usage or config error.
|
|
44
50
|
|
|
51
|
+
`probe wait` is for scripts: `probe wait && pnpm test`. It checks at once, then again after 0.5 s, 1 s, 2 s, 4 s
|
|
52
|
+
and every 5 s, for 60 s in total. `--timeout` sets the total, `--interval` the first pause, `--max-interval` the
|
|
53
|
+
longest one. Unlike `check --timeout`, which is the time given to each service, `wait --timeout` covers the whole
|
|
54
|
+
wait; each check inside it still gets 3 s. Progress goes to stderr, the final report to stdout; exit code `1` names the services still down.
|
|
55
|
+
|
|
45
56
|
`probe` works from any sub-directory: it walks up to the nearest `.probe/`.
|
|
46
57
|
|
|
58
|
+
## Dashboard
|
|
59
|
+
|
|
60
|
+
`probe` alone, on a terminal, opens a live list of your services and checks them every 2 s
|
|
61
|
+
(`probe --interval 5` for another pace). Off a terminal, in CI or in a pipe, nothing changes: use `probe check`.
|
|
62
|
+
|
|
63
|
+
| Key | What it does |
|
|
64
|
+
| --------------- | ------------------------------------------------ |
|
|
65
|
+
| `↑` `↓` `j` `k` | Move |
|
|
66
|
+
| `r` | Check now |
|
|
67
|
+
| `Enter` | Open the panel of the selected service (skip it) |
|
|
68
|
+
| `o` | Options: interval and timeout |
|
|
69
|
+
| `q` | Quit |
|
|
70
|
+
|
|
71
|
+
What you change there is saved in `.probe/local.yml`, for your machine only:
|
|
72
|
+
|
|
73
|
+
```yaml
|
|
74
|
+
dashboard: { interval: 5, timeout: 3 }
|
|
75
|
+
services:
|
|
76
|
+
db: { skip: true }
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
A skipped service is left out of `probe check`, `probe wait` and the dashboard, and shown as `skipped`. Name it
|
|
80
|
+
(`probe check db`) and it is checked anyway.
|
|
81
|
+
|
|
47
82
|
## Config
|
|
48
83
|
|
|
49
84
|
`.probe/config.yml`:
|
|
@@ -63,11 +98,14 @@ services:
|
|
|
63
98
|
| `path` | `/` | Path requested with `GET` |
|
|
64
99
|
| `expect` | any 2xx | Expected status, or a list of statuses |
|
|
65
100
|
|
|
101
|
+
Redirects are followed and the final status is judged, unless `expect` contains a 3xx: then the first response
|
|
102
|
+
is judged as is.
|
|
103
|
+
|
|
66
104
|
Files in `.probe/`:
|
|
67
105
|
|
|
68
106
|
- `config.yml`: services. Commit it.
|
|
69
107
|
- `lock.yml`: CLI version and hash of the Makefile block. Commit it.
|
|
70
|
-
- `local.yml`: per-machine values. Ignored by git.
|
|
108
|
+
- `local.yml`: per-machine values (dashboard settings, skipped services). Ignored by git.
|
|
71
109
|
|
|
72
110
|
Never put secrets in `config.yml` or `lock.yml`.
|
|
73
111
|
|
|
@@ -85,7 +123,8 @@ never touched.
|
|
|
85
123
|
Targets: `help`, `check`, `status` and one `check-<name>` per service. The block uses `probe` when it is in
|
|
86
124
|
your `PATH`, else `npx -y @ninhache/probe@latest`. It works with GNU Make 3.81 (the macOS default).
|
|
87
125
|
|
|
88
|
-
Edited the block by hand? `probe status` says so
|
|
126
|
+
Edited the block by hand? `probe status` says so and `probe add` / `probe rm` leave it alone. `probe sync`
|
|
127
|
+
restores it.
|
|
89
128
|
|
|
90
129
|
## Migrations
|
|
91
130
|
|
|
@@ -125,7 +164,7 @@ make check PROBE="node ../../dist/cli.js"
|
|
|
125
164
|
Release: bump `version` in `package.json`, merge, then push a `v<version>` tag. The Release workflow publishes
|
|
126
165
|
to npm through trusted publishing: no token, the package trusts this repo's `release.yml` (set on npmjs.com,
|
|
127
166
|
package Settings, Trusted Publisher). It then opens a PR on `ninhache/homebrew-tap` with the new `url` and
|
|
128
|
-
`sha256` (secret `HOMEBREW_TAP_TOKEN`)
|
|
167
|
+
`sha256` (secret `HOMEBREW_TAP_TOKEN`), which merges itself once the tap's CI is green.
|
|
129
168
|
|
|
130
169
|
## License
|
|
131
170
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "node:module";
|
|
2
|
+
const require = __createRequire(import.meta.url);
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
10
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
11
|
+
}) : x)(function(x) {
|
|
12
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
13
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
14
|
+
});
|
|
15
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
16
|
+
try {
|
|
17
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
18
|
+
} catch (e) {
|
|
19
|
+
throw mod = 0, e;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var __export = (target, all) => {
|
|
23
|
+
for (var name in all)
|
|
24
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
25
|
+
};
|
|
26
|
+
var __copyProps = (to, from, except, desc) => {
|
|
27
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
28
|
+
for (let key of __getOwnPropNames(from))
|
|
29
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
30
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
31
|
+
}
|
|
32
|
+
return to;
|
|
33
|
+
};
|
|
34
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
35
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
36
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
37
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
38
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
39
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
40
|
+
mod
|
|
41
|
+
));
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
__require,
|
|
45
|
+
__commonJS,
|
|
46
|
+
__export,
|
|
47
|
+
__toESM
|
|
48
|
+
};
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from "node:module";
|
|
2
|
+
const require = __createRequire(import.meta.url);
|
|
3
|
+
|
|
4
|
+
// src/constants.ts
|
|
5
|
+
var PACKAGE_NAME = "@ninhache/probe";
|
|
6
|
+
var PROBE_DIR = ".probe";
|
|
7
|
+
var CONFIG_FILE = "config.yml";
|
|
8
|
+
var LOCK_FILE = "lock.yml";
|
|
9
|
+
var LOCAL_FILE = "local.yml";
|
|
10
|
+
var BACKUP_GLOB = "*.bak.yml";
|
|
11
|
+
var LOCAL_DRAFT_FILE = "local.draft.bak.yml";
|
|
12
|
+
var LOCAL_FILE_MODE = 384;
|
|
13
|
+
var DEFAULT_MAKEFILE = "Makefile";
|
|
14
|
+
var DEFAULT_CHECKER_TYPE = "http";
|
|
15
|
+
var DEFAULT_TIMEOUT_SECONDS = 3;
|
|
16
|
+
var DASHBOARD_INTERVAL_SECONDS = 2;
|
|
17
|
+
var DASHBOARD_INTERVAL_CHOICES_SECONDS = [0.5, 1, 2, 5, 10, 30, 60];
|
|
18
|
+
var DASHBOARD_TIMEOUT_CHOICES_SECONDS = [1, 2, 3, 5, 10, 30];
|
|
19
|
+
var MAX_TIMEOUT_SECONDS = 2147483;
|
|
20
|
+
var DEFAULT_WAIT_TIMEOUT_SECONDS = 60;
|
|
21
|
+
var DEFAULT_WAIT_INTERVAL_SECONDS = 0.5;
|
|
22
|
+
var DEFAULT_WAIT_MAX_INTERVAL_SECONDS = 5;
|
|
23
|
+
var WAIT_BACKOFF_FACTOR = 2;
|
|
24
|
+
var WAIT_LAST_PASS_MS = 1e3;
|
|
25
|
+
var REGISTRY_LATEST_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
26
|
+
var REGISTRY_TIMEOUT_MS = 5e3;
|
|
27
|
+
var UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
28
|
+
var UPDATE_CACHE_FILE = "latest.json";
|
|
29
|
+
var REFRESH_COMMAND = "__refresh-update-cache";
|
|
30
|
+
var EXIT_DOWN = 1;
|
|
31
|
+
var EXIT_ERROR = 2;
|
|
32
|
+
|
|
33
|
+
// src/errors.ts
|
|
34
|
+
var ProbeError = class extends Error {
|
|
35
|
+
constructor(code, message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.code = code;
|
|
38
|
+
this.name = "ProbeError";
|
|
39
|
+
}
|
|
40
|
+
code;
|
|
41
|
+
exitCode = EXIT_ERROR;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// src/ui/terminal.ts
|
|
45
|
+
function version(text) {
|
|
46
|
+
const [major, minor] = (text ?? "").split(".").map((part) => Number.parseInt(part, 10));
|
|
47
|
+
return [major || 0, minor || 0];
|
|
48
|
+
}
|
|
49
|
+
function supportsHyperlinks(env) {
|
|
50
|
+
if (env.TMUX || /^(screen|tmux)/.test(env.TERM ?? "")) return false;
|
|
51
|
+
const [major, minor] = version(env.TERM_PROGRAM_VERSION);
|
|
52
|
+
switch (env.TERM_PROGRAM) {
|
|
53
|
+
case "iTerm.app":
|
|
54
|
+
return major > 3 || major === 3 && minor >= 1;
|
|
55
|
+
case "vscode":
|
|
56
|
+
return major > 1 || major === 1 && minor >= 72;
|
|
57
|
+
case "WezTerm":
|
|
58
|
+
case "ghostty":
|
|
59
|
+
case "WarpTerminal":
|
|
60
|
+
case "zed":
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
if (/^\d{4,}$/.test(env.VTE_VERSION ?? "")) return Number(env.VTE_VERSION) >= 5001;
|
|
64
|
+
return env.TERM === "alacritty" || env.TERM === "xterm-kitty";
|
|
65
|
+
}
|
|
66
|
+
function supportsUnicode(env) {
|
|
67
|
+
if (env.TERM === "linux") return false;
|
|
68
|
+
return /UTF-?8/i.test(env.LC_ALL || env.LC_CTYPE || env.LANG || "");
|
|
69
|
+
}
|
|
70
|
+
function detectTerminal(stream, env) {
|
|
71
|
+
const isTTY = Boolean(stream.isTTY);
|
|
72
|
+
const capable = isTTY && env.TERM !== "dumb";
|
|
73
|
+
const columns = isTTY && stream.columns && stream.columns > 0 ? stream.columns : void 0;
|
|
74
|
+
return {
|
|
75
|
+
isTTY,
|
|
76
|
+
color: capable && !env.NO_COLOR,
|
|
77
|
+
columns,
|
|
78
|
+
hyperlinks: capable && supportsHyperlinks(env),
|
|
79
|
+
unicode: capable && supportsUnicode(env)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function isInteractive(stdin, stdout, env) {
|
|
83
|
+
return Boolean(stdin.isTTY) && Boolean(stdout.isTTY) && env.TERM !== "dumb" && !env.CI;
|
|
84
|
+
}
|
|
85
|
+
var ELLIPSIS = "\u2026";
|
|
86
|
+
function truncate(text, max) {
|
|
87
|
+
const characters = Array.from(text);
|
|
88
|
+
if (characters.length <= max) return text;
|
|
89
|
+
return max <= 1 ? ELLIPSIS : `${characters.slice(0, max - 1).join("")}${ELLIPSIS}`;
|
|
90
|
+
}
|
|
91
|
+
var SGR_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
92
|
+
function stripStyles(text) {
|
|
93
|
+
return text.replace(SGR_PATTERN, "");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/checkers/http.ts
|
|
97
|
+
var REASONS = {
|
|
98
|
+
ECONNREFUSED: "connection refused",
|
|
99
|
+
ECONNRESET: "connection reset",
|
|
100
|
+
ENOTFOUND: "host not found",
|
|
101
|
+
EAI_AGAIN: "host not found",
|
|
102
|
+
EHOSTUNREACH: "host unreachable"
|
|
103
|
+
};
|
|
104
|
+
function targetUrl(service) {
|
|
105
|
+
const bareIpv6 = service.host.includes(":") && !service.host.startsWith("[");
|
|
106
|
+
const host = bareIpv6 ? `[${service.host}]` : service.host;
|
|
107
|
+
return `http://${host}:${service.port}${service.path}`;
|
|
108
|
+
}
|
|
109
|
+
function isExpected(expect, status) {
|
|
110
|
+
if (expect === void 0) return status >= 200 && status < 300;
|
|
111
|
+
return Array.isArray(expect) ? expect.includes(status) : expect === status;
|
|
112
|
+
}
|
|
113
|
+
function expectsRedirect(expect) {
|
|
114
|
+
return [expect ?? []].flat().some((status) => status >= 300 && status < 400);
|
|
115
|
+
}
|
|
116
|
+
function describeExpected(expect) {
|
|
117
|
+
if (expect === void 0) return "2xx";
|
|
118
|
+
return Array.isArray(expect) ? expect.join(" or ") : String(expect);
|
|
119
|
+
}
|
|
120
|
+
function failureReason(error, timeoutMs) {
|
|
121
|
+
if (error instanceof Error && error.name === "TimeoutError") {
|
|
122
|
+
return `timeout after ${timeoutMs / 1e3}s`;
|
|
123
|
+
}
|
|
124
|
+
const cause = error?.cause;
|
|
125
|
+
const code = cause?.code ?? cause?.errors?.[0]?.code;
|
|
126
|
+
if (code) return REASONS[code] ?? code;
|
|
127
|
+
return error instanceof Error ? error.message : String(error);
|
|
128
|
+
}
|
|
129
|
+
var httpChecker = {
|
|
130
|
+
type: "http",
|
|
131
|
+
async check(name, service, { timeoutMs, signal }) {
|
|
132
|
+
const target = targetUrl(service);
|
|
133
|
+
const base = { name, type: "http", target };
|
|
134
|
+
const started = performance.now();
|
|
135
|
+
const elapsed = () => Math.round(performance.now() - started);
|
|
136
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
137
|
+
try {
|
|
138
|
+
const response = await fetch(target, {
|
|
139
|
+
signal: signal ? AbortSignal.any([timeout, signal]) : timeout,
|
|
140
|
+
// Following a redirect hides its status: an expected 3xx could never match.
|
|
141
|
+
redirect: expectsRedirect(service.expect) ? "manual" : "follow"
|
|
142
|
+
});
|
|
143
|
+
const latencyMs = elapsed();
|
|
144
|
+
await response.body?.cancel();
|
|
145
|
+
const { status } = response;
|
|
146
|
+
if (isExpected(service.expect, status)) return { ...base, ok: true, status, latencyMs };
|
|
147
|
+
const error = `expected ${describeExpected(service.expect)}, got ${status}`;
|
|
148
|
+
return { ...base, ok: false, status, latencyMs, error };
|
|
149
|
+
} catch (error) {
|
|
150
|
+
return { ...base, ok: false, latencyMs: elapsed(), error: failureReason(error, timeoutMs) };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// src/checkers/registry.ts
|
|
156
|
+
var checkers = new Map([httpChecker].map((checker) => [checker.type, checker]));
|
|
157
|
+
function getChecker(type) {
|
|
158
|
+
const checker = checkers.get(type);
|
|
159
|
+
if (!checker) {
|
|
160
|
+
const known = [...checkers.keys()].join(", ");
|
|
161
|
+
throw new ProbeError("CHECKER_UNKNOWN", `unknown service type "${type}". Known: ${known}.`);
|
|
162
|
+
}
|
|
163
|
+
return checker;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/checkers/run.ts
|
|
167
|
+
async function runChecks(config, names, context, skipped = /* @__PURE__ */ new Set()) {
|
|
168
|
+
const known = Object.keys(config.services);
|
|
169
|
+
const unknown = names.filter((name) => !known.includes(name));
|
|
170
|
+
if (unknown.length > 0) {
|
|
171
|
+
throw new ProbeError(
|
|
172
|
+
"SERVICE_UNKNOWN",
|
|
173
|
+
`unknown service: ${unknown.join(", ")}. Known: ${known.join(", ") || "(none)"}.`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
const selected = names.length > 0 ? [...new Set(names)] : known;
|
|
177
|
+
return Promise.all(
|
|
178
|
+
selected.map((name) => {
|
|
179
|
+
const service = config.services[name];
|
|
180
|
+
const type = service.type ?? DEFAULT_CHECKER_TYPE;
|
|
181
|
+
if (names.length === 0 && skipped.has(name)) {
|
|
182
|
+
const result = {
|
|
183
|
+
name,
|
|
184
|
+
type,
|
|
185
|
+
ok: true,
|
|
186
|
+
skipped: true,
|
|
187
|
+
target: targetUrl(service),
|
|
188
|
+
latencyMs: 0
|
|
189
|
+
};
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
return getChecker(type).check(name, service, context);
|
|
193
|
+
})
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/ui/colors.ts
|
|
198
|
+
function createColors(enabled) {
|
|
199
|
+
const wrap = (open, close) => (text) => enabled ? `\x1B[${open}m${text}\x1B[${close}m` : text;
|
|
200
|
+
return {
|
|
201
|
+
green: wrap(32, 39),
|
|
202
|
+
red: wrap(31, 39),
|
|
203
|
+
yellow: wrap(33, 39),
|
|
204
|
+
dim: wrap(2, 22),
|
|
205
|
+
bold: wrap(1, 22)
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/ui/report.ts
|
|
210
|
+
var GAP = " ";
|
|
211
|
+
var SKIPPED = "skipped";
|
|
212
|
+
function width(values) {
|
|
213
|
+
return Math.max(0, ...values.map((value) => value.length));
|
|
214
|
+
}
|
|
215
|
+
function nameBudget(nameWidth, columns, reserved) {
|
|
216
|
+
return Math.max(1, Math.min(nameWidth, Math.floor((columns - reserved) / 2)));
|
|
217
|
+
}
|
|
218
|
+
function formatCheckResults(results, terminal) {
|
|
219
|
+
const colors = createColors(terminal.color);
|
|
220
|
+
const nameWidth = width(results.map((result) => result.name));
|
|
221
|
+
const targetWidth = width(results.map((result) => result.target));
|
|
222
|
+
const outcomes = results.map((result) => {
|
|
223
|
+
if (result.skipped) return SKIPPED;
|
|
224
|
+
return result.ok ? `${result.status}${GAP}${result.latencyMs}ms` : result.error ?? "down";
|
|
225
|
+
});
|
|
226
|
+
const fullWidth = 2 + nameWidth + GAP.length + targetWidth + GAP.length + width(outcomes);
|
|
227
|
+
const { columns } = terminal;
|
|
228
|
+
const compact = columns !== void 0 && fullWidth > columns;
|
|
229
|
+
const nameMax = compact ? nameBudget(nameWidth, columns, 2 + GAP.length) : nameWidth;
|
|
230
|
+
const outcomeMax = compact ? Math.max(1, columns - 2 - nameMax - GAP.length) : void 0;
|
|
231
|
+
return results.map((result, index) => {
|
|
232
|
+
const mark = result.skipped ? colors.dim("-") : result.ok ? colors.green("\u2713") : colors.red("\u2717");
|
|
233
|
+
const name = colors.bold(truncate(result.name, nameMax).padEnd(nameMax));
|
|
234
|
+
const target = compact ? "" : `${colors.dim(result.target.padEnd(targetWidth))}${GAP}`;
|
|
235
|
+
const plain = outcomes[index];
|
|
236
|
+
let outcome;
|
|
237
|
+
if (result.skipped) {
|
|
238
|
+
outcome = colors.dim(outcomeMax === void 0 ? plain : truncate(plain, outcomeMax));
|
|
239
|
+
} else if (!result.ok) {
|
|
240
|
+
outcome = colors.red(outcomeMax === void 0 ? plain : truncate(plain, outcomeMax));
|
|
241
|
+
} else if (outcomeMax !== void 0 && plain.length > outcomeMax) {
|
|
242
|
+
outcome = colors.green(truncate(String(result.status), outcomeMax));
|
|
243
|
+
} else {
|
|
244
|
+
outcome = `${colors.green(String(result.status))}${GAP}${colors.dim(`${result.latencyMs}ms`)}`;
|
|
245
|
+
}
|
|
246
|
+
return `${mark} ${name}${GAP}${target}${outcome}`;
|
|
247
|
+
}).join("\n");
|
|
248
|
+
}
|
|
249
|
+
function formatServices(config, terminal, skipped = /* @__PURE__ */ new Set()) {
|
|
250
|
+
const colors = createColors(terminal.color);
|
|
251
|
+
const rows = Object.entries(config.services).map(([name, service]) => ({
|
|
252
|
+
name,
|
|
253
|
+
url: targetUrl(service),
|
|
254
|
+
notes: (service.expect === void 0 ? "" : `${GAP}expect ${service.expect}`) + (skipped.has(name) ? `${GAP}${SKIPPED}` : "")
|
|
255
|
+
}));
|
|
256
|
+
const nameWidth = width(rows.map((row) => row.name));
|
|
257
|
+
const fullWidth = width(rows.map((row) => " ".repeat(nameWidth) + GAP + row.url + row.notes));
|
|
258
|
+
const { columns } = terminal;
|
|
259
|
+
const compact = columns !== void 0 && fullWidth > columns;
|
|
260
|
+
const nameMax = compact ? nameBudget(nameWidth, columns, GAP.length) : nameWidth;
|
|
261
|
+
const urlMax = compact ? Math.max(1, columns - nameMax - GAP.length) : void 0;
|
|
262
|
+
return rows.map((row) => {
|
|
263
|
+
const name = colors.bold(truncate(row.name, nameMax).padEnd(nameMax));
|
|
264
|
+
const url = urlMax === void 0 ? row.url : truncate(row.url, urlMax);
|
|
265
|
+
return `${name}${GAP}${url}${compact || !row.notes ? "" : colors.dim(row.notes)}`;
|
|
266
|
+
}).join("\n");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export {
|
|
270
|
+
PACKAGE_NAME,
|
|
271
|
+
PROBE_DIR,
|
|
272
|
+
CONFIG_FILE,
|
|
273
|
+
LOCK_FILE,
|
|
274
|
+
LOCAL_FILE,
|
|
275
|
+
BACKUP_GLOB,
|
|
276
|
+
LOCAL_DRAFT_FILE,
|
|
277
|
+
LOCAL_FILE_MODE,
|
|
278
|
+
DEFAULT_MAKEFILE,
|
|
279
|
+
DEFAULT_TIMEOUT_SECONDS,
|
|
280
|
+
DASHBOARD_INTERVAL_SECONDS,
|
|
281
|
+
DASHBOARD_INTERVAL_CHOICES_SECONDS,
|
|
282
|
+
DASHBOARD_TIMEOUT_CHOICES_SECONDS,
|
|
283
|
+
MAX_TIMEOUT_SECONDS,
|
|
284
|
+
DEFAULT_WAIT_TIMEOUT_SECONDS,
|
|
285
|
+
DEFAULT_WAIT_INTERVAL_SECONDS,
|
|
286
|
+
DEFAULT_WAIT_MAX_INTERVAL_SECONDS,
|
|
287
|
+
WAIT_BACKOFF_FACTOR,
|
|
288
|
+
WAIT_LAST_PASS_MS,
|
|
289
|
+
REGISTRY_LATEST_URL,
|
|
290
|
+
REGISTRY_TIMEOUT_MS,
|
|
291
|
+
UPDATE_CHECK_TTL_MS,
|
|
292
|
+
UPDATE_CACHE_FILE,
|
|
293
|
+
REFRESH_COMMAND,
|
|
294
|
+
EXIT_DOWN,
|
|
295
|
+
EXIT_ERROR,
|
|
296
|
+
ProbeError,
|
|
297
|
+
createColors,
|
|
298
|
+
detectTerminal,
|
|
299
|
+
isInteractive,
|
|
300
|
+
truncate,
|
|
301
|
+
stripStyles,
|
|
302
|
+
runChecks,
|
|
303
|
+
formatCheckResults,
|
|
304
|
+
formatServices
|
|
305
|
+
};
|