@barekey/cli 0.4.0 → 0.5.1
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.js +6 -6
- package/dist/commands/audit.d.ts +2 -0
- package/dist/commands/audit.js +47 -0
- package/dist/commands/auth.js +31 -9
- package/dist/commands/billing.d.ts +2 -0
- package/dist/commands/billing.js +59 -0
- package/dist/commands/env.js +157 -125
- 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 +57 -32
- 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 +499 -0
- package/dist/contracts/index.js +313 -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.js +14 -26
- 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 +6 -6
- package/src/commands/audit.ts +63 -0
- package/src/commands/auth.ts +45 -39
- package/src/commands/billing.ts +70 -0
- package/src/commands/env.ts +211 -218
- 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 +71 -45
- package/src/constants.ts +1 -1
- package/src/context/session-id.ts +14 -0
- package/src/contracts/index.ts +376 -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 +19 -32
- 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/dist/commands/env.js
CHANGED
|
@@ -1,105 +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
|
-
import { loadRuntimeConfig } from "../runtime-config.js";
|
|
9
7
|
import { collectOptionValues, parseRolloutFunction, parseRolloutMilestones, parseVisibility, } from "./env-helpers.js";
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return
|
|
31
|
-
json: jsonConfig,
|
|
32
|
-
});
|
|
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;
|
|
33
29
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
throw new Error("Organization slug is required.");
|
|
30
|
+
if (!process.stdout.isTTY) {
|
|
31
|
+
throw new Error(`${message} is required in non-interactive mode.`);
|
|
37
32
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
const prompted = await text({
|
|
34
|
+
message,
|
|
35
|
+
validate(value) {
|
|
36
|
+
return value.trim().length > 0 ? undefined : "This value is required.";
|
|
37
|
+
},
|
|
42
38
|
});
|
|
39
|
+
if (isCancel(prompted)) {
|
|
40
|
+
cancel("Command canceled.");
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
return prompted.trim();
|
|
43
44
|
}
|
|
44
|
-
async function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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",
|
|
53
57
|
});
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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,
|
|
57
96
|
});
|
|
58
97
|
if (options.json) {
|
|
59
|
-
toJsonOutput(true,
|
|
60
|
-
name,
|
|
61
|
-
value,
|
|
62
|
-
});
|
|
98
|
+
toJsonOutput(true, value);
|
|
63
99
|
return;
|
|
64
100
|
}
|
|
65
|
-
console.log(
|
|
101
|
+
console.log(renderScalar(value.value));
|
|
66
102
|
}
|
|
67
103
|
async function runEnvGetMany(options) {
|
|
68
|
-
const
|
|
69
|
-
const local
|
|
70
|
-
const
|
|
71
|
-
const client = createEnvClient({
|
|
72
|
-
organization: target.orgSlug ?? local?.credentials.orgSlug,
|
|
73
|
-
project: target.projectSlug,
|
|
74
|
-
environment: target.stageSlug,
|
|
75
|
-
runtimeConfig: runtime,
|
|
76
|
-
});
|
|
77
|
-
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
|
|
78
107
|
.split(",")
|
|
79
108
|
.map((value) => value.trim())
|
|
80
109
|
.filter((value) => value.length > 0);
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
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,
|
|
84
119
|
seed: options.seed,
|
|
85
120
|
key: options.key,
|
|
86
|
-
}
|
|
87
|
-
|
|
121
|
+
},
|
|
122
|
+
schema: EnvEvaluateBatchResponseSchema,
|
|
123
|
+
});
|
|
88
124
|
if (options.json) {
|
|
89
|
-
toJsonOutput(true,
|
|
125
|
+
toJsonOutput(true, response.values);
|
|
90
126
|
return;
|
|
91
127
|
}
|
|
92
|
-
for (const value of
|
|
93
|
-
|
|
94
|
-
console.log(`${value.name}=${String(value.value)}`);
|
|
95
|
-
}
|
|
128
|
+
for (const value of [...response.values].sort((left, right) => left.name.localeCompare(right.name))) {
|
|
129
|
+
console.log(`${value.name}=${renderScalar(value.value)}`);
|
|
96
130
|
}
|
|
97
131
|
}
|
|
98
132
|
async function runEnvList(options) {
|
|
99
|
-
const local = await
|
|
100
|
-
const target = await resolveTarget(options, local);
|
|
101
|
-
const authProvider = createCliAuthProvider();
|
|
102
|
-
const accessToken = await authProvider.getAccessToken();
|
|
133
|
+
const { local, target, accessToken } = await resolveEnvAccess(options);
|
|
103
134
|
const response = await postJson({
|
|
104
135
|
baseUrl: local.baseUrl,
|
|
105
136
|
path: "/v1/env/list",
|
|
@@ -109,6 +140,7 @@ async function runEnvList(options) {
|
|
|
109
140
|
projectSlug: target.projectSlug,
|
|
110
141
|
stageSlug: target.stageSlug,
|
|
111
142
|
},
|
|
143
|
+
schema: EnvListResponseSchema,
|
|
112
144
|
});
|
|
113
145
|
if (options.json) {
|
|
114
146
|
toJsonOutput(true, response.variables);
|
|
@@ -121,56 +153,57 @@ async function runEnvList(options) {
|
|
|
121
153
|
for (const row of response.variables) {
|
|
122
154
|
const chanceSuffix = row.kind === "ab_roll" ? ` chance=${row.chance ?? 0}` : "";
|
|
123
155
|
const rolloutSuffix = row.kind === "rollout"
|
|
124
|
-
? ` ${
|
|
156
|
+
? ` ${row.rolloutFunction ?? "linear"}(${row.rolloutMilestones?.length ?? 0} milestones)`
|
|
125
157
|
: "";
|
|
126
|
-
console.log(`${row.name} ${
|
|
158
|
+
console.log(`${row.name} ${row.visibility} ${row.kind} ${row.declaredType}${chanceSuffix}${rolloutSuffix}`);
|
|
127
159
|
}
|
|
128
160
|
}
|
|
129
161
|
async function runEnvWrite(operation, name, value, options) {
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
const accessToken = await
|
|
134
|
-
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) {
|
|
135
167
|
throw new Error("Use either --ab or --rollout, not both.");
|
|
136
168
|
}
|
|
137
|
-
const hasRolloutPoints = (
|
|
138
|
-
if (
|
|
169
|
+
const hasRolloutPoints = (resolvedOptions.point?.length ?? 0) > 0;
|
|
170
|
+
if (resolvedOptions.rollout === undefined &&
|
|
171
|
+
(resolvedOptions.function !== undefined || hasRolloutPoints)) {
|
|
139
172
|
throw new Error("--function and --point can only be used together with --rollout.");
|
|
140
173
|
}
|
|
141
|
-
if (
|
|
174
|
+
if (resolvedOptions.ab !== undefined && (resolvedOptions.function !== undefined || hasRolloutPoints)) {
|
|
142
175
|
throw new Error("--function and --point are only supported for --rollout, not --ab.");
|
|
143
176
|
}
|
|
144
|
-
if (
|
|
177
|
+
if (resolvedOptions.rollout !== undefined && resolvedOptions.chance !== undefined) {
|
|
145
178
|
throw new Error("--chance only applies to --ab.");
|
|
146
179
|
}
|
|
147
|
-
const entry =
|
|
180
|
+
const entry = resolvedOptions.rollout !== undefined
|
|
148
181
|
? {
|
|
149
|
-
name,
|
|
150
|
-
visibility: parseVisibility(
|
|
182
|
+
name: resolvedName,
|
|
183
|
+
visibility: parseVisibility(resolvedOptions.visibility),
|
|
151
184
|
kind: "rollout",
|
|
152
|
-
declaredType:
|
|
153
|
-
valueA:
|
|
154
|
-
valueB:
|
|
155
|
-
rolloutFunction: parseRolloutFunction(
|
|
156
|
-
rolloutMilestones: parseRolloutMilestones(
|
|
185
|
+
declaredType: resolvedOptions.type ?? "string",
|
|
186
|
+
valueA: resolvedValue,
|
|
187
|
+
valueB: resolvedOptions.rollout,
|
|
188
|
+
rolloutFunction: parseRolloutFunction(resolvedOptions.function),
|
|
189
|
+
rolloutMilestones: parseRolloutMilestones(resolvedOptions.point),
|
|
157
190
|
}
|
|
158
|
-
:
|
|
191
|
+
: resolvedOptions.ab !== undefined
|
|
159
192
|
? {
|
|
160
|
-
name,
|
|
161
|
-
visibility: parseVisibility(
|
|
193
|
+
name: resolvedName,
|
|
194
|
+
visibility: parseVisibility(resolvedOptions.visibility),
|
|
162
195
|
kind: "ab_roll",
|
|
163
|
-
declaredType:
|
|
164
|
-
valueA:
|
|
165
|
-
valueB:
|
|
166
|
-
chance: parseChance(
|
|
196
|
+
declaredType: resolvedOptions.type ?? "string",
|
|
197
|
+
valueA: resolvedValue,
|
|
198
|
+
valueB: resolvedOptions.ab,
|
|
199
|
+
chance: parseChance(resolvedOptions.chance),
|
|
167
200
|
}
|
|
168
201
|
: {
|
|
169
|
-
name,
|
|
170
|
-
visibility: parseVisibility(
|
|
202
|
+
name: resolvedName,
|
|
203
|
+
visibility: parseVisibility(resolvedOptions.visibility),
|
|
171
204
|
kind: "secret",
|
|
172
|
-
declaredType:
|
|
173
|
-
value,
|
|
205
|
+
declaredType: resolvedOptions.type ?? "string",
|
|
206
|
+
value: resolvedValue,
|
|
174
207
|
};
|
|
175
208
|
const result = await postJson({
|
|
176
209
|
baseUrl: local.baseUrl,
|
|
@@ -184,18 +217,17 @@ async function runEnvWrite(operation, name, value, options) {
|
|
|
184
217
|
entries: [entry],
|
|
185
218
|
deletes: [],
|
|
186
219
|
},
|
|
220
|
+
schema: EnvWriteResponseSchema,
|
|
187
221
|
});
|
|
188
|
-
if (
|
|
222
|
+
if (resolvedOptions.json) {
|
|
189
223
|
toJsonOutput(true, result);
|
|
190
224
|
return;
|
|
191
225
|
}
|
|
192
226
|
console.log(`Created: ${result.createdCount}, Updated: ${result.updatedCount}, Deleted: ${result.deletedCount}`);
|
|
193
227
|
}
|
|
194
228
|
async function runEnvDelete(name, options) {
|
|
195
|
-
const
|
|
196
|
-
const target = await
|
|
197
|
-
const authProvider = createCliAuthProvider();
|
|
198
|
-
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);
|
|
199
231
|
if (!options.ignoreMissing) {
|
|
200
232
|
const listed = await postJson({
|
|
201
233
|
baseUrl: local.baseUrl,
|
|
@@ -206,10 +238,11 @@ async function runEnvDelete(name, options) {
|
|
|
206
238
|
projectSlug: target.projectSlug,
|
|
207
239
|
stageSlug: target.stageSlug,
|
|
208
240
|
},
|
|
241
|
+
schema: EnvListResponseSchema,
|
|
209
242
|
});
|
|
210
|
-
const exists = listed.variables.some((row) => row.name ===
|
|
243
|
+
const exists = listed.variables.some((row) => row.name === resolvedName);
|
|
211
244
|
if (!exists) {
|
|
212
|
-
throw new Error(`Variable ${
|
|
245
|
+
throw new Error(`Variable ${resolvedName} was not found in this stage.`);
|
|
213
246
|
}
|
|
214
247
|
}
|
|
215
248
|
if (!options.yes) {
|
|
@@ -217,7 +250,7 @@ async function runEnvDelete(name, options) {
|
|
|
217
250
|
throw new Error("Deletion requires --yes in non-interactive mode.");
|
|
218
251
|
}
|
|
219
252
|
const confirmed = await confirm({
|
|
220
|
-
message: `Delete variable ${
|
|
253
|
+
message: `Delete variable ${resolvedName}?`,
|
|
221
254
|
initialValue: false,
|
|
222
255
|
});
|
|
223
256
|
if (isCancel(confirmed)) {
|
|
@@ -238,8 +271,9 @@ async function runEnvDelete(name, options) {
|
|
|
238
271
|
stageSlug: target.stageSlug,
|
|
239
272
|
mode: "upsert",
|
|
240
273
|
entries: [],
|
|
241
|
-
deletes: [
|
|
274
|
+
deletes: [resolvedName],
|
|
242
275
|
},
|
|
276
|
+
schema: EnvWriteResponseSchema,
|
|
243
277
|
});
|
|
244
278
|
if (options.json) {
|
|
245
279
|
toJsonOutput(true, result);
|
|
@@ -248,10 +282,7 @@ async function runEnvDelete(name, options) {
|
|
|
248
282
|
console.log(`Deleted: ${result.deletedCount}`);
|
|
249
283
|
}
|
|
250
284
|
async function runEnvPull(options) {
|
|
251
|
-
const local = await
|
|
252
|
-
const target = await resolveTarget(options, local);
|
|
253
|
-
const authProvider = createCliAuthProvider();
|
|
254
|
-
const accessToken = await authProvider.getAccessToken();
|
|
285
|
+
const { local, target, accessToken } = await resolveEnvAccess(options);
|
|
255
286
|
const response = await postJson({
|
|
256
287
|
baseUrl: local.baseUrl,
|
|
257
288
|
path: "/v1/env/pull",
|
|
@@ -263,6 +294,7 @@ async function runEnvPull(options) {
|
|
|
263
294
|
seed: options.seed,
|
|
264
295
|
key: options.key,
|
|
265
296
|
},
|
|
297
|
+
schema: EnvPullResponseSchema,
|
|
266
298
|
});
|
|
267
299
|
const format = options.format ?? "dotenv";
|
|
268
300
|
const sortedKeys = Object.keys(response.byName).sort((left, right) => left.localeCompare(right));
|
|
@@ -285,7 +317,7 @@ export function registerEnvCommands(program) {
|
|
|
285
317
|
addTargetOptions(env
|
|
286
318
|
.command("get")
|
|
287
319
|
.description("Evaluate one variable")
|
|
288
|
-
.argument("
|
|
320
|
+
.argument("[name]", "Variable name")
|
|
289
321
|
.option("--seed <value>", "Deterministic seed")
|
|
290
322
|
.option("--key <value>", "Deterministic key")
|
|
291
323
|
.option("--json", "Machine-readable output", false)).action(async (name, options) => {
|
|
@@ -294,7 +326,7 @@ export function registerEnvCommands(program) {
|
|
|
294
326
|
addTargetOptions(env
|
|
295
327
|
.command("get-many")
|
|
296
328
|
.description("Evaluate a batch of variables")
|
|
297
|
-
.
|
|
329
|
+
.option("--names <csv>", "Comma-separated variable names")
|
|
298
330
|
.option("--seed <value>", "Deterministic seed")
|
|
299
331
|
.option("--key <value>", "Deterministic key")
|
|
300
332
|
.option("--json", "Machine-readable output", false)).action(async (options) => {
|
|
@@ -309,8 +341,8 @@ export function registerEnvCommands(program) {
|
|
|
309
341
|
addTargetOptions(env
|
|
310
342
|
.command("new")
|
|
311
343
|
.description("Create one variable")
|
|
312
|
-
.argument("
|
|
313
|
-
.argument("
|
|
344
|
+
.argument("[name]", "Variable name")
|
|
345
|
+
.argument("[value]", "Variable value")
|
|
314
346
|
.option("--ab <value-b>", "Second value for ab_roll")
|
|
315
347
|
.option("--rollout <value-b>", "Second value for rollout")
|
|
316
348
|
.option("--chance <number>", "A-branch probability between 0 and 1")
|
|
@@ -324,8 +356,8 @@ export function registerEnvCommands(program) {
|
|
|
324
356
|
addTargetOptions(env
|
|
325
357
|
.command("set")
|
|
326
358
|
.description("Upsert one variable")
|
|
327
|
-
.argument("
|
|
328
|
-
.argument("
|
|
359
|
+
.argument("[name]", "Variable name")
|
|
360
|
+
.argument("[value]", "Variable value")
|
|
329
361
|
.option("--ab <value-b>", "Second value for ab_roll")
|
|
330
362
|
.option("--rollout <value-b>", "Second value for rollout")
|
|
331
363
|
.option("--chance <number>", "A-branch probability between 0 and 1")
|
|
@@ -339,7 +371,7 @@ export function registerEnvCommands(program) {
|
|
|
339
371
|
addTargetOptions(env
|
|
340
372
|
.command("delete")
|
|
341
373
|
.description("Delete one variable")
|
|
342
|
-
.argument("
|
|
374
|
+
.argument("[name]", "Variable name")
|
|
343
375
|
.option("--yes", "Skip confirmation", false)
|
|
344
376
|
.option("--ignore-missing", "Do not fail when variable is missing", false)
|
|
345
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
|
+
}
|