@hardfin/cli 0.1.0-dev.25 → 0.1.0-dev.27
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 +30 -0
- package/dist/cli.js +25 -5
- package/dist/completion-tree.json +9 -0
- package/dist/{program-b3uLLsYy.js → program-C3SGdPbf.js} +3514 -3122
- package/dist/scrub-VtQmxap1.js +108 -0
- package/dist/sentry-D5lKx0nm.js +4384 -0
- package/package.json +2 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
//#region src/command/registry.ts
|
|
5
|
+
/** ExitCode is what the process returns, and what an agent branches on. */
|
|
6
|
+
const ExitCode = {
|
|
7
|
+
OK: 0,
|
|
8
|
+
ERROR: 1,
|
|
9
|
+
USAGE: 2,
|
|
10
|
+
NOT_AUTHENTICATED: 4,
|
|
11
|
+
INTERRUPTED: 130
|
|
12
|
+
};
|
|
13
|
+
/** defineCommand records one command so every surface reads the same declaration. */
|
|
14
|
+
function defineCommand(command) {
|
|
15
|
+
return command;
|
|
16
|
+
}
|
|
17
|
+
/** GLOBAL_FLAGS are the options the CLI takes before any command. */
|
|
18
|
+
const GLOBAL_FLAGS = [{
|
|
19
|
+
name: "api-url",
|
|
20
|
+
description: "The API to call, whose host also holds the authorization server",
|
|
21
|
+
valueName: "url",
|
|
22
|
+
schema: z.string()
|
|
23
|
+
}, {
|
|
24
|
+
name: "issuer-url",
|
|
25
|
+
description: "The authorization server, when it does not sit at the API's host",
|
|
26
|
+
valueName: "url",
|
|
27
|
+
schema: z.string()
|
|
28
|
+
}];
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/report/scrub.ts
|
|
31
|
+
const REDACTED = "[redacted]";
|
|
32
|
+
/**
|
|
33
|
+
* The shapes a secret takes when it is not one this process knows by value: a bearer
|
|
34
|
+
* credential, a JWT, and a token or key carried in a query string or form.
|
|
35
|
+
*/
|
|
36
|
+
const SECRET_PATTERNS = [
|
|
37
|
+
/(Bearer|Basic)\s+[^\s"',]+/gi,
|
|
38
|
+
/eyJ[\w-]+\.[\w-]+\.[\w-]+/g,
|
|
39
|
+
/((?:access_token|refresh_token|id_token|code|client_secret|api_key|apikey|token|key)=)[^&\s"']+/gi
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* toScrubbed removes what a report must never carry from a piece of text. The secrets this
|
|
43
|
+
* process holds are removed by value, the rest by shape, and the home directory becomes ~ so
|
|
44
|
+
* a path does not name the person.
|
|
45
|
+
*/
|
|
46
|
+
function toScrubbed(text, secrets = [], home = homedir()) {
|
|
47
|
+
let scrubbed = text;
|
|
48
|
+
for (const secret of secrets) if (secret.length >= 8) scrubbed = scrubbed.split(secret).join(REDACTED);
|
|
49
|
+
for (const pattern of SECRET_PATTERNS) scrubbed = scrubbed.replace(pattern, (match, prefix) => prefix && match.startsWith(prefix) && prefix.endsWith("=") ? `${prefix}${REDACTED}` : REDACTED);
|
|
50
|
+
return home.length > 1 ? scrubbed.split(home).join("~") : scrubbed;
|
|
51
|
+
}
|
|
52
|
+
/** The options every command answers, beside the ones it declares. */
|
|
53
|
+
const ALWAYS = [{
|
|
54
|
+
name: "help",
|
|
55
|
+
short: "h",
|
|
56
|
+
description: "Print help for the command",
|
|
57
|
+
schema: z.boolean()
|
|
58
|
+
}, {
|
|
59
|
+
name: "version",
|
|
60
|
+
short: "v",
|
|
61
|
+
description: "Print the version",
|
|
62
|
+
schema: z.boolean()
|
|
63
|
+
}];
|
|
64
|
+
/**
|
|
65
|
+
* toInvocation reads the command and the flag names from the words a person typed. Only a
|
|
66
|
+
* word naming a flag the command has is kept, so a value that happens to start with a dash,
|
|
67
|
+
* such as -500, is never sent. Values and arguments are left out, because they can hold
|
|
68
|
+
* customer data.
|
|
69
|
+
*/
|
|
70
|
+
function toInvocation(commands, args) {
|
|
71
|
+
const path = [];
|
|
72
|
+
const flags = [];
|
|
73
|
+
let available = commands;
|
|
74
|
+
let known = [...GLOBAL_FLAGS, ...ALWAYS];
|
|
75
|
+
let isValueNext = false;
|
|
76
|
+
for (const word of args) {
|
|
77
|
+
if (isValueNext) {
|
|
78
|
+
isValueNext = false;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const flag = word.startsWith("-") ? toFlag(known, word.split("=")[0] ?? word) : void 0;
|
|
82
|
+
if (flag) {
|
|
83
|
+
flags.push(word.split("=")[0] ?? word);
|
|
84
|
+
isValueNext = flag.valueName !== void 0 && !word.includes("=");
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const found = available.find((command) => command.name === word);
|
|
88
|
+
if (found) {
|
|
89
|
+
path.push(found.name);
|
|
90
|
+
available = found.subcommands ?? [];
|
|
91
|
+
known = [...found.flags, ...ALWAYS];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
command: path.join(" ") || "(none)",
|
|
96
|
+
flags
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** toFlag finds the flag a word names, whether long, short, or the --no- form of a boolean. */
|
|
100
|
+
function toFlag(flags, word) {
|
|
101
|
+
if (word.startsWith("--")) {
|
|
102
|
+
const name = word.slice(2);
|
|
103
|
+
return flags.find((flag) => flag.name === name || flag.negatable === true && `no-${flag.name}` === name);
|
|
104
|
+
}
|
|
105
|
+
return flags.find((flag) => flag.short !== void 0 && `-${flag.short}` === word);
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
export { defineCommand as a, GLOBAL_FLAGS as i, toScrubbed as n, ExitCode as r, toInvocation as t };
|