@ninhache/probe 0.6.0 → 0.7.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 +9 -2
- package/dist/cli.js +151 -35
- package/dist/dist-SITQEC3X.js +2894 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ Want `make check` too? See [Makefile](#makefile): it is optional.
|
|
|
41
41
|
| --------------------------------------------------------- | ---------------------------------------------------------------------- |
|
|
42
42
|
| `probe init` | Create `.probe/` and the `.gitignore` entries |
|
|
43
43
|
| `probe add <name> -p <port> [--host] [--path] [--expect]` | Add a service (`--timeout <seconds>` gives it its own timeout) |
|
|
44
|
-
| `probe rm <name
|
|
44
|
+
| `probe rm <name> [--yes]` | Remove a service |
|
|
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 |
|
|
@@ -64,10 +64,17 @@ longest one. Unlike `check --timeout`, which is the time given to each service,
|
|
|
64
64
|
wait; each check inside it still gets 3 s, or the service's own `timeout`. Progress goes to stderr, the final
|
|
65
65
|
report to stdout; exit code `1` names the services still down.
|
|
66
66
|
|
|
67
|
+
`probe add` alone, on a terminal, asks for the name, the port, the path and the expected status, one at a time:
|
|
68
|
+
Enter alone keeps the default, Ctrl+C adds nothing. Off a terminal it needs its arguments, as before.
|
|
69
|
+
|
|
70
|
+
`probe rm` is the one command that deletes something. On a terminal it asks first, with "No" selected: `--yes`
|
|
71
|
+
(`-y`) skips the question. Off a terminal, in a script or in CI, it removes at once, as before.
|
|
72
|
+
|
|
67
73
|
`probe open` uses `open` on macOS and `xdg-open` elsewhere. Without it, the URL is printed and the exit code is
|
|
68
74
|
`1`. `probe curl api` prints a command to copy, or to run: `probe curl api | sh`.
|
|
69
75
|
|
|
70
|
-
`probe` works from any sub-directory: it walks up to the nearest `.probe/`.
|
|
76
|
+
`probe` works from any sub-directory: it walks up to the nearest `.probe/`. When there is none, on a terminal,
|
|
77
|
+
it offers to create it where you are, then runs your command. Off a terminal it stops with exit code `2`.
|
|
71
78
|
|
|
72
79
|
## Completion
|
|
73
80
|
|
package/dist/cli.js
CHANGED
|
@@ -51,6 +51,53 @@ import {
|
|
|
51
51
|
// src/commands/add.ts
|
|
52
52
|
import { defineCommand } from "citty";
|
|
53
53
|
|
|
54
|
+
// src/config/schema.ts
|
|
55
|
+
import { z } from "zod";
|
|
56
|
+
var CURRENT_SCHEMA_VERSION = 1;
|
|
57
|
+
var SERVICE_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
|
|
58
|
+
var statusCode = z.number().int().min(100).max(599);
|
|
59
|
+
var serviceSchemaV1 = z.strictObject({
|
|
60
|
+
host: z.string().min(1).default("localhost"),
|
|
61
|
+
port: z.number().int().min(1).max(65535),
|
|
62
|
+
path: z.string().startsWith("/").default("/"),
|
|
63
|
+
expect: z.union([statusCode, z.array(statusCode).min(1)]).optional(),
|
|
64
|
+
/** Seconds given to this service to answer, instead of the default. */
|
|
65
|
+
timeout: z.number().positive().max(MAX_TIMEOUT_SECONDS).optional()
|
|
66
|
+
});
|
|
67
|
+
var configSchemaV1 = z.strictObject({
|
|
68
|
+
schemaVersion: z.literal(1),
|
|
69
|
+
services: z.record(
|
|
70
|
+
z.string().regex(SERVICE_NAME_PATTERN, "letters, digits, _ and - only"),
|
|
71
|
+
serviceSchemaV1
|
|
72
|
+
).nullish().transform((services) => services ?? {})
|
|
73
|
+
});
|
|
74
|
+
var schemas = {
|
|
75
|
+
1: configSchemaV1
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// src/ui/prompt.ts
|
|
79
|
+
async function confirm(message, streams = {}) {
|
|
80
|
+
const { confirm: ask2, isCancel } = await import("./dist-SITQEC3X.js");
|
|
81
|
+
const answer = await ask2({ message, initialValue: false, ...streams });
|
|
82
|
+
return !isCancel(answer) && answer;
|
|
83
|
+
}
|
|
84
|
+
async function ask(message, question = {}, streams = {}) {
|
|
85
|
+
const { text, isCancel } = await import("./dist-SITQEC3X.js");
|
|
86
|
+
const { placeholder, defaultValue, validate: validate2 } = question;
|
|
87
|
+
const answer = await text({
|
|
88
|
+
message,
|
|
89
|
+
placeholder,
|
|
90
|
+
defaultValue,
|
|
91
|
+
validate: (typed) => {
|
|
92
|
+
const value = typed ?? "";
|
|
93
|
+
return value === "" && defaultValue !== void 0 ? void 0 : validate2?.(value);
|
|
94
|
+
},
|
|
95
|
+
...streams
|
|
96
|
+
});
|
|
97
|
+
if (isCancel(answer)) return null;
|
|
98
|
+
return answer || (defaultValue ?? "");
|
|
99
|
+
}
|
|
100
|
+
|
|
54
101
|
// src/config/discovery.ts
|
|
55
102
|
import { statSync } from "fs";
|
|
56
103
|
import { dirname, join, resolve } from "path";
|
|
@@ -92,30 +139,6 @@ import { copyFile, readFile, writeFile } from "fs/promises";
|
|
|
92
139
|
import { join as join2, relative } from "path";
|
|
93
140
|
import { parseDocument } from "yaml";
|
|
94
141
|
|
|
95
|
-
// src/config/schema.ts
|
|
96
|
-
import { z } from "zod";
|
|
97
|
-
var CURRENT_SCHEMA_VERSION = 1;
|
|
98
|
-
var SERVICE_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
|
|
99
|
-
var statusCode = z.number().int().min(100).max(599);
|
|
100
|
-
var serviceSchemaV1 = z.strictObject({
|
|
101
|
-
host: z.string().min(1).default("localhost"),
|
|
102
|
-
port: z.number().int().min(1).max(65535),
|
|
103
|
-
path: z.string().startsWith("/").default("/"),
|
|
104
|
-
expect: z.union([statusCode, z.array(statusCode).min(1)]).optional(),
|
|
105
|
-
/** Seconds given to this service to answer, instead of the default. */
|
|
106
|
-
timeout: z.number().positive().max(MAX_TIMEOUT_SECONDS).optional()
|
|
107
|
-
});
|
|
108
|
-
var configSchemaV1 = z.strictObject({
|
|
109
|
-
schemaVersion: z.literal(1),
|
|
110
|
-
services: z.record(
|
|
111
|
-
z.string().regex(SERVICE_NAME_PATTERN, "letters, digits, _ and - only"),
|
|
112
|
-
serviceSchemaV1
|
|
113
|
-
).nullish().transform((services) => services ?? {})
|
|
114
|
-
});
|
|
115
|
-
var schemas = {
|
|
116
|
-
1: configSchemaV1
|
|
117
|
-
};
|
|
118
|
-
|
|
119
142
|
// src/config/migrations.ts
|
|
120
143
|
var migrations = {};
|
|
121
144
|
function formatIssues(error) {
|
|
@@ -319,7 +342,7 @@ async function writeLock(paths, lock) {
|
|
|
319
342
|
}
|
|
320
343
|
|
|
321
344
|
// src/version.ts
|
|
322
|
-
var CLI_VERSION = true ? "0.
|
|
345
|
+
var CLI_VERSION = true ? "0.7.0" : "0.0.0-dev";
|
|
323
346
|
|
|
324
347
|
// src/makefile/generator.ts
|
|
325
348
|
import { createHash } from "crypto";
|
|
@@ -463,6 +486,65 @@ function parseExpect(value) {
|
|
|
463
486
|
const codes = value.split(",").map((code) => parseInteger(code.trim(), "--expect"));
|
|
464
487
|
return codes.length === 1 ? codes[0] : codes;
|
|
465
488
|
}
|
|
489
|
+
var INTEGER = /^\d+$/;
|
|
490
|
+
var DEFAULT_PATH = "/";
|
|
491
|
+
function schemaProblem(schema, value) {
|
|
492
|
+
const result = schema.safeParse(value);
|
|
493
|
+
return result.success ? void 0 : result.error.issues[0]?.message;
|
|
494
|
+
}
|
|
495
|
+
function nameProblem(name, services) {
|
|
496
|
+
if (!SERVICE_NAME_PATTERN.test(name)) return "Letters, digits, _ and - only, e.g. api";
|
|
497
|
+
if (Object.hasOwn(services, name)) return `${name} already exists`;
|
|
498
|
+
return void 0;
|
|
499
|
+
}
|
|
500
|
+
function portProblem(port) {
|
|
501
|
+
if (!INTEGER.test(port)) return "A port number, e.g. 3000";
|
|
502
|
+
return schemaProblem(serviceSchemaV1.shape.port, Number(port)) && "A port between 1 and 65535";
|
|
503
|
+
}
|
|
504
|
+
function expectProblem(expect) {
|
|
505
|
+
const codes = expect.split(",").map((code) => code.trim());
|
|
506
|
+
if (!codes.every((code) => INTEGER.test(code))) return "A status, or several: 200 or 200,204";
|
|
507
|
+
return schemaProblem(serviceSchemaV1.shape.expect, parseExpect(expect)) && "Statuses go from 100 to 599";
|
|
508
|
+
}
|
|
509
|
+
async function guidedAdd() {
|
|
510
|
+
const loaded = await openProject();
|
|
511
|
+
const services = rawServices(loaded);
|
|
512
|
+
const answers = [];
|
|
513
|
+
const questions = [
|
|
514
|
+
{
|
|
515
|
+
message: "Service name",
|
|
516
|
+
placeholder: "api",
|
|
517
|
+
validate: (v) => nameProblem(v, services)
|
|
518
|
+
},
|
|
519
|
+
{ message: "Port", placeholder: "3000", validate: portProblem },
|
|
520
|
+
{
|
|
521
|
+
message: "Path to request",
|
|
522
|
+
placeholder: DEFAULT_PATH,
|
|
523
|
+
defaultValue: DEFAULT_PATH,
|
|
524
|
+
validate: (v) => schemaProblem(serviceSchemaV1.shape.path, v)
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
message: "Expected status",
|
|
528
|
+
placeholder: "any 2xx",
|
|
529
|
+
defaultValue: "",
|
|
530
|
+
validate: expectProblem
|
|
531
|
+
}
|
|
532
|
+
];
|
|
533
|
+
for (const { message, ...question } of questions) {
|
|
534
|
+
const answer = await ask(message, question);
|
|
535
|
+
if (answer === null) {
|
|
536
|
+
console.log("Nothing added.");
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
answers.push(answer);
|
|
540
|
+
}
|
|
541
|
+
const [name, port, path, expect] = answers;
|
|
542
|
+
const service = { port: Number(port) };
|
|
543
|
+
if (path !== DEFAULT_PATH) service.path = path;
|
|
544
|
+
if (expect) service.expect = parseExpect(expect);
|
|
545
|
+
await saveServices(loaded, { ...services, [name]: service });
|
|
546
|
+
console.log(`Added ${name}. Try: probe check ${name}`);
|
|
547
|
+
}
|
|
466
548
|
var add_default = defineCommand({
|
|
467
549
|
meta: { name: "add", description: "Add a service" },
|
|
468
550
|
args: {
|
|
@@ -832,19 +914,22 @@ async function ignoreLocalFiles(root) {
|
|
|
832
914
|
await writeFile5(file, `${current}${separator}${missing.join("\n")}
|
|
833
915
|
`);
|
|
834
916
|
}
|
|
917
|
+
async function initProject(root) {
|
|
918
|
+
const paths = pathsFor(root);
|
|
919
|
+
if (existsSync(paths.dir)) {
|
|
920
|
+
throw new ProbeError("ALREADY_INITIALIZED", `${PROBE_DIR}/ already exists here.`);
|
|
921
|
+
}
|
|
922
|
+
await mkdir(paths.dir);
|
|
923
|
+
await writeFile5(paths.config, INITIAL_CONFIG);
|
|
924
|
+
await ignoreLocalFiles(paths.root);
|
|
925
|
+
}
|
|
835
926
|
var init_default = defineCommand5({
|
|
836
927
|
meta: {
|
|
837
928
|
name: "init",
|
|
838
929
|
description: "Create .probe/ and the .gitignore entries"
|
|
839
930
|
},
|
|
840
931
|
async run() {
|
|
841
|
-
|
|
842
|
-
if (existsSync(paths.dir)) {
|
|
843
|
-
throw new ProbeError("ALREADY_INITIALIZED", `${PROBE_DIR}/ already exists here.`);
|
|
844
|
-
}
|
|
845
|
-
await mkdir(paths.dir);
|
|
846
|
-
await writeFile5(paths.config, INITIAL_CONFIG);
|
|
847
|
-
await ignoreLocalFiles(paths.root);
|
|
932
|
+
await initProject(process.cwd());
|
|
848
933
|
console.log(`Created ${PROBE_DIR}/. Next: probe add api -p 3000`);
|
|
849
934
|
}
|
|
850
935
|
});
|
|
@@ -920,7 +1005,8 @@ import { defineCommand as defineCommand9 } from "citty";
|
|
|
920
1005
|
var rm_default = defineCommand9({
|
|
921
1006
|
meta: { name: "rm", description: "Remove a service" },
|
|
922
1007
|
args: {
|
|
923
|
-
name: { type: "positional", description: "Service name", required: true }
|
|
1008
|
+
name: { type: "positional", description: "Service name", required: true },
|
|
1009
|
+
yes: { type: "boolean", alias: "y", description: "Remove without asking" }
|
|
924
1010
|
},
|
|
925
1011
|
async run({ args: args2 }) {
|
|
926
1012
|
const loaded = await openProject();
|
|
@@ -928,6 +1014,11 @@ var rm_default = defineCommand9({
|
|
|
928
1014
|
if (!Object.hasOwn(services, args2.name)) {
|
|
929
1015
|
throw new ProbeError("SERVICE_UNKNOWN", `unknown service "${args2.name}". See: probe ls`);
|
|
930
1016
|
}
|
|
1017
|
+
const asks = !args2.yes && isInteractive(process.stdin, process.stdout, process.env);
|
|
1018
|
+
if (asks && !await confirm(`Remove ${args2.name}?`)) {
|
|
1019
|
+
console.log("Nothing removed.");
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
931
1022
|
const { [args2.name]: _removed, ...rest } = services;
|
|
932
1023
|
await saveServices(loaded, rest);
|
|
933
1024
|
console.log(`Removed ${args2.name}.`);
|
|
@@ -1279,7 +1370,10 @@ var EXAMPLES = {
|
|
|
1279
1370
|
{ command: "probe add admin -p 8080 --expect 200,204", note: "Accept these statuses only" },
|
|
1280
1371
|
{ command: "probe add db -p 5432 --timeout 10", note: "Give it 10 s to answer" }
|
|
1281
1372
|
],
|
|
1282
|
-
rm: [
|
|
1373
|
+
rm: [
|
|
1374
|
+
{ command: "probe rm api", note: "Remove the service named api" },
|
|
1375
|
+
{ command: "probe rm api --yes", note: "Remove it without the question" }
|
|
1376
|
+
],
|
|
1283
1377
|
ls: [{ command: "probe ls", note: "List the services and their URL" }],
|
|
1284
1378
|
check: [
|
|
1285
1379
|
{ command: "probe check", note: "Check every service" },
|
|
@@ -1409,6 +1503,23 @@ async function openedDashboard(rawArgs) {
|
|
|
1409
1503
|
for (const failure of saves.failures()) console.error(`probe: not saved: ${failure}`);
|
|
1410
1504
|
return true;
|
|
1411
1505
|
}
|
|
1506
|
+
async function offeredInit(error, rawArgs) {
|
|
1507
|
+
if (!(error instanceof ProbeError) || error.code !== "NO_PROBE_DIR") return false;
|
|
1508
|
+
if (rawArgs.includes("--json")) return false;
|
|
1509
|
+
if (!isInteractive(process.stdin, process.stdout, process.env)) return false;
|
|
1510
|
+
const root = process.cwd();
|
|
1511
|
+
if (!await confirm(`No ${PROBE_DIR}/ here. Create it in ${root}?`)) return false;
|
|
1512
|
+
await initProject(root);
|
|
1513
|
+
console.log(`Created ${PROBE_DIR}/.`);
|
|
1514
|
+
return true;
|
|
1515
|
+
}
|
|
1516
|
+
function wantsGuidedAdd(rawArgs) {
|
|
1517
|
+
return rawArgs.length === 1 && rawArgs[0] === "add" && isInteractive(process.stdin, process.stdout, process.env);
|
|
1518
|
+
}
|
|
1519
|
+
async function dispatch(rawArgs) {
|
|
1520
|
+
if (wantsGuidedAdd(rawArgs)) return guidedAdd();
|
|
1521
|
+
if (!await openedDashboard(rawArgs)) await runCommand(main, { rawArgs });
|
|
1522
|
+
}
|
|
1412
1523
|
var UNKNOWN_COMMAND = "E_UNKNOWN_COMMAND";
|
|
1413
1524
|
function didYouMean(error, rawArgs) {
|
|
1414
1525
|
if (error.code !== UNKNOWN_COMMAND) return "";
|
|
@@ -1432,7 +1543,12 @@ async function run(rawArgs) {
|
|
|
1432
1543
|
const wantsVersion = rawArgs.length === 1 && ["--version", "-v"].includes(rawArgs[0]);
|
|
1433
1544
|
if (wantsHelp || wantsVersion) return runMain(main, { rawArgs, showUsage: printUsage });
|
|
1434
1545
|
try {
|
|
1435
|
-
|
|
1546
|
+
try {
|
|
1547
|
+
await dispatch(rawArgs);
|
|
1548
|
+
} catch (error) {
|
|
1549
|
+
if (!await offeredInit(error, rawArgs)) throw error;
|
|
1550
|
+
await dispatch(rawArgs);
|
|
1551
|
+
}
|
|
1436
1552
|
} catch (error) {
|
|
1437
1553
|
process.exitCode = EXIT_ERROR;
|
|
1438
1554
|
if (error instanceof ProbeError) {
|