@barekey/cli 0.1.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/LICENSE +28 -0
- package/README.md +36 -0
- package/dist/auth-provider.d.ts +6 -0
- package/dist/auth-provider.js +58 -0
- package/dist/command-utils.d.ts +23 -0
- package/dist/command-utils.js +78 -0
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +186 -0
- package/dist/commands/env.d.ts +2 -0
- package/dist/commands/env.js +295 -0
- package/dist/commands/typegen.d.ts +2 -0
- package/dist/commands/typegen.js +25 -0
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +4 -0
- package/dist/credentials-store.d.ts +7 -0
- package/dist/credentials-store.js +199 -0
- package/dist/http.d.ts +22 -0
- package/dist/http.js +66 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +17 -0
- package/dist/runtime-config.d.ts +10 -0
- package/dist/runtime-config.js +49 -0
- package/dist/typegen.d.ts +20 -0
- package/dist/typegen.js +14 -0
- package/dist/types.d.ts +13 -0
- package/dist/types.js +1 -0
- package/package.json +37 -0
- package/src/auth-provider.ts +86 -0
- package/src/command-utils.ts +118 -0
- package/src/commands/auth.ts +247 -0
- package/src/commands/env.ts +496 -0
- package/src/commands/typegen.ts +38 -0
- package/src/constants.ts +4 -0
- package/src/credentials-store.ts +243 -0
- package/src/http.ts +86 -0
- package/src/index.ts +21 -0
- package/src/runtime-config.ts +66 -0
- package/src/types.ts +14 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import { cancel, confirm, isCancel } from "@clack/prompts";
|
|
3
|
+
import { BarekeyClient } from "@barekey/sdk";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
import { createCliAuthProvider } from "../auth-provider.js";
|
|
6
|
+
import { addTargetOptions, dotenvEscape, parseChance, requireLocalSession, resolveTarget, toJsonOutput, } from "../command-utils.js";
|
|
7
|
+
import { postJson } from "../http.js";
|
|
8
|
+
function createEnvClient(input) {
|
|
9
|
+
const organization = input.organization?.trim();
|
|
10
|
+
if (!organization) {
|
|
11
|
+
throw new Error("Organization slug is required.");
|
|
12
|
+
}
|
|
13
|
+
return new BarekeyClient({
|
|
14
|
+
organization,
|
|
15
|
+
project: input.project,
|
|
16
|
+
environment: input.environment,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
async function runEnvGet(name, options) {
|
|
20
|
+
const local = await requireLocalSession();
|
|
21
|
+
const target = await resolveTarget(options, local);
|
|
22
|
+
const client = createEnvClient({
|
|
23
|
+
organization: target.orgSlug ?? local.credentials.orgSlug,
|
|
24
|
+
project: target.projectSlug,
|
|
25
|
+
environment: target.stageSlug,
|
|
26
|
+
});
|
|
27
|
+
const value = await client.get(name, {
|
|
28
|
+
seed: options.seed,
|
|
29
|
+
key: options.key,
|
|
30
|
+
});
|
|
31
|
+
if (options.json) {
|
|
32
|
+
toJsonOutput(true, {
|
|
33
|
+
name,
|
|
34
|
+
value,
|
|
35
|
+
});
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
console.log(String(value));
|
|
39
|
+
}
|
|
40
|
+
async function runEnvGetMany(options) {
|
|
41
|
+
const local = await requireLocalSession();
|
|
42
|
+
const target = await resolveTarget(options, local);
|
|
43
|
+
const client = createEnvClient({
|
|
44
|
+
organization: target.orgSlug ?? local.credentials.orgSlug,
|
|
45
|
+
project: target.projectSlug,
|
|
46
|
+
environment: target.stageSlug,
|
|
47
|
+
});
|
|
48
|
+
const names = options.names
|
|
49
|
+
.split(",")
|
|
50
|
+
.map((value) => value.trim())
|
|
51
|
+
.filter((value) => value.length > 0);
|
|
52
|
+
const resolved = await Promise.all(names.map(async (resolvedName) => ({
|
|
53
|
+
name: resolvedName,
|
|
54
|
+
value: await client.get(resolvedName, {
|
|
55
|
+
seed: options.seed,
|
|
56
|
+
key: options.key,
|
|
57
|
+
}),
|
|
58
|
+
})));
|
|
59
|
+
if (options.json) {
|
|
60
|
+
toJsonOutput(true, resolved);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
for (const value of resolved.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
64
|
+
if (value) {
|
|
65
|
+
console.log(`${value.name}=${String(value.value)}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async function runEnvList(options) {
|
|
70
|
+
const local = await requireLocalSession();
|
|
71
|
+
const target = await resolveTarget(options, local);
|
|
72
|
+
const authProvider = createCliAuthProvider();
|
|
73
|
+
const accessToken = await authProvider.getAccessToken();
|
|
74
|
+
const response = await postJson({
|
|
75
|
+
baseUrl: local.baseUrl,
|
|
76
|
+
path: "/v1/env/list",
|
|
77
|
+
accessToken,
|
|
78
|
+
payload: {
|
|
79
|
+
orgSlug: target.orgSlug,
|
|
80
|
+
projectSlug: target.projectSlug,
|
|
81
|
+
stageSlug: target.stageSlug,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
if (options.json) {
|
|
85
|
+
toJsonOutput(true, response.variables);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (response.variables.length === 0) {
|
|
89
|
+
console.log("No variables found.");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
for (const row of response.variables) {
|
|
93
|
+
const chanceSuffix = row.kind === "ab_roll" ? ` chance=${row.chance ?? 0}` : "";
|
|
94
|
+
const rolloutSuffix = row.kind === "rollout"
|
|
95
|
+
? ` ${pc.dim(`${row.rolloutFunction ?? "linear"}(${row.rolloutMilestones?.length ?? 0} milestones)`)}`
|
|
96
|
+
: "";
|
|
97
|
+
console.log(`${row.name} ${pc.dim(row.kind)} ${pc.dim(row.declaredType)}${chanceSuffix}${rolloutSuffix}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function runEnvWrite(operation, name, value, options) {
|
|
101
|
+
const local = await requireLocalSession();
|
|
102
|
+
const target = await resolveTarget(options, local);
|
|
103
|
+
const authProvider = createCliAuthProvider();
|
|
104
|
+
const accessToken = await authProvider.getAccessToken();
|
|
105
|
+
const entry = options.ab !== undefined
|
|
106
|
+
? {
|
|
107
|
+
name,
|
|
108
|
+
kind: "ab_roll",
|
|
109
|
+
declaredType: options.type ?? "string",
|
|
110
|
+
valueA: value,
|
|
111
|
+
valueB: options.ab,
|
|
112
|
+
chance: parseChance(options.chance),
|
|
113
|
+
}
|
|
114
|
+
: {
|
|
115
|
+
name,
|
|
116
|
+
kind: "secret",
|
|
117
|
+
declaredType: options.type ?? "string",
|
|
118
|
+
value,
|
|
119
|
+
};
|
|
120
|
+
const result = await postJson({
|
|
121
|
+
baseUrl: local.baseUrl,
|
|
122
|
+
path: "/v1/env/write",
|
|
123
|
+
accessToken,
|
|
124
|
+
payload: {
|
|
125
|
+
orgSlug: target.orgSlug,
|
|
126
|
+
projectSlug: target.projectSlug,
|
|
127
|
+
stageSlug: target.stageSlug,
|
|
128
|
+
mode: operation,
|
|
129
|
+
entries: [entry],
|
|
130
|
+
deletes: [],
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
if (options.json) {
|
|
134
|
+
toJsonOutput(true, result);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
console.log(`Created: ${result.createdCount}, Updated: ${result.updatedCount}, Deleted: ${result.deletedCount}`);
|
|
138
|
+
}
|
|
139
|
+
async function runEnvDelete(name, options) {
|
|
140
|
+
const local = await requireLocalSession();
|
|
141
|
+
const target = await resolveTarget(options, local);
|
|
142
|
+
const authProvider = createCliAuthProvider();
|
|
143
|
+
const accessToken = await authProvider.getAccessToken();
|
|
144
|
+
if (!options.ignoreMissing) {
|
|
145
|
+
const listed = await postJson({
|
|
146
|
+
baseUrl: local.baseUrl,
|
|
147
|
+
path: "/v1/env/list",
|
|
148
|
+
accessToken,
|
|
149
|
+
payload: {
|
|
150
|
+
orgSlug: target.orgSlug,
|
|
151
|
+
projectSlug: target.projectSlug,
|
|
152
|
+
stageSlug: target.stageSlug,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
const exists = listed.variables.some((row) => row.name === name);
|
|
156
|
+
if (!exists) {
|
|
157
|
+
throw new Error(`Variable ${name} was not found in this stage.`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (!options.yes) {
|
|
161
|
+
if (!process.stdout.isTTY) {
|
|
162
|
+
throw new Error("Deletion requires --yes in non-interactive mode.");
|
|
163
|
+
}
|
|
164
|
+
const confirmed = await confirm({
|
|
165
|
+
message: `Delete variable ${name}?`,
|
|
166
|
+
initialValue: false,
|
|
167
|
+
});
|
|
168
|
+
if (isCancel(confirmed)) {
|
|
169
|
+
cancel("Delete canceled.");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (!confirmed) {
|
|
173
|
+
throw new Error("Delete canceled.");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const result = await postJson({
|
|
177
|
+
baseUrl: local.baseUrl,
|
|
178
|
+
path: "/v1/env/write",
|
|
179
|
+
accessToken,
|
|
180
|
+
payload: {
|
|
181
|
+
orgSlug: target.orgSlug,
|
|
182
|
+
projectSlug: target.projectSlug,
|
|
183
|
+
stageSlug: target.stageSlug,
|
|
184
|
+
mode: "upsert",
|
|
185
|
+
entries: [],
|
|
186
|
+
deletes: [name],
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
if (options.json) {
|
|
190
|
+
toJsonOutput(true, result);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
console.log(`Deleted: ${result.deletedCount}`);
|
|
194
|
+
}
|
|
195
|
+
async function runEnvPull(options) {
|
|
196
|
+
const local = await requireLocalSession();
|
|
197
|
+
const target = await resolveTarget(options, local);
|
|
198
|
+
const authProvider = createCliAuthProvider();
|
|
199
|
+
const accessToken = await authProvider.getAccessToken();
|
|
200
|
+
const response = await postJson({
|
|
201
|
+
baseUrl: local.baseUrl,
|
|
202
|
+
path: "/v1/env/pull",
|
|
203
|
+
accessToken,
|
|
204
|
+
payload: {
|
|
205
|
+
orgSlug: target.orgSlug,
|
|
206
|
+
projectSlug: target.projectSlug,
|
|
207
|
+
stageSlug: target.stageSlug,
|
|
208
|
+
seed: options.seed,
|
|
209
|
+
key: options.key,
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
const format = options.format ?? "dotenv";
|
|
213
|
+
const sortedKeys = Object.keys(response.byName).sort((left, right) => left.localeCompare(right));
|
|
214
|
+
const output = format === "json"
|
|
215
|
+
? `${JSON.stringify(response.byName, null, 2)}\n`
|
|
216
|
+
: `${sortedKeys.map((keyName) => `${keyName}=${dotenvEscape(response.byName[keyName] ?? "")}`).join("\n")}\n`;
|
|
217
|
+
if (options.out) {
|
|
218
|
+
await writeFile(options.out, output, "utf8");
|
|
219
|
+
console.log(`Wrote ${options.out}`);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (options.redact && !process.stdout.isTTY) {
|
|
223
|
+
console.log(`Pulled ${response.values.length} variables.`);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
process.stdout.write(output);
|
|
227
|
+
}
|
|
228
|
+
export function registerEnvCommands(program) {
|
|
229
|
+
const env = program.command("env").description("Environment variable operations");
|
|
230
|
+
addTargetOptions(env
|
|
231
|
+
.command("get")
|
|
232
|
+
.description("Evaluate one variable")
|
|
233
|
+
.argument("<name>", "Variable name")
|
|
234
|
+
.option("--seed <value>", "Deterministic seed")
|
|
235
|
+
.option("--key <value>", "Deterministic key")
|
|
236
|
+
.option("--json", "Machine-readable output", false)).action(async (name, options) => {
|
|
237
|
+
await runEnvGet(name, options);
|
|
238
|
+
});
|
|
239
|
+
addTargetOptions(env
|
|
240
|
+
.command("get-many")
|
|
241
|
+
.description("Evaluate a batch of variables")
|
|
242
|
+
.requiredOption("--names <csv>", "Comma-separated variable names")
|
|
243
|
+
.option("--seed <value>", "Deterministic seed")
|
|
244
|
+
.option("--key <value>", "Deterministic key")
|
|
245
|
+
.option("--json", "Machine-readable output", false)).action(async (options) => {
|
|
246
|
+
await runEnvGetMany(options);
|
|
247
|
+
});
|
|
248
|
+
addTargetOptions(env
|
|
249
|
+
.command("list")
|
|
250
|
+
.description("List variables for a project stage")
|
|
251
|
+
.option("--json", "Machine-readable output", false)).action(async (options) => {
|
|
252
|
+
await runEnvList(options);
|
|
253
|
+
});
|
|
254
|
+
addTargetOptions(env
|
|
255
|
+
.command("new")
|
|
256
|
+
.description("Create one variable")
|
|
257
|
+
.argument("<name>", "Variable name")
|
|
258
|
+
.argument("<value>", "Variable value")
|
|
259
|
+
.option("--ab <value-b>", "Second value for ab_roll")
|
|
260
|
+
.option("--chance <number>", "A-branch probability between 0 and 1")
|
|
261
|
+
.option("--type <type>", "Declared value type", "string")
|
|
262
|
+
.option("--json", "Machine-readable output", false)).action(async (name, value, options) => {
|
|
263
|
+
await runEnvWrite("create_only", name, value, options);
|
|
264
|
+
});
|
|
265
|
+
addTargetOptions(env
|
|
266
|
+
.command("set")
|
|
267
|
+
.description("Upsert one variable")
|
|
268
|
+
.argument("<name>", "Variable name")
|
|
269
|
+
.argument("<value>", "Variable value")
|
|
270
|
+
.option("--ab <value-b>", "Second value for ab_roll")
|
|
271
|
+
.option("--chance <number>", "A-branch probability between 0 and 1")
|
|
272
|
+
.option("--type <type>", "Declared value type", "string")
|
|
273
|
+
.option("--json", "Machine-readable output", false)).action(async (name, value, options) => {
|
|
274
|
+
await runEnvWrite("upsert", name, value, options);
|
|
275
|
+
});
|
|
276
|
+
addTargetOptions(env
|
|
277
|
+
.command("delete")
|
|
278
|
+
.description("Delete one variable")
|
|
279
|
+
.argument("<name>", "Variable name")
|
|
280
|
+
.option("--yes", "Skip confirmation", false)
|
|
281
|
+
.option("--ignore-missing", "Do not fail when variable is missing", false)
|
|
282
|
+
.option("--json", "Machine-readable output", false)).action(async (name, options) => {
|
|
283
|
+
await runEnvDelete(name, options);
|
|
284
|
+
});
|
|
285
|
+
addTargetOptions(env
|
|
286
|
+
.command("pull")
|
|
287
|
+
.description("Pull resolved variables for a project stage")
|
|
288
|
+
.option("--format <type>", "Output format: dotenv|json", "dotenv")
|
|
289
|
+
.option("--out <path>", "Output file path")
|
|
290
|
+
.option("--seed <value>", "Deterministic seed")
|
|
291
|
+
.option("--key <value>", "Deterministic key")
|
|
292
|
+
.option("--redact", "Print only summary in non-file mode", false)).action(async (options) => {
|
|
293
|
+
await runEnvPull(options);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { BarekeyClient } from "@barekey/sdk";
|
|
2
|
+
import { addTargetOptions, requireLocalSession, resolveTarget, } from "../command-utils.js";
|
|
3
|
+
async function runTypegen(options) {
|
|
4
|
+
const local = await requireLocalSession();
|
|
5
|
+
const target = await resolveTarget(options, local);
|
|
6
|
+
const organization = target.orgSlug ?? local.credentials.orgSlug;
|
|
7
|
+
if (!organization || organization.trim().length === 0) {
|
|
8
|
+
throw new Error("Organization slug is required.");
|
|
9
|
+
}
|
|
10
|
+
const client = new BarekeyClient({
|
|
11
|
+
organization,
|
|
12
|
+
project: target.projectSlug,
|
|
13
|
+
environment: target.stageSlug,
|
|
14
|
+
typegen: false,
|
|
15
|
+
});
|
|
16
|
+
const result = await client.typegen();
|
|
17
|
+
console.log(`${result.written ? "Updated" : "Unchanged"} ${result.path}`);
|
|
18
|
+
}
|
|
19
|
+
export function registerTypegenCommand(program) {
|
|
20
|
+
addTargetOptions(program
|
|
21
|
+
.command("typegen")
|
|
22
|
+
.description("Generate Barekey SDK types in the installed @barekey/sdk module")).action(async (options) => {
|
|
23
|
+
await runTypegen(options);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CliConfig, CliCredentials } from "./types.js";
|
|
2
|
+
export declare function saveConfig(config: CliConfig): Promise<void>;
|
|
3
|
+
export declare function loadConfig(): Promise<CliConfig | null>;
|
|
4
|
+
export declare function clearConfig(): Promise<void>;
|
|
5
|
+
export declare function saveCredentials(accountId: string, credentials: CliCredentials): Promise<void>;
|
|
6
|
+
export declare function loadCredentials(accountId: string): Promise<CliCredentials | null>;
|
|
7
|
+
export declare function deleteCredentials(accountId: string): Promise<void>;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
const SERVICE_NAME = "barekey-cli";
|
|
6
|
+
const CONFIG_DIR = path.join(homedir(), ".config", "barekey");
|
|
7
|
+
const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
8
|
+
const CREDENTIALS_DIR = path.join(CONFIG_DIR, "credentials");
|
|
9
|
+
function runCommand(command, args, input) {
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
const child = spawn(command, args, {
|
|
12
|
+
stdio: "pipe",
|
|
13
|
+
});
|
|
14
|
+
let stdout = "";
|
|
15
|
+
let stderr = "";
|
|
16
|
+
child.stdout.on("data", (chunk) => {
|
|
17
|
+
stdout += chunk.toString("utf8");
|
|
18
|
+
});
|
|
19
|
+
child.stderr.on("data", (chunk) => {
|
|
20
|
+
stderr += chunk.toString("utf8");
|
|
21
|
+
});
|
|
22
|
+
child.on("error", () => {
|
|
23
|
+
resolve({ stdout: "", stderr: "", code: 127 });
|
|
24
|
+
});
|
|
25
|
+
child.on("close", (code) => {
|
|
26
|
+
resolve({ stdout, stderr, code: code ?? 1 });
|
|
27
|
+
});
|
|
28
|
+
if (input !== undefined) {
|
|
29
|
+
child.stdin.write(input);
|
|
30
|
+
}
|
|
31
|
+
child.stdin.end();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async function ensureConfigDir() {
|
|
35
|
+
await mkdir(CONFIG_DIR, {
|
|
36
|
+
recursive: true,
|
|
37
|
+
mode: 0o700,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
async function ensureCredentialsDir() {
|
|
41
|
+
await ensureConfigDir();
|
|
42
|
+
await mkdir(CREDENTIALS_DIR, {
|
|
43
|
+
recursive: true,
|
|
44
|
+
mode: 0o700,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async function readJsonFile(filePath) {
|
|
48
|
+
try {
|
|
49
|
+
const value = await readFile(filePath, "utf8");
|
|
50
|
+
return JSON.parse(value);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function writeJsonFile(filePath, value) {
|
|
57
|
+
await ensureConfigDir();
|
|
58
|
+
await writeFile(filePath, JSON.stringify(value, null, 2), {
|
|
59
|
+
encoding: "utf8",
|
|
60
|
+
mode: 0o600,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function credentialsPathForAccount(accountId) {
|
|
64
|
+
return path.join(CREDENTIALS_DIR, `${encodeURIComponent(accountId)}.json`);
|
|
65
|
+
}
|
|
66
|
+
async function canUseLinuxSecretTool() {
|
|
67
|
+
if (process.platform !== "linux") {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
const result = await runCommand("which", ["secret-tool"]);
|
|
71
|
+
return result.code === 0;
|
|
72
|
+
}
|
|
73
|
+
async function canUseMacSecurity() {
|
|
74
|
+
if (process.platform !== "darwin") {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
const result = await runCommand("which", ["security"]);
|
|
78
|
+
return result.code === 0;
|
|
79
|
+
}
|
|
80
|
+
async function setInKeychain(account, value) {
|
|
81
|
+
if (await canUseMacSecurity()) {
|
|
82
|
+
const result = await runCommand("security", [
|
|
83
|
+
"add-generic-password",
|
|
84
|
+
"-U",
|
|
85
|
+
"-s",
|
|
86
|
+
SERVICE_NAME,
|
|
87
|
+
"-a",
|
|
88
|
+
account,
|
|
89
|
+
"-w",
|
|
90
|
+
value,
|
|
91
|
+
]);
|
|
92
|
+
return result.code === 0;
|
|
93
|
+
}
|
|
94
|
+
if (await canUseLinuxSecretTool()) {
|
|
95
|
+
const result = await runCommand("secret-tool", ["store", `--label=${SERVICE_NAME}`, "service", SERVICE_NAME, "account", account], value);
|
|
96
|
+
return result.code === 0;
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
async function getFromKeychain(account) {
|
|
101
|
+
if (await canUseMacSecurity()) {
|
|
102
|
+
const result = await runCommand("security", [
|
|
103
|
+
"find-generic-password",
|
|
104
|
+
"-s",
|
|
105
|
+
SERVICE_NAME,
|
|
106
|
+
"-a",
|
|
107
|
+
account,
|
|
108
|
+
"-w",
|
|
109
|
+
]);
|
|
110
|
+
if (result.code !== 0) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
const value = result.stdout.trim();
|
|
114
|
+
return value.length > 0 ? value : null;
|
|
115
|
+
}
|
|
116
|
+
if (await canUseLinuxSecretTool()) {
|
|
117
|
+
const result = await runCommand("secret-tool", [
|
|
118
|
+
"lookup",
|
|
119
|
+
"service",
|
|
120
|
+
SERVICE_NAME,
|
|
121
|
+
"account",
|
|
122
|
+
account,
|
|
123
|
+
]);
|
|
124
|
+
if (result.code !== 0) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const value = result.stdout.trim();
|
|
128
|
+
return value.length > 0 ? value : null;
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
async function deleteFromKeychain(account) {
|
|
133
|
+
if (await canUseMacSecurity()) {
|
|
134
|
+
const result = await runCommand("security", [
|
|
135
|
+
"delete-generic-password",
|
|
136
|
+
"-s",
|
|
137
|
+
SERVICE_NAME,
|
|
138
|
+
"-a",
|
|
139
|
+
account,
|
|
140
|
+
]);
|
|
141
|
+
return result.code === 0;
|
|
142
|
+
}
|
|
143
|
+
if (await canUseLinuxSecretTool()) {
|
|
144
|
+
const result = await runCommand("secret-tool", [
|
|
145
|
+
"clear",
|
|
146
|
+
"service",
|
|
147
|
+
SERVICE_NAME,
|
|
148
|
+
"account",
|
|
149
|
+
account,
|
|
150
|
+
]);
|
|
151
|
+
return result.code === 0;
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
export async function saveConfig(config) {
|
|
156
|
+
await writeJsonFile(CONFIG_PATH, config);
|
|
157
|
+
}
|
|
158
|
+
export async function loadConfig() {
|
|
159
|
+
return readJsonFile(CONFIG_PATH);
|
|
160
|
+
}
|
|
161
|
+
export async function clearConfig() {
|
|
162
|
+
await rm(CONFIG_PATH, {
|
|
163
|
+
force: true,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
export async function saveCredentials(accountId, credentials) {
|
|
167
|
+
const serialized = JSON.stringify(credentials);
|
|
168
|
+
const storedInKeychain = await setInKeychain(accountId, serialized);
|
|
169
|
+
if (storedInKeychain) {
|
|
170
|
+
await rm(credentialsPathForAccount(accountId), {
|
|
171
|
+
force: true,
|
|
172
|
+
});
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
await ensureCredentialsDir();
|
|
176
|
+
await writeFile(credentialsPathForAccount(accountId), serialized, {
|
|
177
|
+
encoding: "utf8",
|
|
178
|
+
mode: 0o600,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
export async function loadCredentials(accountId) {
|
|
182
|
+
const fromKeychain = await getFromKeychain(accountId);
|
|
183
|
+
if (fromKeychain !== null) {
|
|
184
|
+
try {
|
|
185
|
+
return JSON.parse(fromKeychain);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Fall back to file credentials when keychain data is malformed.
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const fromFile = await readJsonFile(credentialsPathForAccount(accountId));
|
|
192
|
+
return fromFile;
|
|
193
|
+
}
|
|
194
|
+
export async function deleteCredentials(accountId) {
|
|
195
|
+
await deleteFromKeychain(accountId);
|
|
196
|
+
await rm(credentialsPathForAccount(accountId), {
|
|
197
|
+
force: true,
|
|
198
|
+
});
|
|
199
|
+
}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare class CliHttpError extends Error {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
readonly status: number;
|
|
4
|
+
readonly requestId: string | null;
|
|
5
|
+
constructor(input: {
|
|
6
|
+
code: string;
|
|
7
|
+
status: number;
|
|
8
|
+
message: string;
|
|
9
|
+
requestId?: string | null;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export declare function postJson<TResponse>(input: {
|
|
13
|
+
baseUrl: string;
|
|
14
|
+
path: string;
|
|
15
|
+
payload: unknown;
|
|
16
|
+
accessToken?: string | null;
|
|
17
|
+
}): Promise<TResponse>;
|
|
18
|
+
export declare function getJson<TResponse>(input: {
|
|
19
|
+
baseUrl: string;
|
|
20
|
+
path: string;
|
|
21
|
+
accessToken?: string | null;
|
|
22
|
+
}): Promise<TResponse>;
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export class CliHttpError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
status;
|
|
4
|
+
requestId;
|
|
5
|
+
constructor(input) {
|
|
6
|
+
super(input.message);
|
|
7
|
+
this.name = "CliHttpError";
|
|
8
|
+
this.code = input.code;
|
|
9
|
+
this.status = input.status;
|
|
10
|
+
this.requestId = input.requestId ?? null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
async function parseJson(response) {
|
|
14
|
+
try {
|
|
15
|
+
return await response.json();
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function postJson(input) {
|
|
22
|
+
const response = await fetch(`${input.baseUrl.replace(/\/$/, "")}${input.path}`, {
|
|
23
|
+
method: "POST",
|
|
24
|
+
headers: {
|
|
25
|
+
"content-type": "application/json",
|
|
26
|
+
...(input.accessToken
|
|
27
|
+
? {
|
|
28
|
+
authorization: `Bearer ${input.accessToken}`,
|
|
29
|
+
}
|
|
30
|
+
: {}),
|
|
31
|
+
},
|
|
32
|
+
body: JSON.stringify(input.payload),
|
|
33
|
+
});
|
|
34
|
+
const parsed = await parseJson(response);
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
const payload = parsed;
|
|
37
|
+
throw new CliHttpError({
|
|
38
|
+
code: payload?.error?.code ?? "HTTP_ERROR",
|
|
39
|
+
status: response.status,
|
|
40
|
+
message: payload?.error?.message ?? `HTTP ${response.status}`,
|
|
41
|
+
requestId: payload?.error?.requestId ?? null,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return parsed;
|
|
45
|
+
}
|
|
46
|
+
export async function getJson(input) {
|
|
47
|
+
const response = await fetch(`${input.baseUrl.replace(/\/$/, "")}${input.path}`, {
|
|
48
|
+
method: "GET",
|
|
49
|
+
headers: input.accessToken
|
|
50
|
+
? {
|
|
51
|
+
authorization: `Bearer ${input.accessToken}`,
|
|
52
|
+
}
|
|
53
|
+
: undefined,
|
|
54
|
+
});
|
|
55
|
+
const parsed = await parseJson(response);
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
const payload = parsed;
|
|
58
|
+
throw new CliHttpError({
|
|
59
|
+
code: payload?.error?.code ?? "HTTP_ERROR",
|
|
60
|
+
status: response.status,
|
|
61
|
+
message: payload?.error?.message ?? `HTTP ${response.status}`,
|
|
62
|
+
requestId: payload?.error?.requestId ?? null,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return parsed;
|
|
66
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import pc from "picocolors";
|
|
4
|
+
import { registerAuthCommands } from "./commands/auth.js";
|
|
5
|
+
import { registerEnvCommands } from "./commands/env.js";
|
|
6
|
+
import { registerTypegenCommand } from "./commands/typegen.js";
|
|
7
|
+
import { CLI_DESCRIPTION, CLI_NAME, CLI_VERSION } from "./constants.js";
|
|
8
|
+
const program = new Command();
|
|
9
|
+
program.name(CLI_NAME).description(CLI_DESCRIPTION).version(CLI_VERSION);
|
|
10
|
+
registerAuthCommands(program);
|
|
11
|
+
registerEnvCommands(program);
|
|
12
|
+
registerTypegenCommand(program);
|
|
13
|
+
program.parseAsync(process.argv).catch((error) => {
|
|
14
|
+
const message = error instanceof Error ? error.message : "Command failed.";
|
|
15
|
+
console.error(pc.red(message));
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type BarekeyRuntimeConfig = {
|
|
2
|
+
org?: string;
|
|
3
|
+
project?: string;
|
|
4
|
+
environment?: string;
|
|
5
|
+
};
|
|
6
|
+
export type BarekeyRuntimeConfigResult = {
|
|
7
|
+
config: BarekeyRuntimeConfig;
|
|
8
|
+
path: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function loadRuntimeConfig(): Promise<BarekeyRuntimeConfigResult | null>;
|