@red-hat-developer-hub/e2e-test-utils 2.1.15 → 2.1.16
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/dist/secrets/bitwarden.d.ts +43 -0
- package/dist/secrets/bitwarden.d.ts.map +1 -1
- package/dist/secrets/bitwarden.js +465 -53
- package/dist/secrets/cli.d.ts +48 -1
- package/dist/secrets/cli.d.ts.map +1 -1
- package/dist/secrets/cli.js +455 -28
- package/dist/secrets/command.d.ts +7 -0
- package/dist/secrets/command.d.ts.map +1 -1
- package/dist/secrets/command.js +103 -26
- package/dist/secrets/config.d.ts +11 -1
- package/dist/secrets/config.d.ts.map +1 -1
- package/dist/secrets/config.js +32 -1
- package/dist/secrets/environment-name.d.ts +2 -0
- package/dist/secrets/environment-name.d.ts.map +1 -0
- package/dist/secrets/environment-name.js +4 -0
- package/dist/secrets/environment.d.ts +10 -0
- package/dist/secrets/environment.d.ts.map +1 -1
- package/dist/secrets/environment.js +35 -4
- package/dist/secrets/exec.d.ts +9 -1
- package/dist/secrets/exec.d.ts.map +1 -1
- package/dist/secrets/exec.js +318 -38
- package/dist/secrets/gsm-wrapper.d.ts +42 -0
- package/dist/secrets/gsm-wrapper.d.ts.map +1 -0
- package/dist/secrets/gsm-wrapper.js +238 -0
- package/dist/secrets/gsm.d.ts +33 -0
- package/dist/secrets/gsm.d.ts.map +1 -0
- package/dist/secrets/gsm.js +172 -0
- package/dist/secrets/index.d.ts +9 -4
- package/dist/secrets/index.d.ts.map +1 -1
- package/dist/secrets/index.js +7 -2
- package/dist/secrets/lock.d.ts +3 -0
- package/dist/secrets/lock.d.ts.map +1 -0
- package/dist/secrets/lock.js +28 -0
- package/dist/secrets/mutation.d.ts +71 -0
- package/dist/secrets/mutation.d.ts.map +1 -0
- package/dist/secrets/mutation.js +210 -0
- package/dist/secrets/secret-input.d.ts +17 -0
- package/dist/secrets/secret-input.d.ts.map +1 -0
- package/dist/secrets/secret-input.js +40 -0
- package/dist/secrets/stream.d.ts +9 -0
- package/dist/secrets/stream.d.ts.map +1 -0
- package/dist/secrets/stream.js +211 -0
- package/dist/secrets/temporary-secret.d.ts +13 -0
- package/dist/secrets/temporary-secret.d.ts.map +1 -0
- package/dist/secrets/temporary-secret.js +87 -0
- package/dist/secrets/tests/bitwarden.test.js +640 -0
- package/dist/secrets/tests/cli.integration.test.js +131 -3
- package/dist/secrets/tests/cli.test.js +281 -6
- package/dist/secrets/tests/command.test.d.ts +2 -0
- package/dist/secrets/tests/command.test.d.ts.map +1 -0
- package/dist/secrets/tests/command.test.js +39 -0
- package/dist/secrets/tests/config.test.js +20 -1
- package/dist/secrets/tests/environment.test.js +145 -3
- package/dist/secrets/tests/exec.test.js +469 -2
- package/dist/secrets/tests/gsm-wrapper.test.d.ts +2 -0
- package/dist/secrets/tests/gsm-wrapper.test.d.ts.map +1 -0
- package/dist/secrets/tests/gsm-wrapper.test.js +166 -0
- package/dist/secrets/tests/gsm.test.d.ts +2 -0
- package/dist/secrets/tests/gsm.test.d.ts.map +1 -0
- package/dist/secrets/tests/gsm.test.js +181 -0
- package/dist/secrets/tests/mutation.test.d.ts +2 -0
- package/dist/secrets/tests/mutation.test.d.ts.map +1 -0
- package/dist/secrets/tests/mutation.test.js +390 -0
- package/dist/secrets/tests/secret-input.test.d.ts +2 -0
- package/dist/secrets/tests/secret-input.test.d.ts.map +1 -0
- package/dist/secrets/tests/secret-input.test.js +71 -0
- package/dist/secrets/tests/stream.test.d.ts +2 -0
- package/dist/secrets/tests/stream.test.d.ts.map +1 -0
- package/dist/secrets/tests/stream.test.js +88 -0
- package/dist/secrets/tests/temporary-secret.test.d.ts +2 -0
- package/dist/secrets/tests/temporary-secret.test.d.ts.map +1 -0
- package/dist/secrets/tests/temporary-secret.test.js +76 -0
- package/package.json +1 -1
package/dist/secrets/cli.d.ts
CHANGED
|
@@ -1,11 +1,58 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { type MutationPlan } from "./mutation.js";
|
|
2
3
|
export interface ExecCliArguments {
|
|
3
4
|
command: "exec";
|
|
4
5
|
profilePath: string;
|
|
5
6
|
workspaces: string[];
|
|
6
7
|
executable: string;
|
|
7
8
|
args: string[];
|
|
9
|
+
streamSecrets?: boolean;
|
|
8
10
|
}
|
|
9
|
-
export
|
|
11
|
+
export type HelpTopic = "root" | "exec" | "create" | "update" | "delete" | "describe" | "list" | "gsm-login" | "gsm-clean";
|
|
12
|
+
export interface HelpCliArguments {
|
|
13
|
+
command: "help";
|
|
14
|
+
topic: HelpTopic;
|
|
15
|
+
}
|
|
16
|
+
interface MutationCliBase {
|
|
17
|
+
collection: string;
|
|
18
|
+
bitwardenPath: string;
|
|
19
|
+
dryRun: boolean;
|
|
20
|
+
gsmTimeoutMs?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface CreateCliArguments extends MutationCliBase {
|
|
23
|
+
command: "create";
|
|
24
|
+
fromFile?: string;
|
|
25
|
+
fromStdin: boolean;
|
|
26
|
+
allowEmpty: boolean;
|
|
27
|
+
force: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface UpdateCliArguments extends MutationCliBase {
|
|
30
|
+
command: "update";
|
|
31
|
+
fromFile?: string;
|
|
32
|
+
fromStdin: boolean;
|
|
33
|
+
allowEmpty: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface DeleteCliArguments extends MutationCliBase {
|
|
36
|
+
command: "delete";
|
|
37
|
+
force: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface DescribeCliArguments {
|
|
40
|
+
command: "describe";
|
|
41
|
+
collection: string;
|
|
42
|
+
bitwardenPath: string;
|
|
43
|
+
output: "text" | "json";
|
|
44
|
+
}
|
|
45
|
+
export interface ListCliArguments {
|
|
46
|
+
command: "list";
|
|
47
|
+
collection?: string;
|
|
48
|
+
output: "text" | "json";
|
|
49
|
+
}
|
|
50
|
+
export type CliArguments = HelpCliArguments | ExecCliArguments | CreateCliArguments | UpdateCliArguments | DeleteCliArguments | DescribeCliArguments | ListCliArguments | {
|
|
51
|
+
command: "gsm-login" | "gsm-clean";
|
|
52
|
+
};
|
|
53
|
+
export declare function parseCliArguments(argv: readonly string[]): CliArguments;
|
|
54
|
+
export declare function getHelpText(topic: HelpTopic): string;
|
|
10
55
|
export declare function main(argv?: readonly string[]): Promise<number>;
|
|
56
|
+
export declare function formatMutationResult(state: "dry-run" | "applied", plan: MutationPlan): string;
|
|
57
|
+
export {};
|
|
11
58
|
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/secrets/cli.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/secrets/cli.ts"],"names":[],"mappings":";AAeA,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,eAAe,CAAC;AAIvB,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,MAAM,GACN,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,MAAM,GACN,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,SAAS,CAAC;CAClB;AAED,UAAU,eAAe;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,kBAAmB,SAAQ,eAAe;IACzD,OAAO,EAAE,QAAQ,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,kBAAmB,SAAQ,eAAe;IACzD,OAAO,EAAE,QAAQ,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,kBAAmB,SAAQ,eAAe;IACzD,OAAO,EAAE,QAAQ,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,UAAU,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,YAAY,GACpB,gBAAgB,GAChB,gBAAgB,GAChB,kBAAkB,GAClB,kBAAkB,GAClB,kBAAkB,GAClB,oBAAoB,GACpB,gBAAgB,GAChB;IAAE,OAAO,EAAE,WAAW,GAAG,WAAW,CAAA;CAAE,CAAC;AA6F3C,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,YAAY,CAgBvE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAEpD;AA2SD,wBAAsB,IAAI,CACxB,IAAI,GAAE,SAAS,MAAM,EAA0B,GAC9C,OAAO,CAAC,MAAM,CAAC,CAoFjB;AAoBD,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,GAAG,SAAS,EAC5B,IAAI,EAAE,YAAY,GACjB,MAAM,CASR"}
|
package/dist/secrets/cli.js
CHANGED
|
@@ -4,50 +4,177 @@ import { realpathSync } from "node:fs";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
6
|
import { executeCommand } from "./exec.js";
|
|
7
|
-
import { parseProfile } from "./config.js";
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import { getCollectionMapping, gsmPathFromBitwardenPath, parseProfile, READABLE_COLLECTIONS, } from "./config.js";
|
|
8
|
+
import { GsmClient } from "./gsm.js";
|
|
9
|
+
import { GsmWrapper } from "./gsm-wrapper.js";
|
|
10
|
+
import { executeMutation, } from "./mutation.js";
|
|
11
|
+
import { BitwardenClient } from "./bitwarden.js";
|
|
12
|
+
import { MAX_TIMEOUT_MS } from "./command.js";
|
|
13
|
+
const HELP_TEXT = {
|
|
14
|
+
root: `Usage:
|
|
15
|
+
rhdh-e2e-secrets exec -p <profile.json> [-w <name> ...] [--stream-secrets] -- <command> [args...]
|
|
16
|
+
rhdh-e2e-secrets create -c <collection> <secret-path> (-f <file> | -i) [--allow-empty] [--force] [--dry-run]
|
|
17
|
+
rhdh-e2e-secrets update -c <collection> <secret-path> (-f <file> | -i) [--allow-empty] [--dry-run]
|
|
18
|
+
rhdh-e2e-secrets delete -c <collection> <secret-path> [--force] [--dry-run]
|
|
19
|
+
rhdh-e2e-secrets describe -c <collection> <secret-path> [-o text|json]
|
|
20
|
+
rhdh-e2e-secrets list [-c <collection>] [-o text|json]
|
|
21
|
+
rhdh-e2e-secrets gsm-login
|
|
22
|
+
rhdh-e2e-secrets gsm-clean
|
|
10
23
|
|
|
11
24
|
Requirements:
|
|
12
|
-
BW_SESSION must contain an already unlocked Bitwarden CLI session.
|
|
13
|
-
|
|
14
|
-
|
|
25
|
+
BW_SESSION must contain an already unlocked Bitwarden CLI session for exec and create, update, and delete.
|
|
26
|
+
GSM commands use a cached copy of openshift/release hack/secret-manager.sh and require local gcloud authentication.
|
|
27
|
+
`,
|
|
28
|
+
exec: `Usage:
|
|
29
|
+
rhdh-e2e-secrets exec -p <profile.json> [-w <name> ...] [--stream-secrets] -- <command> [args...]
|
|
30
|
+
|
|
31
|
+
Options:
|
|
32
|
+
-p, --profile <file> Secret profile JSON file (required)
|
|
33
|
+
-w, --workspace <name> Limit execution to a workspace; repeatable
|
|
34
|
+
--stream-secrets Stream selected values to child file descriptor 3
|
|
35
|
+
-h, --help Show this help
|
|
36
|
+
`,
|
|
37
|
+
create: `Usage:
|
|
38
|
+
rhdh-e2e-secrets create -c <collection> <secret-path> (-f <file> | -i) [options]
|
|
39
|
+
|
|
40
|
+
Options:
|
|
41
|
+
-c, --collection <name> Paired secret collection (required)
|
|
42
|
+
-f, --from-file <file> Read an attachment-backed secret from a file
|
|
43
|
+
-i, --from-stdin Read a note-backed secret from stdin
|
|
44
|
+
--allow-empty Allow an empty secret value
|
|
45
|
+
--force Reconcile an existing or partial target
|
|
46
|
+
--dry-run Validate and print the plan without writing
|
|
47
|
+
--gsm-timeout-seconds <n>
|
|
48
|
+
GSM operation timeout
|
|
49
|
+
-h, --help Show this help
|
|
50
|
+
`,
|
|
51
|
+
update: `Usage:
|
|
52
|
+
rhdh-e2e-secrets update -c <collection> <secret-path> (-f <file> | -i) [options]
|
|
53
|
+
|
|
54
|
+
Options:
|
|
55
|
+
-c, --collection <name> Paired secret collection (required)
|
|
56
|
+
-f, --from-file <file> Read an attachment-backed secret from a file
|
|
57
|
+
-i, --from-stdin Read a note-backed secret from stdin
|
|
58
|
+
--allow-empty Allow an empty secret value
|
|
59
|
+
--dry-run Validate and print the plan without writing
|
|
60
|
+
--gsm-timeout-seconds <n>
|
|
61
|
+
GSM operation timeout
|
|
62
|
+
-h, --help Show this help
|
|
63
|
+
`,
|
|
64
|
+
delete: `Usage:
|
|
65
|
+
rhdh-e2e-secrets delete -c <collection> <secret-path> [options]
|
|
66
|
+
|
|
67
|
+
Options:
|
|
68
|
+
-c, --collection <name> Paired secret collection (required)
|
|
69
|
+
--force Reconcile an already partial target
|
|
70
|
+
--dry-run Validate and print the plan without writing
|
|
71
|
+
--gsm-timeout-seconds <n>
|
|
72
|
+
GSM operation timeout
|
|
73
|
+
-h, --help Show this help
|
|
74
|
+
`,
|
|
75
|
+
describe: `Usage:
|
|
76
|
+
rhdh-e2e-secrets describe -c <collection> <secret-path> [-o text|json]
|
|
77
|
+
|
|
78
|
+
Options:
|
|
79
|
+
-c, --collection <name> Paired secret collection (required)
|
|
80
|
+
-o, --output <text|json> Output format (default: text)
|
|
81
|
+
-h, --help Show this help
|
|
82
|
+
`,
|
|
83
|
+
list: `Usage:
|
|
84
|
+
rhdh-e2e-secrets list [-c <collection>] [-o text|json]
|
|
85
|
+
|
|
86
|
+
Options:
|
|
87
|
+
-c, --collection <name> List paths in one paired collection
|
|
88
|
+
-o, --output <text|json> Output format (default: text)
|
|
89
|
+
-h, --help Show this help
|
|
90
|
+
`,
|
|
91
|
+
["gsm-login"]: `Usage:
|
|
92
|
+
rhdh-e2e-secrets gsm-login
|
|
93
|
+
|
|
94
|
+
Authenticate the cached GSM wrapper.
|
|
95
|
+
`,
|
|
96
|
+
["gsm-clean"]: `Usage:
|
|
97
|
+
rhdh-e2e-secrets gsm-clean
|
|
98
|
+
|
|
99
|
+
Remove cached GSM authentication.
|
|
100
|
+
`,
|
|
101
|
+
};
|
|
102
|
+
const MUTATION_COMMANDS = ["create", "update", "delete"];
|
|
15
103
|
export function parseCliArguments(argv) {
|
|
16
|
-
if (argv.length === 0
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
104
|
+
if (argv.length === 0)
|
|
105
|
+
return { command: "help", topic: "root" };
|
|
106
|
+
if (argv[0] === "--help" || argv[0] === "-h")
|
|
107
|
+
return { command: "help", topic: "root" };
|
|
108
|
+
const helpTopic = findHelpTopic(argv);
|
|
109
|
+
if (helpTopic)
|
|
110
|
+
return { command: "help", topic: helpTopic };
|
|
111
|
+
if (argv[0] === "exec")
|
|
112
|
+
return parseExecArguments(argv);
|
|
113
|
+
if (isMutationCommand(argv[0]))
|
|
114
|
+
return parseMutationArguments(argv);
|
|
115
|
+
if (argv[0] === "describe")
|
|
116
|
+
return parseDescribeArguments(argv);
|
|
117
|
+
if (argv[0] === "list")
|
|
118
|
+
return parseListArguments(argv);
|
|
119
|
+
if (argv[0] === "gsm-login" || argv[0] === "gsm-clean") {
|
|
120
|
+
if (argv.length !== 1)
|
|
121
|
+
throw new Error(`${argv[0]} does not accept options`);
|
|
122
|
+
return { command: argv[0] };
|
|
21
123
|
}
|
|
124
|
+
throw new Error(`Unsupported command: ${argv[0]}`);
|
|
125
|
+
}
|
|
126
|
+
export function getHelpText(topic) {
|
|
127
|
+
return HELP_TEXT[topic];
|
|
128
|
+
}
|
|
129
|
+
function findHelpTopic(argv) {
|
|
130
|
+
const command = argv[0];
|
|
131
|
+
if (!isHelpTopic(command))
|
|
132
|
+
return undefined;
|
|
133
|
+
const delimiter = command === "exec" ? argv.indexOf("--") : -1;
|
|
134
|
+
const options = argv.slice(1, delimiter === -1 ? argv.length : delimiter);
|
|
135
|
+
return options.includes("--help") || options.includes("-h")
|
|
136
|
+
? command
|
|
137
|
+
: undefined;
|
|
138
|
+
}
|
|
139
|
+
function parseExecArguments(argv) {
|
|
22
140
|
const delimiter = argv.indexOf("--");
|
|
23
|
-
if (delimiter === -1)
|
|
141
|
+
if (delimiter === -1)
|
|
24
142
|
throw new Error("A command after -- is required");
|
|
25
|
-
}
|
|
26
143
|
const options = argv.slice(1, delimiter);
|
|
27
144
|
const command = argv[delimiter + 1];
|
|
28
145
|
const args = argv.slice(delimiter + 2);
|
|
29
146
|
let profilePath;
|
|
30
147
|
const workspaces = [];
|
|
148
|
+
let streamSecrets = false;
|
|
31
149
|
for (let index = 0; index < options.length; index++) {
|
|
32
150
|
const option = options[index];
|
|
33
|
-
if (option === "--
|
|
151
|
+
if (option === "--stream-secrets") {
|
|
152
|
+
streamSecrets = true;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (option === "--profile" ||
|
|
156
|
+
option === "-p" ||
|
|
157
|
+
option === "--workspace" ||
|
|
158
|
+
option === "-w") {
|
|
34
159
|
const value = options[++index];
|
|
35
160
|
if (!value)
|
|
36
161
|
throw new Error(`${option} requires a value`);
|
|
37
|
-
if (option === "--profile")
|
|
162
|
+
if (option === "--profile" || option === "-p")
|
|
38
163
|
profilePath = value;
|
|
39
164
|
else
|
|
40
165
|
workspaces.push(value);
|
|
41
166
|
continue;
|
|
42
167
|
}
|
|
43
|
-
if (option.startsWith("--profile=")) {
|
|
44
|
-
|
|
168
|
+
if (option.startsWith("--profile=") || option.startsWith("-p=")) {
|
|
169
|
+
const prefix = option.startsWith("--profile=") ? "--profile=" : "-p=";
|
|
170
|
+
profilePath = option.slice(prefix.length);
|
|
45
171
|
if (!profilePath)
|
|
46
172
|
throw new Error("--profile requires a value");
|
|
47
173
|
continue;
|
|
48
174
|
}
|
|
49
|
-
if (option.startsWith("--workspace=")) {
|
|
50
|
-
const
|
|
175
|
+
if (option.startsWith("--workspace=") || option.startsWith("-w=")) {
|
|
176
|
+
const prefix = option.startsWith("--workspace=") ? "--workspace=" : "-w=";
|
|
177
|
+
const workspace = option.slice(prefix.length);
|
|
51
178
|
if (!workspace)
|
|
52
179
|
throw new Error("--workspace requires a value");
|
|
53
180
|
workspaces.push(workspace);
|
|
@@ -65,29 +192,329 @@ export function parseCliArguments(argv) {
|
|
|
65
192
|
workspaces,
|
|
66
193
|
executable: command,
|
|
67
194
|
args,
|
|
195
|
+
...(streamSecrets ? { streamSecrets: true } : {}),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function parseMutationArguments(argv) {
|
|
199
|
+
const command = argv[0];
|
|
200
|
+
let collection;
|
|
201
|
+
let bitwardenPath;
|
|
202
|
+
let fromFile;
|
|
203
|
+
let fromStdin = false;
|
|
204
|
+
let allowEmpty = false;
|
|
205
|
+
let force = false;
|
|
206
|
+
let dryRun = false;
|
|
207
|
+
let gsmTimeoutMs;
|
|
208
|
+
let pathSeen = false;
|
|
209
|
+
const seenOptions = new Set();
|
|
210
|
+
const markOption = (name) => {
|
|
211
|
+
if (seenOptions.has(name))
|
|
212
|
+
throw new Error(`Duplicate option: ${name}`);
|
|
213
|
+
seenOptions.add(name);
|
|
214
|
+
};
|
|
215
|
+
for (let index = 1; index < argv.length; index++) {
|
|
216
|
+
const option = argv[index];
|
|
217
|
+
if (!option.startsWith("-")) {
|
|
218
|
+
if (pathSeen)
|
|
219
|
+
throw new Error("Only one secret path is allowed");
|
|
220
|
+
pathSeen = true;
|
|
221
|
+
bitwardenPath = option;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (option === "--from-stdin" || option === "-i") {
|
|
225
|
+
markOption("--from-stdin");
|
|
226
|
+
fromStdin = true;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (option === "--allow-empty" ||
|
|
230
|
+
option === "--force" ||
|
|
231
|
+
option === "--dry-run") {
|
|
232
|
+
markOption(option);
|
|
233
|
+
if (option === "--allow-empty")
|
|
234
|
+
allowEmpty = true;
|
|
235
|
+
else if (option === "--force")
|
|
236
|
+
force = true;
|
|
237
|
+
else
|
|
238
|
+
dryRun = true;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
const valueOption = readMutationValueOption(option, argv, index);
|
|
242
|
+
if (valueOption) {
|
|
243
|
+
markOption(valueOption.name);
|
|
244
|
+
index = valueOption.nextIndex;
|
|
245
|
+
if (valueOption.name === "--collection")
|
|
246
|
+
collection = valueOption.value;
|
|
247
|
+
else if (valueOption.name === "--from-file")
|
|
248
|
+
fromFile = valueOption.value;
|
|
249
|
+
else if (valueOption.name === "--gsm-timeout-seconds") {
|
|
250
|
+
const seconds = Number(valueOption.value);
|
|
251
|
+
if (!Number.isSafeInteger(seconds) ||
|
|
252
|
+
seconds <= 0 ||
|
|
253
|
+
seconds * 1000 > MAX_TIMEOUT_MS) {
|
|
254
|
+
throw new Error(`--gsm-timeout-seconds must be a positive integer no greater than ${Math.floor(MAX_TIMEOUT_MS / 1000)}`);
|
|
255
|
+
}
|
|
256
|
+
gsmTimeoutMs = seconds * 1000;
|
|
257
|
+
}
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
throw new Error(`Unknown option: ${option}`);
|
|
261
|
+
}
|
|
262
|
+
if (!collection)
|
|
263
|
+
throw new Error("--collection is required");
|
|
264
|
+
if (!bitwardenPath)
|
|
265
|
+
throw new Error("A secret path is required");
|
|
266
|
+
if (command === "delete") {
|
|
267
|
+
if (fromFile !== undefined || fromStdin || allowEmpty) {
|
|
268
|
+
throw new Error("delete does not accept input options");
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
command,
|
|
272
|
+
collection,
|
|
273
|
+
bitwardenPath,
|
|
274
|
+
force,
|
|
275
|
+
dryRun,
|
|
276
|
+
gsmTimeoutMs,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
if (force && command === "update") {
|
|
280
|
+
throw new Error("--force is not supported for update");
|
|
281
|
+
}
|
|
282
|
+
if (Number(fromFile !== undefined) + Number(fromStdin) !== 1) {
|
|
283
|
+
throw new Error("Exactly one of --from-file or --from-stdin is required");
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
command,
|
|
287
|
+
collection,
|
|
288
|
+
bitwardenPath,
|
|
289
|
+
fromFile,
|
|
290
|
+
fromStdin,
|
|
291
|
+
allowEmpty,
|
|
292
|
+
...(command === "create" ? { force } : {}),
|
|
293
|
+
dryRun,
|
|
294
|
+
gsmTimeoutMs,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function readMutationValueOption(option, argv, index) {
|
|
298
|
+
const aliases = new Map([
|
|
299
|
+
["-c", "--collection"],
|
|
300
|
+
["-f", "--from-file"],
|
|
301
|
+
]);
|
|
302
|
+
const names = [
|
|
303
|
+
"--collection",
|
|
304
|
+
"--from-file",
|
|
305
|
+
"--gsm-timeout-seconds",
|
|
306
|
+
];
|
|
307
|
+
const name = aliases.get(option) ??
|
|
308
|
+
names.find((candidate) => option === candidate || option.startsWith(`${candidate}=`));
|
|
309
|
+
if (!name)
|
|
310
|
+
return undefined;
|
|
311
|
+
const inline = option.startsWith(`${name}=`)
|
|
312
|
+
? option.slice(name.length + 1)
|
|
313
|
+
: argv[index + 1];
|
|
314
|
+
if (!inline)
|
|
315
|
+
throw new Error(`${name} requires a value`);
|
|
316
|
+
return {
|
|
317
|
+
name,
|
|
318
|
+
value: inline,
|
|
319
|
+
nextIndex: option === name || option === "-c" || option === "-f" ? index + 1 : index,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function parseDescribeArguments(argv) {
|
|
323
|
+
const parsed = parseReadArguments(argv, true);
|
|
324
|
+
return {
|
|
325
|
+
command: "describe",
|
|
326
|
+
collection: parsed.collection,
|
|
327
|
+
bitwardenPath: parsed.bitwardenPath,
|
|
328
|
+
output: parsed.output,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function parseListArguments(argv) {
|
|
332
|
+
const parsed = parseReadArguments(argv, false);
|
|
333
|
+
return {
|
|
334
|
+
command: "list",
|
|
335
|
+
...(parsed.collection === undefined
|
|
336
|
+
? {}
|
|
337
|
+
: { collection: parsed.collection }),
|
|
338
|
+
output: parsed.output,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
function parseReadArguments(argv, pathRequired) {
|
|
342
|
+
let collection;
|
|
343
|
+
let bitwardenPath;
|
|
344
|
+
let output = "text";
|
|
345
|
+
const seen = new Set();
|
|
346
|
+
for (let index = 1; index < argv.length; index++) {
|
|
347
|
+
const option = argv[index];
|
|
348
|
+
if (!option.startsWith("-")) {
|
|
349
|
+
if (bitwardenPath)
|
|
350
|
+
throw new Error("Only one secret path is allowed");
|
|
351
|
+
bitwardenPath = option;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
const value = readReadValueOption(option, argv, index);
|
|
355
|
+
if (value) {
|
|
356
|
+
if (seen.has(value.name))
|
|
357
|
+
throw new Error(`Duplicate option: ${value.name}`);
|
|
358
|
+
seen.add(value.name);
|
|
359
|
+
index = value.nextIndex;
|
|
360
|
+
if (value.name === "--collection")
|
|
361
|
+
collection = value.value;
|
|
362
|
+
else {
|
|
363
|
+
const normalized = value.value.toLowerCase();
|
|
364
|
+
if (normalized !== "text" && normalized !== "json") {
|
|
365
|
+
throw new Error("--output must be text or json");
|
|
366
|
+
}
|
|
367
|
+
output = normalized;
|
|
368
|
+
}
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
throw new Error(`Unknown option: ${option}`);
|
|
372
|
+
}
|
|
373
|
+
if (pathRequired && !bitwardenPath)
|
|
374
|
+
throw new Error("A secret path is required");
|
|
375
|
+
if (pathRequired && !collection)
|
|
376
|
+
throw new Error("--collection is required");
|
|
377
|
+
if (!pathRequired && bitwardenPath) {
|
|
378
|
+
throw new Error("list does not accept a secret path");
|
|
379
|
+
}
|
|
380
|
+
return { collection, bitwardenPath, output };
|
|
381
|
+
}
|
|
382
|
+
function readReadValueOption(option, argv, index) {
|
|
383
|
+
const name = option === "-c"
|
|
384
|
+
? "--collection"
|
|
385
|
+
: option === "-o"
|
|
386
|
+
? "--output"
|
|
387
|
+
: ["--collection", "--output"].find((candidate) => option === candidate || option.startsWith(`${candidate}=`));
|
|
388
|
+
if (!name)
|
|
389
|
+
return undefined;
|
|
390
|
+
const inline = option.startsWith(`${name}=`)
|
|
391
|
+
? option.slice(name.length + 1)
|
|
392
|
+
: argv[index + 1];
|
|
393
|
+
if (!inline)
|
|
394
|
+
throw new Error(`${name} requires a value`);
|
|
395
|
+
return {
|
|
396
|
+
name,
|
|
397
|
+
value: inline,
|
|
398
|
+
nextIndex: option === name || option === "-c" || option === "-o" ? index + 1 : index,
|
|
68
399
|
};
|
|
69
400
|
}
|
|
70
401
|
export async function main(argv = process.argv.slice(2)) {
|
|
71
402
|
if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
|
|
72
|
-
console.log(
|
|
403
|
+
console.log(getHelpText("root"));
|
|
73
404
|
return 0;
|
|
74
405
|
}
|
|
75
406
|
try {
|
|
76
407
|
const parsed = parseCliArguments(argv);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
408
|
+
if (parsed.command === "help") {
|
|
409
|
+
console.log(getHelpText(parsed.topic));
|
|
410
|
+
return 0;
|
|
411
|
+
}
|
|
412
|
+
if (parsed.command === "exec") {
|
|
413
|
+
const profileValue = JSON.parse(await readFile(resolve(parsed.profilePath), "utf8"));
|
|
414
|
+
return await executeCommand({
|
|
415
|
+
profile: parseProfile(profileValue),
|
|
416
|
+
workspaces: parsed.workspaces,
|
|
417
|
+
command: parsed.executable,
|
|
418
|
+
args: parsed.args,
|
|
419
|
+
streamSecrets: parsed.streamSecrets,
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
if (parsed.command === "gsm-login" || parsed.command === "gsm-clean") {
|
|
423
|
+
const wrapper = new GsmWrapper();
|
|
424
|
+
const result = parsed.command === "gsm-login"
|
|
425
|
+
? await wrapper.login()
|
|
426
|
+
: await wrapper.clean();
|
|
427
|
+
if (result.status !== 0)
|
|
428
|
+
throw new Error(`${parsed.command} failed`);
|
|
429
|
+
return 0;
|
|
430
|
+
}
|
|
431
|
+
const gsm = new GsmClient();
|
|
432
|
+
if (parsed.command === "describe") {
|
|
433
|
+
const mapping = getCollectionMapping(parsed.collection);
|
|
434
|
+
const metadata = await gsm.describe(mapping.gsmCollection, gsmPathFromBitwardenPath(parsed.bitwardenPath));
|
|
435
|
+
printOutput(metadata, parsed.output);
|
|
436
|
+
return 0;
|
|
437
|
+
}
|
|
438
|
+
if (parsed.command === "list") {
|
|
439
|
+
if (parsed.collection === undefined) {
|
|
440
|
+
printOutput([...READABLE_COLLECTIONS], parsed.output);
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
const mapping = getCollectionMapping(parsed.collection);
|
|
444
|
+
const paths = await gsm.list(mapping.gsmCollection);
|
|
445
|
+
printOutput(paths, parsed.output);
|
|
446
|
+
}
|
|
447
|
+
return 0;
|
|
448
|
+
}
|
|
449
|
+
if (!isMutationCommand(parsed.command)) {
|
|
450
|
+
throw new Error(`Unsupported command: ${parsed.command}`);
|
|
451
|
+
}
|
|
452
|
+
const mutation = parsed;
|
|
453
|
+
const result = await executeMutation({
|
|
454
|
+
command: mutation.command,
|
|
455
|
+
collection: mutation.collection,
|
|
456
|
+
bitwardenPath: mutation.bitwardenPath,
|
|
457
|
+
fromFile: "fromFile" in mutation ? mutation.fromFile : undefined,
|
|
458
|
+
fromStdin: "fromStdin" in mutation ? mutation.fromStdin : undefined,
|
|
459
|
+
stdin: process.stdin,
|
|
460
|
+
allowEmpty: "allowEmpty" in mutation ? mutation.allowEmpty : undefined,
|
|
461
|
+
force: "force" in mutation ? mutation.force : undefined,
|
|
462
|
+
dryRun: mutation.dryRun,
|
|
463
|
+
gsmTimeoutMs: mutation.gsmTimeoutMs,
|
|
464
|
+
bitwarden: new BitwardenClient(),
|
|
465
|
+
gsm,
|
|
84
466
|
});
|
|
467
|
+
console.log(formatMutationResult(result.state, result.plan));
|
|
468
|
+
return 0;
|
|
85
469
|
}
|
|
86
470
|
catch (error) {
|
|
87
|
-
console.error(error instanceof Error ? error.message : "Secret
|
|
471
|
+
console.error(error instanceof Error ? error.message : "Secret operation failed");
|
|
88
472
|
return 1;
|
|
89
473
|
}
|
|
90
474
|
}
|
|
475
|
+
function printOutput(value, output) {
|
|
476
|
+
if (output === "json") {
|
|
477
|
+
console.log(JSON.stringify(value, null, 2));
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
if (Array.isArray(value)) {
|
|
481
|
+
for (const entry of value)
|
|
482
|
+
console.log(String(entry));
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
if (value && typeof value === "object") {
|
|
486
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
487
|
+
console.log(`${key}: ${String(entry)}`);
|
|
488
|
+
}
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
console.log(String(value));
|
|
492
|
+
}
|
|
493
|
+
export function formatMutationResult(state, plan) {
|
|
494
|
+
const lines = [
|
|
495
|
+
`${state === "dry-run" ? "Dry-run" : "Applied"}: ${plan.command} ${plan.collection}/${plan.bitwardenPath}`,
|
|
496
|
+
`Bitwarden: ${plan.bitwardenAction} ${plan.bitwardenCollection}/${plan.bitwardenPath}${plan.storage ? ` (${plan.storage})` : ""}`,
|
|
497
|
+
`GSM: ${plan.gsmAction} ${plan.gsmCollection}/${plan.gsmPath}`,
|
|
498
|
+
];
|
|
499
|
+
if (plan.byteLength !== undefined)
|
|
500
|
+
lines.push(`Input: ${plan.byteLength} bytes`);
|
|
501
|
+
return lines.join("\n");
|
|
502
|
+
}
|
|
503
|
+
function isMutationCommand(value) {
|
|
504
|
+
return MUTATION_COMMANDS.includes(value);
|
|
505
|
+
}
|
|
506
|
+
function isHelpTopic(value) {
|
|
507
|
+
return [
|
|
508
|
+
"exec",
|
|
509
|
+
"create",
|
|
510
|
+
"update",
|
|
511
|
+
"delete",
|
|
512
|
+
"describe",
|
|
513
|
+
"list",
|
|
514
|
+
"gsm-login",
|
|
515
|
+
"gsm-clean",
|
|
516
|
+
].includes(value);
|
|
517
|
+
}
|
|
91
518
|
if (process.argv[1] &&
|
|
92
519
|
realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) {
|
|
93
520
|
void main().then((exitCode) => {
|
|
@@ -3,13 +3,20 @@ export interface CommandResult {
|
|
|
3
3
|
stdout: string;
|
|
4
4
|
stderr: string;
|
|
5
5
|
error?: Error;
|
|
6
|
+
signal?: NodeJS.Signals;
|
|
7
|
+
timedOut?: boolean;
|
|
6
8
|
}
|
|
7
9
|
export interface CommandOptions {
|
|
8
10
|
cwd?: string;
|
|
9
11
|
env?: NodeJS.ProcessEnv;
|
|
10
12
|
input?: string;
|
|
11
13
|
stdio?: "pipe" | "inherit";
|
|
14
|
+
tty?: boolean;
|
|
15
|
+
timeoutMs?: number;
|
|
12
16
|
}
|
|
17
|
+
export declare const MAX_TIMEOUT_MS = 2147483647;
|
|
13
18
|
export type CommandRunner = (command: string, args: readonly string[], options?: CommandOptions) => Promise<CommandResult>;
|
|
19
|
+
export declare function shouldDetachCommand(options?: Pick<CommandOptions, "tty">): boolean;
|
|
14
20
|
export declare const runCommand: CommandRunner;
|
|
21
|
+
export declare function assertInteractiveTerminal(): Promise<void>;
|
|
15
22
|
//# sourceMappingURL=command.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/secrets/command.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/secrets/command.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,cAAc,aAAgB,CAAC;AAE5C,MAAM,MAAM,aAAa,GAAG,CAC1B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,CAAC,EAAE,cAAc,KACrB,OAAO,CAAC,aAAa,CAAC,CAAC;AAE5B,wBAAgB,mBAAmB,CACjC,OAAO,GAAE,IAAI,CAAC,cAAc,EAAE,KAAK,CAAM,GACxC,OAAO,CAET;AAED,eAAO,MAAM,UAAU,EAAE,aAuGxB,CAAC;AAEF,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC,CAE/D"}
|