@ninhache/probe 0.2.0 → 0.4.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
@@ -27,23 +27,25 @@ Use the full name with `npx`: `npx probe` is another package.
27
27
 
28
28
  ## Quick start
29
29
 
30
- 1. `probe init` creates `.probe/` and a `probe` block in your `Makefile`.
30
+ 1. `probe init` creates `.probe/`.
31
31
  2. `probe add api -p 3000 --path /health` declares a service.
32
- 3. `probe check` or `make check` checks everything. Exit code is 1 if a service is down.
33
- 4. Commit `.probe/config.yml`, `.probe/lock.yml` and the `Makefile`.
32
+ 3. `probe check` checks everything. Exit code is 1 if a service is down.
33
+ 4. Commit `.probe/config.yml`.
34
+
35
+ Want `make check` too? See [Makefile](#makefile): it is optional.
34
36
 
35
37
  ## Commands
36
38
 
37
39
  | Command | What it does |
38
40
  | --------------------------------------------------------- | ---------------------------------------------------------------------- |
39
- | `probe init` | Create `.probe/`, the Makefile block and the `.gitignore` entries |
40
- | `probe add <name> -p <port> [--host] [--path] [--expect]` | Add a service |
41
+ | `probe init` | Create `.probe/` and the `.gitignore` entries |
42
+ | `probe add <name> -p <port> [--host] [--path] [--expect]` | Add a service (`--timeout <seconds>` gives it its own timeout) |
41
43
  | `probe rm <name>` | Remove a service |
42
44
  | `probe ls` | List services |
43
45
  | `probe check [name...] [--json] [--timeout <seconds>]` | Check all services, or the named ones |
44
46
  | `probe wait [name...] [--json] [--timeout <seconds>]` | Check again until every service is up, or give up after the timeout |
45
47
  | `probe status` | CLI version, pending migration, Makefile block state, available update |
46
- | `probe sync` | Regenerate the Makefile block |
48
+ | `probe sync` | Add or regenerate the Makefile block (optional) |
47
49
  | `probe migrate` | Migrate `.probe/config.yml` to the current schema |
48
50
 
49
51
  Exit codes: `0` all up, `1` a service is down, `2` usage or config error.
@@ -51,7 +53,8 @@ Exit codes: `0` all up, `1` a service is down, `2` usage or config error.
51
53
  `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
54
  and every 5 s, for 60 s in total. `--timeout` sets the total, `--interval` the first pause, `--max-interval` the
53
55
  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.
56
+ wait; each check inside it still gets 3 s, or the service's own `timeout`. Progress goes to stderr, the final
57
+ report to stdout; exit code `1` names the services still down.
55
58
 
56
59
  `probe` works from any sub-directory: it walks up to the nearest `.probe/`.
57
60
 
@@ -60,13 +63,13 @@ wait; each check inside it still gets 3 s. Progress goes to stderr, the final re
60
63
  `probe` alone, on a terminal, opens a live list of your services and checks them every 2 s
61
64
  (`probe --interval 5` for another pace). Off a terminal, in CI or in a pipe, nothing changes: use `probe check`.
62
65
 
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 |
66
+ | Key | What it does |
67
+ | --------------- | -------------------------------------- |
68
+ | `↑` `↓` `j` `k` | Move |
69
+ | `r` | Check now |
70
+ | `Enter` | Open the panel of the selected service |
71
+ | `o` | Options: interval and timeout |
72
+ | `q` | Quit |
70
73
 
71
74
  What you change there is saved in `.probe/local.yml`, for your machine only:
72
75
 
@@ -79,6 +82,9 @@ services:
79
82
  A skipped service is left out of `probe check`, `probe wait` and the dashboard, and shown as `skipped`. Name it
80
83
  (`probe check db`) and it is checked anyway.
81
84
 
85
+ The panel of a service also sets its timeout. That one is shared: it is written to `.probe/config.yml`, so
86
+ commit it.
87
+
82
88
  ## Config
83
89
 
84
90
  `.probe/config.yml`:
@@ -89,14 +95,19 @@ services:
89
95
  api: { port: 3000, path: /health }
90
96
  front: { port: 5173 }
91
97
  admin: { host: 127.0.0.1, port: 8080, expect: [200, 204] }
98
+ db: { port: 5432, timeout: 10 }
92
99
  ```
93
100
 
94
- | Key | Default | Meaning |
95
- | -------- | ----------- | -------------------------------------- |
96
- | `port` | required | TCP port |
97
- | `host` | `localhost` | Host name or IP |
98
- | `path` | `/` | Path requested with `GET` |
99
- | `expect` | any 2xx | Expected status, or a list of statuses |
101
+ | Key | Default | Meaning |
102
+ | --------- | ----------- | --------------------------------------- |
103
+ | `port` | required | TCP port |
104
+ | `host` | `localhost` | Host name or IP |
105
+ | `path` | `/` | Path requested with `GET` |
106
+ | `expect` | any 2xx | Expected status, or a list of statuses |
107
+ | `timeout` | 3 | Seconds given to this service to answer |
108
+
109
+ Which timeout applies: `--timeout` when you pass it, else the service's `timeout`, else 3 s (in the dashboard,
110
+ the timeout set in its options). A config with a `timeout` needs `probe` 0.4.0 or later: older versions refuse it.
100
111
 
101
112
  Redirects are followed and the final status is judged, unless `expect` contains a 3xx: then the first response
102
113
  is judged as is.
@@ -104,15 +115,15 @@ is judged as is.
104
115
  Files in `.probe/`:
105
116
 
106
117
  - `config.yml`: services. Commit it.
107
- - `lock.yml`: CLI version and hash of the Makefile block. Commit it.
118
+ - `lock.yml`: CLI version and hash of the Makefile block. Only there once `probe sync` has run. Commit it.
108
119
  - `local.yml`: per-machine values (dashboard settings, skipped services). Ignored by git.
109
120
 
110
121
  Never put secrets in `config.yml` or `lock.yml`.
111
122
 
112
123
  ## Makefile
113
124
 
114
- `probe init` and `probe sync` write a block between two markers. Everything outside the block is yours and is
115
- never touched.
125
+ Optional. `probe sync` writes a block between two markers, in a new `Makefile` or at the end of yours. Everything
126
+ outside the block is yours and is never touched. Without a block, no command creates or changes a `Makefile`.
116
127
 
117
128
  ```make
118
129
  # >>> probe (generated - do not edit, run `probe sync`)
@@ -120,6 +131,9 @@ never touched.
120
131
  # <<< probe
121
132
  ```
122
133
 
134
+ Once the block is there, `probe add` and `probe rm` keep it up to date. Commit the `Makefile` and
135
+ `.probe/lock.yml`.
136
+
123
137
  Targets: `help`, `check`, `status` and one `check-<name>` per service. The block uses `probe` when it is in
124
138
  your `PATH`, else `npx -y @ninhache/probe@latest`. It works with GNU Make 3.81 (the macOS default).
125
139
 
@@ -164,7 +164,12 @@ function getChecker(type) {
164
164
  }
165
165
 
166
166
  // src/checkers/run.ts
167
- async function runChecks(config, names, context, skipped = /* @__PURE__ */ new Set()) {
167
+ function resolveTimeoutMs(service, options) {
168
+ const own = options.forceTimeout || service.timeout === void 0 ? void 0 : service.timeout;
169
+ const wanted = own === void 0 ? options.timeoutMs : Math.ceil(own * 1e3);
170
+ return Math.max(1, Math.min(wanted, options.maxTimeoutMs ?? wanted));
171
+ }
172
+ async function runChecks(config, names, options, skipped = /* @__PURE__ */ new Set()) {
168
173
  const known = Object.keys(config.services);
169
174
  const unknown = names.filter((name) => !known.includes(name));
170
175
  if (unknown.length > 0) {
@@ -189,7 +194,10 @@ async function runChecks(config, names, context, skipped = /* @__PURE__ */ new S
189
194
  };
190
195
  return result;
191
196
  }
192
- return getChecker(type).check(name, service, context);
197
+ return getChecker(type).check(name, service, {
198
+ timeoutMs: resolveTimeoutMs(service, options),
199
+ signal: options.signal
200
+ });
193
201
  })
194
202
  );
195
203
  }
@@ -251,7 +259,7 @@ function formatServices(config, terminal, skipped = /* @__PURE__ */ new Set()) {
251
259
  const rows = Object.entries(config.services).map(([name, service]) => ({
252
260
  name,
253
261
  url: targetUrl(service),
254
- notes: (service.expect === void 0 ? "" : `${GAP}expect ${service.expect}`) + (skipped.has(name) ? `${GAP}${SKIPPED}` : "")
262
+ notes: (service.expect === void 0 ? "" : `${GAP}expect ${service.expect}`) + (service.timeout === void 0 ? "" : `${GAP}timeout ${service.timeout}s`) + (skipped.has(name) ? `${GAP}${SKIPPED}` : "")
255
263
  }));
256
264
  const nameWidth = width(rows.map((row) => row.name));
257
265
  const fullWidth = width(rows.map((row) => " ".repeat(nameWidth) + GAP + row.url + row.notes));
package/dist/cli.js CHANGED
@@ -34,7 +34,7 @@ import {
34
34
  isInteractive,
35
35
  runChecks,
36
36
  stripStyles
37
- } from "./chunk-ZCAUPLQS.js";
37
+ } from "./chunk-HHTOVIAQ.js";
38
38
  import "./chunk-M3ASEG5F.js";
39
39
 
40
40
  // src/cli.ts
@@ -98,7 +98,9 @@ var serviceSchemaV1 = z.strictObject({
98
98
  host: z.string().min(1).default("localhost"),
99
99
  port: z.number().int().min(1).max(65535),
100
100
  path: z.string().startsWith("/").default("/"),
101
- expect: z.union([statusCode, z.array(statusCode).min(1)]).optional()
101
+ expect: z.union([statusCode, z.array(statusCode).min(1)]).optional(),
102
+ /** Seconds given to this service to answer, instead of the default. */
103
+ timeout: z.number().positive().max(MAX_TIMEOUT_SECONDS).optional()
102
104
  });
103
105
  var configSchemaV1 = z.strictObject({
104
106
  schemaVersion: z.literal(1),
@@ -278,6 +280,15 @@ async function saveConfig(loaded, raw, options = {}) {
278
280
  await writeFile(loaded.paths.config, loaded.doc.toString());
279
281
  return { ...loaded, config, raw };
280
282
  }
283
+ async function writeServiceTimeout(paths, name, seconds2) {
284
+ const loaded = await loadConfig(paths);
285
+ const services = loaded.raw.services ?? {};
286
+ if (!Object.hasOwn(services, name)) return false;
287
+ const { timeout: _previous, ...service } = services[name];
288
+ if (seconds2 !== void 0) service.timeout = seconds2;
289
+ await saveConfig(loaded, { ...loaded.raw, services: { ...services, [name]: service } });
290
+ return true;
291
+ }
281
292
 
282
293
  // src/makefile/sync.ts
283
294
  import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
@@ -305,7 +316,7 @@ async function writeLock(paths, lock) {
305
316
  }
306
317
 
307
318
  // src/version.ts
308
- var CLI_VERSION = true ? "0.2.0" : "0.0.0-dev";
319
+ var CLI_VERSION = true ? "0.4.0" : "0.0.0-dev";
309
320
 
310
321
  // src/makefile/generator.ts
311
322
  import { createHash } from "crypto";
@@ -419,9 +430,10 @@ function rawServices(loaded) {
419
430
  return loaded.raw.services ?? {};
420
431
  }
421
432
  async function saveServices(loaded, services) {
422
- const edited = await blockState(loaded.paths, loaded.config) === "modified";
433
+ const block = await blockState(loaded.paths, loaded.config);
423
434
  const saved = await saveConfig(loaded, { ...loaded.raw, services });
424
- if (!edited) {
435
+ if (block === "missing") return;
436
+ if (block !== "modified") {
425
437
  await syncMakefile(saved.paths, saved.config);
426
438
  return;
427
439
  }
@@ -448,7 +460,11 @@ var add_default = defineCommand({
448
460
  port: { type: "string", alias: "p", description: "Port", required: true },
449
461
  host: { type: "string", description: "Host (default: localhost)" },
450
462
  path: { type: "string", description: "Path to request (default: /)" },
451
- expect: { type: "string", description: "Expected status, e.g. 200 or 200,204 (default: 2xx)" }
463
+ expect: { type: "string", description: "Expected status, e.g. 200 or 200,204 (default: 2xx)" },
464
+ timeout: {
465
+ type: "string",
466
+ description: `Seconds to wait for this service (default: ${DEFAULT_TIMEOUT_SECONDS})`
467
+ }
452
468
  },
453
469
  async run({ args }) {
454
470
  const loaded = await openProject();
@@ -463,6 +479,10 @@ var add_default = defineCommand({
463
479
  if (args.host) service.host = args.host;
464
480
  if (args.path) service.path = args.path;
465
481
  if (args.expect) service.expect = parseExpect(String(args.expect));
482
+ if (args.timeout !== void 0) {
483
+ secondsToMs("--timeout", String(args.timeout));
484
+ service.timeout = Number(args.timeout);
485
+ }
466
486
  await saveServices(loaded, { ...services, [args.name]: service });
467
487
  console.log(`Added ${args.name}. Try: probe check ${args.name}`);
468
488
  }
@@ -582,15 +602,16 @@ var check_default = defineCommand2({
582
602
  json: { type: "boolean", description: "Print results as JSON" },
583
603
  timeout: {
584
604
  type: "string",
585
- description: "Seconds to wait for each service",
586
- default: String(DEFAULT_TIMEOUT_SECONDS)
605
+ // No citty default: a value given here must be told apart from none, it beats the services' own.
606
+ description: `Seconds to wait for each service (default: its own timeout, else ${DEFAULT_TIMEOUT_SECONDS})`
587
607
  }
588
608
  },
589
609
  async run({ args }) {
590
- const timeoutMs = secondsToMs("--timeout", args.timeout);
610
+ const forceTimeout = args.timeout !== void 0;
611
+ const timeoutMs = secondsToMs("--timeout", args.timeout ?? String(DEFAULT_TIMEOUT_SECONDS));
591
612
  const { config, paths } = await openProject();
592
613
  const skipped = await readSkippedServices(paths);
593
- const results = await runChecks(config, args._, { timeoutMs }, skipped);
614
+ const results = await runChecks(config, args._, { timeoutMs, forceTimeout }, skipped);
594
615
  if (args.json) {
595
616
  console.log(JSON.stringify(results, null, 2));
596
617
  } else if (results.length === 0) {
@@ -625,7 +646,7 @@ async function ignoreLocalFiles(root) {
625
646
  var init_default = defineCommand3({
626
647
  meta: {
627
648
  name: "init",
628
- description: "Create .probe/, the .gitignore entries and the Makefile block"
649
+ description: "Create .probe/ and the .gitignore entries"
629
650
  },
630
651
  async run() {
631
652
  const paths = pathsFor(process.cwd());
@@ -635,9 +656,7 @@ var init_default = defineCommand3({
635
656
  await mkdir(paths.dir);
636
657
  await writeFile5(paths.config, INITIAL_CONFIG);
637
658
  await ignoreLocalFiles(paths.root);
638
- const { config } = await loadConfig(paths);
639
- await syncMakefile(paths, config);
640
- console.log(`Created ${PROBE_DIR}/ and the Makefile block. Next: probe add api -p 3000`);
659
+ console.log(`Created ${PROBE_DIR}/. Next: probe add api -p 3000`);
641
660
  }
642
661
  });
643
662
 
@@ -768,7 +787,7 @@ function describeBlock(state, colors) {
768
787
  case "outdated":
769
788
  return colors.yellow("out of date \u2192 run `probe sync`");
770
789
  case "missing":
771
- return colors.yellow("no probe block \u2192 run `probe sync`");
790
+ return colors.dim("no probe block (optional) \u2192 `probe sync` adds one");
772
791
  }
773
792
  }
774
793
  function describeUpdate(colors) {
@@ -802,7 +821,10 @@ var status_default = defineCommand7({
802
821
  import { relative as relative2 } from "path";
803
822
  import { defineCommand as defineCommand8 } from "citty";
804
823
  var sync_default = defineCommand8({
805
- meta: { name: "sync", description: "Regenerate the probe block of the Makefile" },
824
+ meta: {
825
+ name: "sync",
826
+ description: "Add or regenerate the probe block of the Makefile (optional)"
827
+ },
806
828
  async run() {
807
829
  const { config, paths } = await openProject();
808
830
  const { created, changed } = await syncMakefile(paths, config);
@@ -827,15 +849,16 @@ async function pollUntilUp(check, options) {
827
849
  let pause = Math.min(options.intervalMs, maxIntervalMs);
828
850
  let lastPass = false;
829
851
  while (true) {
830
- const results = await check(Math.max(1, Math.ceil(Math.min(attemptTimeoutMs, remaining()))));
852
+ const left = Math.max(1, Math.ceil(remaining()));
853
+ const results = await check(Math.min(attemptTimeoutMs, left), left);
831
854
  const elapsedMs = Math.round(now() - started);
832
855
  onPass?.(results, elapsedMs);
833
856
  if (results.every((result) => result.ok)) return { results, elapsedMs, timedOut: false };
834
- const left = remaining();
835
- if (lastPass || left <= 0) return { results, elapsedMs, timedOut: true };
836
- if (pause >= left) {
857
+ const rest = remaining();
858
+ if (lastPass || rest <= 0) return { results, elapsedMs, timedOut: true };
859
+ if (pause >= rest) {
837
860
  lastPass = true;
838
- await sleep(left - Math.min(WAIT_LAST_PASS_MS, left / 2));
861
+ await sleep(rest - Math.min(WAIT_LAST_PASS_MS, rest / 2));
839
862
  } else {
840
863
  await sleep(pause);
841
864
  pause = Math.min(pause * WAIT_BACKOFF_FACTOR, maxIntervalMs);
@@ -899,7 +922,13 @@ var wait_default = defineCommand9({
899
922
  const skipped = await readSkippedServices(paths);
900
923
  const report = progressReporter();
901
924
  const { results, timedOut } = await pollUntilUp(
902
- (timeoutMs) => runChecks(config, args._, { timeoutMs }, skipped),
925
+ // A service with its own timeout keeps it here, within what is left of the total.
926
+ (_passMs, remainingMs) => runChecks(
927
+ config,
928
+ args._,
929
+ { timeoutMs: DEFAULT_TIMEOUT_SECONDS * 1e3, maxTimeoutMs: remainingMs },
930
+ skipped
931
+ ),
903
932
  {
904
933
  deadlineMs,
905
934
  attemptTimeoutMs: DEFAULT_TIMEOUT_SECONDS * 1e3,
@@ -985,7 +1014,7 @@ async function openedDashboard(rawArgs) {
985
1014
  if (!asked) return false;
986
1015
  throw new ProbeError("USAGE", "No services yet. Add one: probe add api -p 3000");
987
1016
  }
988
- const { openDashboard } = await import("./open-TQ6NPJ7V.js");
1017
+ const { openDashboard } = await import("./open-QTP4Z5QX.js");
989
1018
  let settings = initialDashboardSettings(askedMs, await readDashboardPreferences(paths));
990
1019
  let saving = Promise.resolve();
991
1020
  const save = (write) => {
@@ -1000,7 +1029,8 @@ async function openedDashboard(rawArgs) {
1000
1029
  settings = next;
1001
1030
  save(() => writeDashboardPreferences(paths, changed));
1002
1031
  },
1003
- onSkipChange: (name, skip) => save(() => writeServiceSkip(paths, name, skip))
1032
+ onSkipChange: (name, skip) => save(() => writeServiceSkip(paths, name, skip)),
1033
+ onTimeoutChange: (name, seconds2) => save(() => writeServiceTimeout(paths, name, seconds2))
1004
1034
  });
1005
1035
  await saving;
1006
1036
  return true;
@@ -7,7 +7,7 @@ import {
7
7
  formatCheckResults,
8
8
  runChecks,
9
9
  truncate
10
- } from "./chunk-ZCAUPLQS.js";
10
+ } from "./chunk-HHTOVIAQ.js";
11
11
  import {
12
12
  __commonJS,
13
13
  __export,
@@ -20252,6 +20252,12 @@ var SETTING_ROWS = [
20252
20252
  { key: "intervalMs", label: "Interval", choicesMs: DASHBOARD_INTERVAL_CHOICES_SECONDS.map(toMs) },
20253
20253
  { key: "timeoutMs", label: "Timeout", choicesMs: DASHBOARD_TIMEOUT_CHOICES_SECONDS.map(toMs) }
20254
20254
  ];
20255
+ function stepServiceTimeout(current, direction) {
20256
+ const choices = DASHBOARD_TIMEOUT_CHOICES_SECONDS;
20257
+ if (current === void 0) return direction === 1 ? choices[0] : void 0;
20258
+ if (direction === 1) return choices.find((choice) => choice > current) ?? current;
20259
+ return choices.findLast((choice) => choice < current);
20260
+ }
20255
20261
  function stepChoice(choices, current, direction) {
20256
20262
  const next = direction === 1 ? choices.find((choice) => choice > current) : choices.findLast((choice) => choice < current);
20257
20263
  return next ?? current;
@@ -20259,7 +20265,9 @@ function stepChoice(choices, current, direction) {
20259
20265
 
20260
20266
  // src/dashboard/Dashboard.tsx
20261
20267
  var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
20262
- var SERVICE_ROWS = ["Skip"];
20268
+ var SERVICE_ROWS = ["Skip", "Timeout"];
20269
+ var TIMEOUT_ROW = SERVICE_ROWS.indexOf("Timeout");
20270
+ var NO_TIMEOUTS = {};
20263
20271
  var MARKER_WIDTH = 2;
20264
20272
  var PANEL_WIDTH = 30;
20265
20273
  var PANEL_FRAME_WIDTH = 4;
@@ -20268,9 +20276,11 @@ function Dashboard({
20268
20276
  terminal,
20269
20277
  settings: initial,
20270
20278
  skipped: initiallySkipped,
20279
+ timeouts: initialTimeouts = NO_TIMEOUTS,
20271
20280
  check: check2,
20272
20281
  onSettingsChange,
20273
- onSkipChange
20282
+ onSkipChange,
20283
+ onTimeoutChange
20274
20284
  }) {
20275
20285
  const { exit } = useApp();
20276
20286
  const { columns } = use_window_size_default();
@@ -20278,6 +20288,7 @@ function Dashboard({
20278
20288
  const [selected, setSelected] = (0, import_react36.useState)(0);
20279
20289
  const [settings, setSettings] = (0, import_react36.useState)(initial);
20280
20290
  const [skipped, setSkipped] = (0, import_react36.useState)(() => new Set(initiallySkipped));
20291
+ const [timeouts, setTimeouts] = (0, import_react36.useState)(initialTimeouts);
20281
20292
  const [panel, setPanel] = (0, import_react36.useState)(null);
20282
20293
  const skipPause = (0, import_react36.useRef)(() => {
20283
20294
  });
@@ -20287,7 +20298,7 @@ function Dashboard({
20287
20298
  const { signal } = controller;
20288
20299
  const loop = async () => {
20289
20300
  while (!signal.aborted) {
20290
- const next = await check2(signal, timeoutMs, skipped);
20301
+ const next = await check2(signal, timeoutMs, skipped, timeouts);
20291
20302
  if (signal.aborted) return;
20292
20303
  setLatest(next);
20293
20304
  await new Promise((resolve) => {
@@ -20302,7 +20313,7 @@ function Dashboard({
20302
20313
  };
20303
20314
  void loop();
20304
20315
  return () => controller.abort();
20305
- }, [check2, intervalMs, timeoutMs, skipped]);
20316
+ }, [check2, intervalMs, timeoutMs, skipped, timeouts]);
20306
20317
  const last = (latest?.length ?? 1) - 1;
20307
20318
  const current = Math.min(selected, last);
20308
20319
  const change = (rowIndex, direction) => {
@@ -20321,6 +20332,12 @@ function Dashboard({
20321
20332
  setSkipped(next);
20322
20333
  onSkipChange?.(name, skip);
20323
20334
  };
20335
+ const changeTimeout = (name, direction) => {
20336
+ const value = stepServiceTimeout(timeouts[name], direction);
20337
+ if (value === timeouts[name]) return;
20338
+ setTimeouts({ ...timeouts, [name]: value });
20339
+ onTimeoutChange?.(name, value);
20340
+ };
20324
20341
  use_input_default((input, key) => {
20325
20342
  const up2 = key.upArrow || input === "k";
20326
20343
  const down = key.downArrow || input === "j";
@@ -20342,6 +20359,7 @@ function Dashboard({
20342
20359
  setPanel({ ...panel, row: Math.max(0, Math.min(lastRow, panel.row + (down ? 1 : -1))) });
20343
20360
  } else if (left || right) {
20344
20361
  if (panel.kind === "options") change(panel.row, right ? 1 : -1);
20362
+ else if (panel.row === TIMEOUT_ROW) changeTimeout(panel.name, right ? 1 : -1);
20345
20363
  else toggleSkip(panel.name);
20346
20364
  }
20347
20365
  });
@@ -20371,7 +20389,13 @@ function Dashboard({
20371
20389
  }).split("\n");
20372
20390
  const focused = results[current];
20373
20391
  const panelWidth = Math.min(PANEL_WIDTH, columns);
20374
- const panelRows = panel?.kind === "options" ? SETTING_ROWS.map((row) => [row.label, seconds(settings[row.key])]) : panel ? SERVICE_ROWS.map((label) => [label, skipped.has(panel.name) ? "yes" : "no"]) : [];
20392
+ const panelRows = panel?.kind === "options" ? SETTING_ROWS.map((row) => [row.label, seconds(settings[row.key])]) : panel ? [
20393
+ ["Skip", skipped.has(panel.name) ? "yes" : "no"],
20394
+ [
20395
+ "Timeout",
20396
+ timeouts[panel.name] === void 0 ? "default" : `${timeouts[panel.name]}s`
20397
+ ]
20398
+ ] : [];
20375
20399
  const labelWidth = Math.max(0, ...panelRows.map(([label]) => label.length));
20376
20400
  const hint = panel === null ? `${unicode ? "\u2191\u2193 jk" : "jk"} move ${dot} r refresh ${dot} ${unicode ? "\u21B5" : "enter"} service ${dot} o options ${dot} q quit` : `${unicode ? "\u2191\u2193 jk" : "jk"} move ${dot} ${unicode ? "\u2190\u2192 hl" : "hl"} change ${dot} esc close`;
20377
20401
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { flexDirection: "column", children: [
@@ -20411,10 +20435,24 @@ function Dashboard({
20411
20435
  // src/dashboard/open.tsx
20412
20436
  var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
20413
20437
  async function openDashboard(config, terminal, state) {
20414
- const check2 = (signal, timeoutMs, skipped) => runChecks(config, [], { timeoutMs, signal }, skipped);
20415
- const app = render_default(/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dashboard, { terminal, check: check2, ...state }), {
20416
- alternateScreen: true
20417
- });
20438
+ const check2 = (signal, timeoutMs, skipped, timeouts2) => {
20439
+ const services = Object.fromEntries(
20440
+ Object.entries(config.services).map(([name, service]) => [
20441
+ name,
20442
+ { ...service, timeout: timeouts2[name] }
20443
+ ])
20444
+ );
20445
+ return runChecks({ ...config, services }, [], { timeoutMs, signal }, skipped);
20446
+ };
20447
+ const timeouts = Object.fromEntries(
20448
+ Object.entries(config.services).flatMap(
20449
+ ([name, { timeout }]) => timeout === void 0 ? [] : [[name, timeout]]
20450
+ )
20451
+ );
20452
+ const app = render_default(
20453
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Dashboard, { terminal, check: check2, timeouts, ...state }),
20454
+ { alternateScreen: true }
20455
+ );
20418
20456
  await app.waitUntilExit();
20419
20457
  }
20420
20458
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ninhache/probe",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Health checks for your local dev services, from the CLI or a generated Makefile",
5
5
  "license": "MIT",
6
6
  "author": "Ninhache",