@barekey/cli 0.3.3 → 0.5.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 +53 -12
- package/bun.lock +9 -3
- package/dist/auth-provider.js +7 -4
- package/dist/command-utils.d.ts +1 -1
- package/dist/command-utils.js +7 -6
- package/dist/commands/audit.d.ts +2 -0
- package/dist/commands/audit.js +47 -0
- package/dist/commands/auth.js +22 -7
- package/dist/commands/billing.d.ts +2 -0
- package/dist/commands/billing.js +62 -0
- package/dist/commands/env.js +158 -98
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +32 -0
- package/dist/commands/org.d.ts +2 -0
- package/dist/commands/org.js +85 -0
- package/dist/commands/project.d.ts +2 -0
- package/dist/commands/project.js +99 -0
- package/dist/commands/stage.d.ts +2 -0
- package/dist/commands/stage.js +125 -0
- package/dist/commands/target-prompts.d.ts +184 -0
- package/dist/commands/target-prompts.js +312 -0
- package/dist/commands/typegen.d.ts +2 -2
- package/dist/commands/typegen.js +61 -22
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/context/session-id.d.ts +11 -0
- package/dist/context/session-id.js +14 -0
- package/dist/contracts/index.d.ts +491 -0
- package/dist/contracts/index.js +307 -0
- package/dist/credentials-store.js +70 -11
- package/dist/http.d.ts +34 -0
- package/dist/http.js +56 -2
- package/dist/index.js +12 -0
- package/dist/runtime-config.d.ts +4 -0
- package/dist/runtime-config.js +16 -11
- package/dist/typegen/core.d.ts +45 -0
- package/dist/typegen/core.js +219 -0
- package/dist/types.d.ts +5 -3
- package/package.json +2 -2
- package/src/auth-provider.ts +8 -5
- package/src/command-utils.ts +8 -7
- package/src/commands/audit.ts +63 -0
- package/src/commands/auth.ts +32 -37
- package/src/commands/billing.ts +73 -0
- package/src/commands/env.ts +215 -189
- package/src/commands/init.ts +47 -0
- package/src/commands/org.ts +104 -0
- package/src/commands/project.ts +130 -0
- package/src/commands/stage.ts +167 -0
- package/src/commands/target-prompts.ts +357 -0
- package/src/commands/typegen.ts +75 -29
- package/src/constants.ts +1 -1
- package/src/context/session-id.ts +14 -0
- package/src/contracts/index.ts +370 -0
- package/src/credentials-store.ts +86 -12
- package/src/http.ts +78 -2
- package/src/index.ts +12 -0
- package/src/runtime-config.ts +25 -14
- package/src/typegen/core.ts +311 -0
- package/src/types.ts +5 -3
- package/test/command-utils.test.ts +47 -0
- package/test/credentials-store.test.ts +40 -0
- package/test/runtime-config.test.ts +125 -0
package/dist/commands/env.js
CHANGED
|
@@ -1,77 +1,136 @@
|
|
|
1
1
|
import { writeFile } from "node:fs/promises";
|
|
2
|
-
import { cancel, confirm, isCancel } from "@clack/prompts";
|
|
3
|
-
import { BarekeyClient } from "@barekey/sdk/server";
|
|
4
|
-
import pc from "picocolors";
|
|
2
|
+
import { cancel, confirm, isCancel, select, text } from "@clack/prompts";
|
|
5
3
|
import { createCliAuthProvider } from "../auth-provider.js";
|
|
6
4
|
import { addTargetOptions, dotenvEscape, parseChance, requireLocalSession, resolveTarget, toJsonOutput, } from "../command-utils.js";
|
|
5
|
+
import { EnvEvaluateBatchResponseSchema, EnvEvaluateResponseSchema, EnvListResponseSchema, EnvPullResponseSchema, EnvWriteResponseSchema, } from "../contracts/index.js";
|
|
7
6
|
import { postJson } from "../http.js";
|
|
8
7
|
import { collectOptionValues, parseRolloutFunction, parseRolloutMilestones, parseVisibility, } from "./env-helpers.js";
|
|
9
|
-
function
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
async function resolveEnvAccess(options) {
|
|
9
|
+
const local = await requireLocalSession();
|
|
10
|
+
const target = await resolveTarget(options, local);
|
|
11
|
+
const authProvider = createCliAuthProvider();
|
|
12
|
+
const accessToken = await authProvider.getAccessToken();
|
|
13
|
+
return {
|
|
14
|
+
local,
|
|
15
|
+
target,
|
|
16
|
+
accessToken,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function renderScalar(value) {
|
|
20
|
+
if (typeof value === "string") {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
return JSON.stringify(value);
|
|
24
|
+
}
|
|
25
|
+
async function promptForRequiredText(currentValue, message) {
|
|
26
|
+
const existing = currentValue?.trim();
|
|
27
|
+
if (existing && existing.length > 0) {
|
|
28
|
+
return existing;
|
|
13
29
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
30
|
+
if (!process.stdout.isTTY) {
|
|
31
|
+
throw new Error(`${message} is required in non-interactive mode.`);
|
|
32
|
+
}
|
|
33
|
+
const prompted = await text({
|
|
34
|
+
message,
|
|
35
|
+
validate(value) {
|
|
36
|
+
return value.trim().length > 0 ? undefined : "This value is required.";
|
|
37
|
+
},
|
|
18
38
|
});
|
|
39
|
+
if (isCancel(prompted)) {
|
|
40
|
+
cancel("Command canceled.");
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
return prompted.trim();
|
|
19
44
|
}
|
|
20
|
-
async function
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
45
|
+
async function maybePromptVariant(writeOptions) {
|
|
46
|
+
if (writeOptions.ab !== undefined || writeOptions.rollout !== undefined || !process.stdout.isTTY) {
|
|
47
|
+
return writeOptions;
|
|
48
|
+
}
|
|
49
|
+
const selectedKind = await select({
|
|
50
|
+
message: "What kind of variable is this?",
|
|
51
|
+
options: [
|
|
52
|
+
{ value: "secret", label: "Secret", hint: "One stored value" },
|
|
53
|
+
{ value: "ab_roll", label: "A/B roll", hint: "Two values plus traffic split" },
|
|
54
|
+
{ value: "rollout", label: "Rollout", hint: "Two values plus rollout milestones" },
|
|
55
|
+
],
|
|
56
|
+
initialValue: "secret",
|
|
27
57
|
});
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
58
|
+
if (isCancel(selectedKind)) {
|
|
59
|
+
cancel("Command canceled.");
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
if (selectedKind === "ab_roll") {
|
|
63
|
+
const valueB = await promptForRequiredText(undefined, "What's the alternate value?");
|
|
64
|
+
const chance = await promptForRequiredText(writeOptions.chance, "What's the A-branch probability between 0 and 1?");
|
|
65
|
+
return {
|
|
66
|
+
...writeOptions,
|
|
67
|
+
ab: valueB,
|
|
68
|
+
chance,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (selectedKind === "rollout") {
|
|
72
|
+
const valueB = await promptForRequiredText(undefined, "What's the rollout value B?");
|
|
73
|
+
return {
|
|
74
|
+
...writeOptions,
|
|
75
|
+
rollout: valueB,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return writeOptions;
|
|
79
|
+
}
|
|
80
|
+
async function runEnvGet(name, options) {
|
|
81
|
+
const resolvedName = await promptForRequiredText(name, "What's the name of this variable?");
|
|
82
|
+
const { local, target, accessToken } = await resolveEnvAccess(options);
|
|
83
|
+
const value = await postJson({
|
|
84
|
+
baseUrl: local.baseUrl,
|
|
85
|
+
path: "/v1/env/evaluate",
|
|
86
|
+
accessToken,
|
|
87
|
+
payload: {
|
|
88
|
+
orgSlug: target.orgSlug,
|
|
89
|
+
projectSlug: target.projectSlug,
|
|
90
|
+
stageSlug: target.stageSlug,
|
|
91
|
+
name: resolvedName,
|
|
92
|
+
seed: options.seed,
|
|
93
|
+
key: options.key,
|
|
94
|
+
},
|
|
95
|
+
schema: EnvEvaluateResponseSchema,
|
|
31
96
|
});
|
|
32
97
|
if (options.json) {
|
|
33
|
-
toJsonOutput(true,
|
|
34
|
-
name,
|
|
35
|
-
value,
|
|
36
|
-
});
|
|
98
|
+
toJsonOutput(true, value);
|
|
37
99
|
return;
|
|
38
100
|
}
|
|
39
|
-
console.log(
|
|
101
|
+
console.log(renderScalar(value.value));
|
|
40
102
|
}
|
|
41
103
|
async function runEnvGetMany(options) {
|
|
42
|
-
const
|
|
43
|
-
const target = await
|
|
44
|
-
const
|
|
45
|
-
organization: target.orgSlug ?? local.credentials.orgSlug,
|
|
46
|
-
project: target.projectSlug,
|
|
47
|
-
environment: target.stageSlug,
|
|
48
|
-
});
|
|
49
|
-
const names = options.names
|
|
104
|
+
const namesCsv = await promptForRequiredText(options.names, "Which variable names do you want, comma-separated?");
|
|
105
|
+
const { local, target, accessToken } = await resolveEnvAccess(options);
|
|
106
|
+
const names = namesCsv
|
|
50
107
|
.split(",")
|
|
51
108
|
.map((value) => value.trim())
|
|
52
109
|
.filter((value) => value.length > 0);
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
110
|
+
const response = await postJson({
|
|
111
|
+
baseUrl: local.baseUrl,
|
|
112
|
+
path: "/v1/env/evaluate-batch",
|
|
113
|
+
accessToken,
|
|
114
|
+
payload: {
|
|
115
|
+
orgSlug: target.orgSlug,
|
|
116
|
+
projectSlug: target.projectSlug,
|
|
117
|
+
stageSlug: target.stageSlug,
|
|
118
|
+
names,
|
|
56
119
|
seed: options.seed,
|
|
57
120
|
key: options.key,
|
|
58
|
-
}
|
|
59
|
-
|
|
121
|
+
},
|
|
122
|
+
schema: EnvEvaluateBatchResponseSchema,
|
|
123
|
+
});
|
|
60
124
|
if (options.json) {
|
|
61
|
-
toJsonOutput(true,
|
|
125
|
+
toJsonOutput(true, response.values);
|
|
62
126
|
return;
|
|
63
127
|
}
|
|
64
|
-
for (const value of
|
|
65
|
-
|
|
66
|
-
console.log(`${value.name}=${String(value.value)}`);
|
|
67
|
-
}
|
|
128
|
+
for (const value of [...response.values].sort((left, right) => left.name.localeCompare(right.name))) {
|
|
129
|
+
console.log(`${value.name}=${renderScalar(value.value)}`);
|
|
68
130
|
}
|
|
69
131
|
}
|
|
70
132
|
async function runEnvList(options) {
|
|
71
|
-
const local = await
|
|
72
|
-
const target = await resolveTarget(options, local);
|
|
73
|
-
const authProvider = createCliAuthProvider();
|
|
74
|
-
const accessToken = await authProvider.getAccessToken();
|
|
133
|
+
const { local, target, accessToken } = await resolveEnvAccess(options);
|
|
75
134
|
const response = await postJson({
|
|
76
135
|
baseUrl: local.baseUrl,
|
|
77
136
|
path: "/v1/env/list",
|
|
@@ -81,6 +140,7 @@ async function runEnvList(options) {
|
|
|
81
140
|
projectSlug: target.projectSlug,
|
|
82
141
|
stageSlug: target.stageSlug,
|
|
83
142
|
},
|
|
143
|
+
schema: EnvListResponseSchema,
|
|
84
144
|
});
|
|
85
145
|
if (options.json) {
|
|
86
146
|
toJsonOutput(true, response.variables);
|
|
@@ -93,56 +153,57 @@ async function runEnvList(options) {
|
|
|
93
153
|
for (const row of response.variables) {
|
|
94
154
|
const chanceSuffix = row.kind === "ab_roll" ? ` chance=${row.chance ?? 0}` : "";
|
|
95
155
|
const rolloutSuffix = row.kind === "rollout"
|
|
96
|
-
? ` ${
|
|
156
|
+
? ` ${row.rolloutFunction ?? "linear"}(${row.rolloutMilestones?.length ?? 0} milestones)`
|
|
97
157
|
: "";
|
|
98
|
-
console.log(`${row.name} ${
|
|
158
|
+
console.log(`${row.name} ${row.visibility} ${row.kind} ${row.declaredType}${chanceSuffix}${rolloutSuffix}`);
|
|
99
159
|
}
|
|
100
160
|
}
|
|
101
161
|
async function runEnvWrite(operation, name, value, options) {
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
const accessToken = await
|
|
106
|
-
if (
|
|
162
|
+
const resolvedName = await promptForRequiredText(name, "What's the name of this variable?");
|
|
163
|
+
const resolvedValue = await promptForRequiredText(value, "What's the value of this variable?");
|
|
164
|
+
const resolvedOptions = await maybePromptVariant(options);
|
|
165
|
+
const { local, target, accessToken } = await resolveEnvAccess(resolvedOptions);
|
|
166
|
+
if (resolvedOptions.ab !== undefined && resolvedOptions.rollout !== undefined) {
|
|
107
167
|
throw new Error("Use either --ab or --rollout, not both.");
|
|
108
168
|
}
|
|
109
|
-
const hasRolloutPoints = (
|
|
110
|
-
if (
|
|
169
|
+
const hasRolloutPoints = (resolvedOptions.point?.length ?? 0) > 0;
|
|
170
|
+
if (resolvedOptions.rollout === undefined &&
|
|
171
|
+
(resolvedOptions.function !== undefined || hasRolloutPoints)) {
|
|
111
172
|
throw new Error("--function and --point can only be used together with --rollout.");
|
|
112
173
|
}
|
|
113
|
-
if (
|
|
174
|
+
if (resolvedOptions.ab !== undefined && (resolvedOptions.function !== undefined || hasRolloutPoints)) {
|
|
114
175
|
throw new Error("--function and --point are only supported for --rollout, not --ab.");
|
|
115
176
|
}
|
|
116
|
-
if (
|
|
177
|
+
if (resolvedOptions.rollout !== undefined && resolvedOptions.chance !== undefined) {
|
|
117
178
|
throw new Error("--chance only applies to --ab.");
|
|
118
179
|
}
|
|
119
|
-
const entry =
|
|
180
|
+
const entry = resolvedOptions.rollout !== undefined
|
|
120
181
|
? {
|
|
121
|
-
name,
|
|
122
|
-
visibility: parseVisibility(
|
|
182
|
+
name: resolvedName,
|
|
183
|
+
visibility: parseVisibility(resolvedOptions.visibility),
|
|
123
184
|
kind: "rollout",
|
|
124
|
-
declaredType:
|
|
125
|
-
valueA:
|
|
126
|
-
valueB:
|
|
127
|
-
rolloutFunction: parseRolloutFunction(
|
|
128
|
-
rolloutMilestones: parseRolloutMilestones(
|
|
185
|
+
declaredType: resolvedOptions.type ?? "string",
|
|
186
|
+
valueA: resolvedValue,
|
|
187
|
+
valueB: resolvedOptions.rollout,
|
|
188
|
+
rolloutFunction: parseRolloutFunction(resolvedOptions.function),
|
|
189
|
+
rolloutMilestones: parseRolloutMilestones(resolvedOptions.point),
|
|
129
190
|
}
|
|
130
|
-
:
|
|
191
|
+
: resolvedOptions.ab !== undefined
|
|
131
192
|
? {
|
|
132
|
-
name,
|
|
133
|
-
visibility: parseVisibility(
|
|
193
|
+
name: resolvedName,
|
|
194
|
+
visibility: parseVisibility(resolvedOptions.visibility),
|
|
134
195
|
kind: "ab_roll",
|
|
135
|
-
declaredType:
|
|
136
|
-
valueA:
|
|
137
|
-
valueB:
|
|
138
|
-
chance: parseChance(
|
|
196
|
+
declaredType: resolvedOptions.type ?? "string",
|
|
197
|
+
valueA: resolvedValue,
|
|
198
|
+
valueB: resolvedOptions.ab,
|
|
199
|
+
chance: parseChance(resolvedOptions.chance),
|
|
139
200
|
}
|
|
140
201
|
: {
|
|
141
|
-
name,
|
|
142
|
-
visibility: parseVisibility(
|
|
202
|
+
name: resolvedName,
|
|
203
|
+
visibility: parseVisibility(resolvedOptions.visibility),
|
|
143
204
|
kind: "secret",
|
|
144
|
-
declaredType:
|
|
145
|
-
value,
|
|
205
|
+
declaredType: resolvedOptions.type ?? "string",
|
|
206
|
+
value: resolvedValue,
|
|
146
207
|
};
|
|
147
208
|
const result = await postJson({
|
|
148
209
|
baseUrl: local.baseUrl,
|
|
@@ -156,18 +217,17 @@ async function runEnvWrite(operation, name, value, options) {
|
|
|
156
217
|
entries: [entry],
|
|
157
218
|
deletes: [],
|
|
158
219
|
},
|
|
220
|
+
schema: EnvWriteResponseSchema,
|
|
159
221
|
});
|
|
160
|
-
if (
|
|
222
|
+
if (resolvedOptions.json) {
|
|
161
223
|
toJsonOutput(true, result);
|
|
162
224
|
return;
|
|
163
225
|
}
|
|
164
226
|
console.log(`Created: ${result.createdCount}, Updated: ${result.updatedCount}, Deleted: ${result.deletedCount}`);
|
|
165
227
|
}
|
|
166
228
|
async function runEnvDelete(name, options) {
|
|
167
|
-
const
|
|
168
|
-
const target = await
|
|
169
|
-
const authProvider = createCliAuthProvider();
|
|
170
|
-
const accessToken = await authProvider.getAccessToken();
|
|
229
|
+
const resolvedName = await promptForRequiredText(name, "What's the name of the variable to delete?");
|
|
230
|
+
const { local, target, accessToken } = await resolveEnvAccess(options);
|
|
171
231
|
if (!options.ignoreMissing) {
|
|
172
232
|
const listed = await postJson({
|
|
173
233
|
baseUrl: local.baseUrl,
|
|
@@ -178,10 +238,11 @@ async function runEnvDelete(name, options) {
|
|
|
178
238
|
projectSlug: target.projectSlug,
|
|
179
239
|
stageSlug: target.stageSlug,
|
|
180
240
|
},
|
|
241
|
+
schema: EnvListResponseSchema,
|
|
181
242
|
});
|
|
182
|
-
const exists = listed.variables.some((row) => row.name ===
|
|
243
|
+
const exists = listed.variables.some((row) => row.name === resolvedName);
|
|
183
244
|
if (!exists) {
|
|
184
|
-
throw new Error(`Variable ${
|
|
245
|
+
throw new Error(`Variable ${resolvedName} was not found in this stage.`);
|
|
185
246
|
}
|
|
186
247
|
}
|
|
187
248
|
if (!options.yes) {
|
|
@@ -189,7 +250,7 @@ async function runEnvDelete(name, options) {
|
|
|
189
250
|
throw new Error("Deletion requires --yes in non-interactive mode.");
|
|
190
251
|
}
|
|
191
252
|
const confirmed = await confirm({
|
|
192
|
-
message: `Delete variable ${
|
|
253
|
+
message: `Delete variable ${resolvedName}?`,
|
|
193
254
|
initialValue: false,
|
|
194
255
|
});
|
|
195
256
|
if (isCancel(confirmed)) {
|
|
@@ -210,8 +271,9 @@ async function runEnvDelete(name, options) {
|
|
|
210
271
|
stageSlug: target.stageSlug,
|
|
211
272
|
mode: "upsert",
|
|
212
273
|
entries: [],
|
|
213
|
-
deletes: [
|
|
274
|
+
deletes: [resolvedName],
|
|
214
275
|
},
|
|
276
|
+
schema: EnvWriteResponseSchema,
|
|
215
277
|
});
|
|
216
278
|
if (options.json) {
|
|
217
279
|
toJsonOutput(true, result);
|
|
@@ -220,10 +282,7 @@ async function runEnvDelete(name, options) {
|
|
|
220
282
|
console.log(`Deleted: ${result.deletedCount}`);
|
|
221
283
|
}
|
|
222
284
|
async function runEnvPull(options) {
|
|
223
|
-
const local = await
|
|
224
|
-
const target = await resolveTarget(options, local);
|
|
225
|
-
const authProvider = createCliAuthProvider();
|
|
226
|
-
const accessToken = await authProvider.getAccessToken();
|
|
285
|
+
const { local, target, accessToken } = await resolveEnvAccess(options);
|
|
227
286
|
const response = await postJson({
|
|
228
287
|
baseUrl: local.baseUrl,
|
|
229
288
|
path: "/v1/env/pull",
|
|
@@ -235,6 +294,7 @@ async function runEnvPull(options) {
|
|
|
235
294
|
seed: options.seed,
|
|
236
295
|
key: options.key,
|
|
237
296
|
},
|
|
297
|
+
schema: EnvPullResponseSchema,
|
|
238
298
|
});
|
|
239
299
|
const format = options.format ?? "dotenv";
|
|
240
300
|
const sortedKeys = Object.keys(response.byName).sort((left, right) => left.localeCompare(right));
|
|
@@ -257,7 +317,7 @@ export function registerEnvCommands(program) {
|
|
|
257
317
|
addTargetOptions(env
|
|
258
318
|
.command("get")
|
|
259
319
|
.description("Evaluate one variable")
|
|
260
|
-
.argument("
|
|
320
|
+
.argument("[name]", "Variable name")
|
|
261
321
|
.option("--seed <value>", "Deterministic seed")
|
|
262
322
|
.option("--key <value>", "Deterministic key")
|
|
263
323
|
.option("--json", "Machine-readable output", false)).action(async (name, options) => {
|
|
@@ -266,7 +326,7 @@ export function registerEnvCommands(program) {
|
|
|
266
326
|
addTargetOptions(env
|
|
267
327
|
.command("get-many")
|
|
268
328
|
.description("Evaluate a batch of variables")
|
|
269
|
-
.
|
|
329
|
+
.option("--names <csv>", "Comma-separated variable names")
|
|
270
330
|
.option("--seed <value>", "Deterministic seed")
|
|
271
331
|
.option("--key <value>", "Deterministic key")
|
|
272
332
|
.option("--json", "Machine-readable output", false)).action(async (options) => {
|
|
@@ -281,8 +341,8 @@ export function registerEnvCommands(program) {
|
|
|
281
341
|
addTargetOptions(env
|
|
282
342
|
.command("new")
|
|
283
343
|
.description("Create one variable")
|
|
284
|
-
.argument("
|
|
285
|
-
.argument("
|
|
344
|
+
.argument("[name]", "Variable name")
|
|
345
|
+
.argument("[value]", "Variable value")
|
|
286
346
|
.option("--ab <value-b>", "Second value for ab_roll")
|
|
287
347
|
.option("--rollout <value-b>", "Second value for rollout")
|
|
288
348
|
.option("--chance <number>", "A-branch probability between 0 and 1")
|
|
@@ -296,8 +356,8 @@ export function registerEnvCommands(program) {
|
|
|
296
356
|
addTargetOptions(env
|
|
297
357
|
.command("set")
|
|
298
358
|
.description("Upsert one variable")
|
|
299
|
-
.argument("
|
|
300
|
-
.argument("
|
|
359
|
+
.argument("[name]", "Variable name")
|
|
360
|
+
.argument("[value]", "Variable value")
|
|
301
361
|
.option("--ab <value-b>", "Second value for ab_roll")
|
|
302
362
|
.option("--rollout <value-b>", "Second value for rollout")
|
|
303
363
|
.option("--chance <number>", "A-branch probability between 0 and 1")
|
|
@@ -311,7 +371,7 @@ export function registerEnvCommands(program) {
|
|
|
311
371
|
addTargetOptions(env
|
|
312
372
|
.command("delete")
|
|
313
373
|
.description("Delete one variable")
|
|
314
|
-
.argument("
|
|
374
|
+
.argument("[name]", "Variable name")
|
|
315
375
|
.option("--yes", "Skip confirmation", false)
|
|
316
376
|
.option("--ignore-missing", "Do not fail when variable is missing", false)
|
|
317
377
|
.option("--json", "Machine-readable output", false)).action(async (name, options) => {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { promptForOrganizationSlug, promptForProjectSlug, promptForStageSlug, } from "./target-prompts.js";
|
|
4
|
+
async function runInit(options) {
|
|
5
|
+
const orgSlug = await promptForOrganizationSlug(options.org);
|
|
6
|
+
const projectSlug = await promptForProjectSlug(orgSlug, options.project);
|
|
7
|
+
const stageSlug = await promptForStageSlug(orgSlug, projectSlug, options.stage);
|
|
8
|
+
const configPath = path.resolve(options.path?.trim() || "barekey.json");
|
|
9
|
+
const contents = `${JSON.stringify({
|
|
10
|
+
organization: orgSlug,
|
|
11
|
+
project: projectSlug,
|
|
12
|
+
environment: stageSlug,
|
|
13
|
+
config: {
|
|
14
|
+
mode: "centralized",
|
|
15
|
+
typegen: "semantic",
|
|
16
|
+
},
|
|
17
|
+
}, null, 2)}\n`;
|
|
18
|
+
await writeFile(configPath, contents, "utf8");
|
|
19
|
+
console.log(`Wrote ${configPath}`);
|
|
20
|
+
}
|
|
21
|
+
export function registerInitCommand(program) {
|
|
22
|
+
program
|
|
23
|
+
.command("init")
|
|
24
|
+
.description("Create or update barekey.json for the current repo")
|
|
25
|
+
.option("--org <slug>", "Organization slug")
|
|
26
|
+
.option("--project <slug>", "Project slug")
|
|
27
|
+
.option("--stage <slug>", "Stage slug")
|
|
28
|
+
.option("--path <path>", "Config path", "barekey.json")
|
|
29
|
+
.action(async (options) => {
|
|
30
|
+
await runInit(options);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { text, isCancel, cancel } from "@clack/prompts";
|
|
2
|
+
import { createCliAuthProvider } from "../auth-provider.js";
|
|
3
|
+
import { requireLocalSession, toJsonOutput } from "../command-utils.js";
|
|
4
|
+
import { OrganizationsResponseSchema } from "../contracts/index.js";
|
|
5
|
+
import { getJson, postJson } from "../http.js";
|
|
6
|
+
async function promptForName(name) {
|
|
7
|
+
const existing = name?.trim();
|
|
8
|
+
if (existing && existing.length > 0) {
|
|
9
|
+
return existing;
|
|
10
|
+
}
|
|
11
|
+
if (!process.stdout.isTTY) {
|
|
12
|
+
throw new Error("Organization name is required in non-interactive mode.");
|
|
13
|
+
}
|
|
14
|
+
const prompted = await text({
|
|
15
|
+
message: "What’s the name of this organization?",
|
|
16
|
+
validate: (value) => (value.trim().length > 0 ? undefined : "Name is required."),
|
|
17
|
+
});
|
|
18
|
+
if (isCancel(prompted)) {
|
|
19
|
+
cancel("Command canceled.");
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
return prompted.trim();
|
|
23
|
+
}
|
|
24
|
+
async function runOrgList(options) {
|
|
25
|
+
const local = await requireLocalSession();
|
|
26
|
+
const authProvider = createCliAuthProvider();
|
|
27
|
+
const accessToken = await authProvider.getAccessToken();
|
|
28
|
+
const response = await getJson({
|
|
29
|
+
baseUrl: local.baseUrl,
|
|
30
|
+
path: "/v1/cli/orgs",
|
|
31
|
+
accessToken,
|
|
32
|
+
schema: OrganizationsResponseSchema,
|
|
33
|
+
});
|
|
34
|
+
if (options.json) {
|
|
35
|
+
toJsonOutput(true, response.organizations);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (response.organizations.length === 0) {
|
|
39
|
+
console.log("No organizations found.");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
for (const organization of response.organizations) {
|
|
43
|
+
console.log(`${organization.name} (${organization.slug}) ${organization.role}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function runOrgCreate(name, options) {
|
|
47
|
+
const resolvedName = await promptForName(name);
|
|
48
|
+
const local = await requireLocalSession();
|
|
49
|
+
const authProvider = createCliAuthProvider();
|
|
50
|
+
const accessToken = await authProvider.getAccessToken();
|
|
51
|
+
const response = await postJson({
|
|
52
|
+
baseUrl: local.baseUrl,
|
|
53
|
+
path: "/v1/cli/orgs/create",
|
|
54
|
+
accessToken,
|
|
55
|
+
payload: {
|
|
56
|
+
name: resolvedName,
|
|
57
|
+
slug: options.slug?.trim() || null,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
if (options.json) {
|
|
61
|
+
toJsonOutput(true, response);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const organization = response.organization;
|
|
65
|
+
console.log(`Created organization ${organization.name} (${organization.slug}).`);
|
|
66
|
+
}
|
|
67
|
+
export function registerOrgCommands(program) {
|
|
68
|
+
const org = program.command("org").description("Organization management");
|
|
69
|
+
org
|
|
70
|
+
.command("list")
|
|
71
|
+
.description("List organizations available to the current user")
|
|
72
|
+
.option("--json", "Machine-readable output", false)
|
|
73
|
+
.action(async (options) => {
|
|
74
|
+
await runOrgList(options);
|
|
75
|
+
});
|
|
76
|
+
org
|
|
77
|
+
.command("create")
|
|
78
|
+
.description("Create an organization")
|
|
79
|
+
.argument("[name]", "Organization name")
|
|
80
|
+
.option("--slug <slug>", "Organization slug")
|
|
81
|
+
.option("--json", "Machine-readable output", false)
|
|
82
|
+
.action(async (name, options) => {
|
|
83
|
+
await runOrgCreate(name, options);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { cancel, confirm, isCancel, text } from "@clack/prompts";
|
|
2
|
+
import { toJsonOutput } from "../command-utils.js";
|
|
3
|
+
import { createProjectForOrganization, deleteProjectForOrganization, listProjectsForOrganization, promptForOrganizationSlug, promptForProjectSlug, } from "./target-prompts.js";
|
|
4
|
+
async function promptForProjectName(name) {
|
|
5
|
+
const existing = name?.trim();
|
|
6
|
+
if (existing && existing.length > 0) {
|
|
7
|
+
return existing;
|
|
8
|
+
}
|
|
9
|
+
if (!process.stdout.isTTY) {
|
|
10
|
+
throw new Error("Project name is required in non-interactive mode.");
|
|
11
|
+
}
|
|
12
|
+
const prompted = await text({
|
|
13
|
+
message: "What’s the name of this project?",
|
|
14
|
+
validate: (value) => (value.trim().length > 0 ? undefined : "Project name is required."),
|
|
15
|
+
});
|
|
16
|
+
if (isCancel(prompted)) {
|
|
17
|
+
cancel("Command canceled.");
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
return prompted.trim();
|
|
21
|
+
}
|
|
22
|
+
async function runProjectList(options) {
|
|
23
|
+
const orgSlug = await promptForOrganizationSlug(options.org);
|
|
24
|
+
const projects = await listProjectsForOrganization(orgSlug);
|
|
25
|
+
if (options.json) {
|
|
26
|
+
toJsonOutput(true, projects);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (projects.length === 0) {
|
|
30
|
+
console.log("No projects found.");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
for (const project of projects) {
|
|
34
|
+
console.log(`${project.name} (${project.slug}) secrets=${project.secretCount}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function runProjectCreate(name, options) {
|
|
38
|
+
const orgSlug = await promptForOrganizationSlug(options.org);
|
|
39
|
+
const projectName = await promptForProjectName(name);
|
|
40
|
+
const project = await createProjectForOrganization(orgSlug, projectName);
|
|
41
|
+
if (options.json) {
|
|
42
|
+
toJsonOutput(true, project);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
console.log(`Created project ${project.name} (${project.slug}).`);
|
|
46
|
+
}
|
|
47
|
+
export function registerProjectCommands(program) {
|
|
48
|
+
const project = program.command("project").description("Project management");
|
|
49
|
+
project
|
|
50
|
+
.command("list")
|
|
51
|
+
.description("List projects in an organization")
|
|
52
|
+
.option("--org <slug>", "Organization slug")
|
|
53
|
+
.option("--json", "Machine-readable output", false)
|
|
54
|
+
.action(async (options) => {
|
|
55
|
+
await runProjectList(options);
|
|
56
|
+
});
|
|
57
|
+
project
|
|
58
|
+
.command("create")
|
|
59
|
+
.description("Create a project")
|
|
60
|
+
.argument("[name]", "Project name")
|
|
61
|
+
.option("--org <slug>", "Organization slug")
|
|
62
|
+
.option("--json", "Machine-readable output", false)
|
|
63
|
+
.action(async (name, options) => {
|
|
64
|
+
await runProjectCreate(name, options);
|
|
65
|
+
});
|
|
66
|
+
project
|
|
67
|
+
.command("delete")
|
|
68
|
+
.description("Delete a project")
|
|
69
|
+
.argument("[slug]", "Project slug")
|
|
70
|
+
.option("--org <slug>", "Organization slug")
|
|
71
|
+
.option("--yes", "Skip confirmation prompt", false)
|
|
72
|
+
.option("--json", "Machine-readable output", false)
|
|
73
|
+
.action(async (slug, options) => {
|
|
74
|
+
const orgSlug = await promptForOrganizationSlug(options.org);
|
|
75
|
+
const projectSlug = await promptForProjectSlug(orgSlug, slug);
|
|
76
|
+
if (!options.yes) {
|
|
77
|
+
if (!process.stdout.isTTY) {
|
|
78
|
+
throw new Error("Project deletion requires --yes in non-interactive mode.");
|
|
79
|
+
}
|
|
80
|
+
const confirmed = await confirm({
|
|
81
|
+
message: `Delete project ${projectSlug}?`,
|
|
82
|
+
initialValue: false,
|
|
83
|
+
});
|
|
84
|
+
if (isCancel(confirmed)) {
|
|
85
|
+
cancel("Command canceled.");
|
|
86
|
+
process.exit(0);
|
|
87
|
+
}
|
|
88
|
+
if (!confirmed) {
|
|
89
|
+
throw new Error("Delete canceled.");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const response = await deleteProjectForOrganization(orgSlug, projectSlug);
|
|
93
|
+
if (options.json) {
|
|
94
|
+
toJsonOutput(true, response);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
console.log(`Deleted project ${response.deletedProjectSlug}.`);
|
|
98
|
+
});
|
|
99
|
+
}
|