@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.
Files changed (63) hide show
  1. package/README.md +53 -12
  2. package/bun.lock +9 -3
  3. package/dist/auth-provider.js +7 -4
  4. package/dist/command-utils.d.ts +1 -1
  5. package/dist/command-utils.js +7 -6
  6. package/dist/commands/audit.d.ts +2 -0
  7. package/dist/commands/audit.js +47 -0
  8. package/dist/commands/auth.js +22 -7
  9. package/dist/commands/billing.d.ts +2 -0
  10. package/dist/commands/billing.js +62 -0
  11. package/dist/commands/env.js +158 -98
  12. package/dist/commands/init.d.ts +2 -0
  13. package/dist/commands/init.js +32 -0
  14. package/dist/commands/org.d.ts +2 -0
  15. package/dist/commands/org.js +85 -0
  16. package/dist/commands/project.d.ts +2 -0
  17. package/dist/commands/project.js +99 -0
  18. package/dist/commands/stage.d.ts +2 -0
  19. package/dist/commands/stage.js +125 -0
  20. package/dist/commands/target-prompts.d.ts +184 -0
  21. package/dist/commands/target-prompts.js +312 -0
  22. package/dist/commands/typegen.d.ts +2 -2
  23. package/dist/commands/typegen.js +61 -22
  24. package/dist/constants.d.ts +1 -1
  25. package/dist/constants.js +1 -1
  26. package/dist/context/session-id.d.ts +11 -0
  27. package/dist/context/session-id.js +14 -0
  28. package/dist/contracts/index.d.ts +491 -0
  29. package/dist/contracts/index.js +307 -0
  30. package/dist/credentials-store.js +70 -11
  31. package/dist/http.d.ts +34 -0
  32. package/dist/http.js +56 -2
  33. package/dist/index.js +12 -0
  34. package/dist/runtime-config.d.ts +4 -0
  35. package/dist/runtime-config.js +16 -11
  36. package/dist/typegen/core.d.ts +45 -0
  37. package/dist/typegen/core.js +219 -0
  38. package/dist/types.d.ts +5 -3
  39. package/package.json +2 -2
  40. package/src/auth-provider.ts +8 -5
  41. package/src/command-utils.ts +8 -7
  42. package/src/commands/audit.ts +63 -0
  43. package/src/commands/auth.ts +32 -37
  44. package/src/commands/billing.ts +73 -0
  45. package/src/commands/env.ts +215 -189
  46. package/src/commands/init.ts +47 -0
  47. package/src/commands/org.ts +104 -0
  48. package/src/commands/project.ts +130 -0
  49. package/src/commands/stage.ts +167 -0
  50. package/src/commands/target-prompts.ts +357 -0
  51. package/src/commands/typegen.ts +75 -29
  52. package/src/constants.ts +1 -1
  53. package/src/context/session-id.ts +14 -0
  54. package/src/contracts/index.ts +370 -0
  55. package/src/credentials-store.ts +86 -12
  56. package/src/http.ts +78 -2
  57. package/src/index.ts +12 -0
  58. package/src/runtime-config.ts +25 -14
  59. package/src/typegen/core.ts +311 -0
  60. package/src/types.ts +5 -3
  61. package/test/command-utils.test.ts +47 -0
  62. package/test/credentials-store.test.ts +40 -0
  63. package/test/runtime-config.test.ts +125 -0
@@ -1,9 +1,7 @@
1
1
  import { writeFile } from "node:fs/promises";
2
2
 
3
- import { cancel, confirm, isCancel } from "@clack/prompts";
4
- import { BarekeyClient } from "@barekey/sdk/server";
3
+ import { cancel, confirm, isCancel, select, text } from "@clack/prompts";
5
4
  import { Command } from "commander";
6
- import pc from "picocolors";
7
5
 
8
6
  import { createCliAuthProvider } from "../auth-provider.js";
9
7
  import {
@@ -15,6 +13,13 @@ import {
15
13
  toJsonOutput,
16
14
  type EnvTargetOptions,
17
15
  } from "../command-utils.js";
16
+ import {
17
+ EnvEvaluateBatchResponseSchema,
18
+ EnvEvaluateResponseSchema,
19
+ EnvListResponseSchema,
20
+ EnvPullResponseSchema,
21
+ EnvWriteResponseSchema,
22
+ } from "../contracts/index.js";
18
23
  import { postJson } from "../http.js";
19
24
  import {
20
25
  collectOptionValues,
@@ -25,116 +30,184 @@ import {
25
30
  type CliVisibility,
26
31
  } from "./env-helpers.js";
27
32
 
28
- function createEnvClient(input: {
29
- organization: string | undefined;
30
- project: string;
31
- environment: string;
32
- }): BarekeyClient {
33
- const organization = input.organization?.trim();
34
- if (!organization) {
35
- throw new Error("Organization slug is required.");
33
+ type EnvWriteOptions = EnvTargetOptions & {
34
+ ab?: string;
35
+ rollout?: string;
36
+ function?: string;
37
+ point?: Array<string>;
38
+ chance?: string;
39
+ visibility?: CliVisibility;
40
+ type?: "string" | "boolean" | "int64" | "float" | "date" | "json";
41
+ json?: boolean;
42
+ };
43
+
44
+ async function resolveEnvAccess(options: EnvTargetOptions) {
45
+ const local = await requireLocalSession();
46
+ const target = await resolveTarget(options, local);
47
+ const authProvider = createCliAuthProvider();
48
+ const accessToken = await authProvider.getAccessToken();
49
+ return {
50
+ local,
51
+ target,
52
+ accessToken,
53
+ };
54
+ }
55
+
56
+ function renderScalar(value: unknown): string {
57
+ if (typeof value === "string") {
58
+ return value;
59
+ }
60
+ return JSON.stringify(value);
61
+ }
62
+
63
+ async function promptForRequiredText(
64
+ currentValue: string | undefined,
65
+ message: string,
66
+ ): Promise<string> {
67
+ const existing = currentValue?.trim();
68
+ if (existing && existing.length > 0) {
69
+ return existing;
70
+ }
71
+ if (!process.stdout.isTTY) {
72
+ throw new Error(`${message} is required in non-interactive mode.`);
36
73
  }
37
74
 
38
- return new BarekeyClient({
39
- organization,
40
- project: input.project,
41
- environment: input.environment,
75
+ const prompted = await text({
76
+ message,
77
+ validate(value) {
78
+ return value.trim().length > 0 ? undefined : "This value is required.";
79
+ },
42
80
  });
81
+ if (isCancel(prompted)) {
82
+ cancel("Command canceled.");
83
+ process.exit(0);
84
+ }
85
+ return prompted.trim();
86
+ }
87
+
88
+ async function maybePromptVariant(writeOptions: EnvWriteOptions): Promise<EnvWriteOptions> {
89
+ if (writeOptions.ab !== undefined || writeOptions.rollout !== undefined || !process.stdout.isTTY) {
90
+ return writeOptions;
91
+ }
92
+
93
+ const selectedKind = await select({
94
+ message: "What kind of variable is this?",
95
+ options: [
96
+ { value: "secret", label: "Secret", hint: "One stored value" },
97
+ { value: "ab_roll", label: "A/B roll", hint: "Two values plus traffic split" },
98
+ { value: "rollout", label: "Rollout", hint: "Two values plus rollout milestones" },
99
+ ],
100
+ initialValue: "secret",
101
+ });
102
+ if (isCancel(selectedKind)) {
103
+ cancel("Command canceled.");
104
+ process.exit(0);
105
+ }
106
+
107
+ if (selectedKind === "ab_roll") {
108
+ const valueB = await promptForRequiredText(undefined, "What's the alternate value?");
109
+ const chance = await promptForRequiredText(
110
+ writeOptions.chance,
111
+ "What's the A-branch probability between 0 and 1?",
112
+ );
113
+ return {
114
+ ...writeOptions,
115
+ ab: valueB,
116
+ chance,
117
+ };
118
+ }
119
+
120
+ if (selectedKind === "rollout") {
121
+ const valueB = await promptForRequiredText(undefined, "What's the rollout value B?");
122
+ return {
123
+ ...writeOptions,
124
+ rollout: valueB,
125
+ };
126
+ }
127
+
128
+ return writeOptions;
43
129
  }
44
130
 
45
131
  async function runEnvGet(
46
- name: string,
132
+ name: string | undefined,
47
133
  options: EnvTargetOptions & {
48
134
  seed?: string;
49
135
  key?: string;
50
136
  json?: boolean;
51
137
  },
52
138
  ): Promise<void> {
53
- const local = await requireLocalSession();
54
- const target = await resolveTarget(options, local);
55
- const client = createEnvClient({
56
- organization: target.orgSlug ?? local.credentials.orgSlug,
57
- project: target.projectSlug,
58
- environment: target.stageSlug,
59
- });
60
- const value = await client.get(name, {
61
- seed: options.seed,
62
- key: options.key,
139
+ const resolvedName = await promptForRequiredText(name, "What's the name of this variable?");
140
+ const { local, target, accessToken } = await resolveEnvAccess(options);
141
+ const value = await postJson({
142
+ baseUrl: local.baseUrl,
143
+ path: "/v1/env/evaluate",
144
+ accessToken,
145
+ payload: {
146
+ orgSlug: target.orgSlug,
147
+ projectSlug: target.projectSlug,
148
+ stageSlug: target.stageSlug,
149
+ name: resolvedName,
150
+ seed: options.seed,
151
+ key: options.key,
152
+ },
153
+ schema: EnvEvaluateResponseSchema,
63
154
  });
64
155
 
65
156
  if (options.json) {
66
- toJsonOutput(true, {
67
- name,
68
- value,
69
- });
157
+ toJsonOutput(true, value);
70
158
  return;
71
159
  }
72
160
 
73
- console.log(String(value));
161
+ console.log(renderScalar(value.value));
74
162
  }
75
163
 
76
164
  async function runEnvGetMany(
77
165
  options: EnvTargetOptions & {
78
- names: string;
166
+ names?: string;
79
167
  seed?: string;
80
168
  key?: string;
81
169
  json?: boolean;
82
170
  },
83
171
  ): Promise<void> {
84
- const local = await requireLocalSession();
85
- const target = await resolveTarget(options, local);
86
- const client = createEnvClient({
87
- organization: target.orgSlug ?? local.credentials.orgSlug,
88
- project: target.projectSlug,
89
- environment: target.stageSlug,
90
- });
172
+ const namesCsv = await promptForRequiredText(
173
+ options.names,
174
+ "Which variable names do you want, comma-separated?",
175
+ );
176
+ const { local, target, accessToken } = await resolveEnvAccess(options);
91
177
 
92
- const names = options.names
178
+ const names = namesCsv
93
179
  .split(",")
94
180
  .map((value) => value.trim())
95
181
  .filter((value) => value.length > 0);
96
182
 
97
- const resolved = await Promise.all(
98
- names.map(async (resolvedName) => ({
99
- name: resolvedName,
100
- value: await client.get(resolvedName, {
101
- seed: options.seed,
102
- key: options.key,
103
- }),
104
- })),
105
- );
183
+ const response = await postJson({
184
+ baseUrl: local.baseUrl,
185
+ path: "/v1/env/evaluate-batch",
186
+ accessToken,
187
+ payload: {
188
+ orgSlug: target.orgSlug,
189
+ projectSlug: target.projectSlug,
190
+ stageSlug: target.stageSlug,
191
+ names,
192
+ seed: options.seed,
193
+ key: options.key,
194
+ },
195
+ schema: EnvEvaluateBatchResponseSchema,
196
+ });
106
197
 
107
198
  if (options.json) {
108
- toJsonOutput(true, resolved);
199
+ toJsonOutput(true, response.values);
109
200
  return;
110
201
  }
111
202
 
112
- for (const value of resolved.sort((left, right) => left.name.localeCompare(right.name))) {
113
- if (value) {
114
- console.log(`${value.name}=${String(value.value)}`);
115
- }
203
+ for (const value of [...response.values].sort((left, right) => left.name.localeCompare(right.name))) {
204
+ console.log(`${value.name}=${renderScalar(value.value)}`);
116
205
  }
117
206
  }
118
207
 
119
208
  async function runEnvList(options: EnvTargetOptions & { json?: boolean }): Promise<void> {
120
- const local = await requireLocalSession();
121
- const target = await resolveTarget(options, local);
122
- const authProvider = createCliAuthProvider();
123
- const accessToken = await authProvider.getAccessToken();
124
-
125
- const response = await postJson<{
126
- variables: Array<{
127
- name: string;
128
- visibility: CliVisibility;
129
- kind: "secret" | "ab_roll" | "rollout";
130
- declaredType: "string" | "boolean" | "int64" | "float" | "date" | "json";
131
- createdAtMs: number;
132
- updatedAtMs: number;
133
- chance: number | null;
134
- rolloutFunction: CliRolloutFunction | null;
135
- rolloutMilestones: Array<{ at: string; percentage: number }> | null;
136
- }>;
137
- }>({
209
+ const { local, target, accessToken } = await resolveEnvAccess(options);
210
+ const response = await postJson({
138
211
  baseUrl: local.baseUrl,
139
212
  path: "/v1/env/list",
140
213
  accessToken,
@@ -143,6 +216,7 @@ async function runEnvList(options: EnvTargetOptions & { json?: boolean }): Promi
143
216
  projectSlug: target.projectSlug,
144
217
  stageSlug: target.stageSlug,
145
218
  },
219
+ schema: EnvListResponseSchema,
146
220
  });
147
221
 
148
222
  if (options.json) {
@@ -159,85 +233,73 @@ async function runEnvList(options: EnvTargetOptions & { json?: boolean }): Promi
159
233
  const chanceSuffix = row.kind === "ab_roll" ? ` chance=${row.chance ?? 0}` : "";
160
234
  const rolloutSuffix =
161
235
  row.kind === "rollout"
162
- ? ` ${pc.dim(
163
- `${row.rolloutFunction ?? "linear"}(${row.rolloutMilestones?.length ?? 0} milestones)`,
164
- )}`
236
+ ? ` ${row.rolloutFunction ?? "linear"}(${row.rolloutMilestones?.length ?? 0} milestones)`
165
237
  : "";
166
238
  console.log(
167
- `${row.name} ${pc.dim(row.visibility)} ${pc.dim(row.kind)} ${pc.dim(row.declaredType)}${chanceSuffix}${rolloutSuffix}`,
239
+ `${row.name} ${row.visibility} ${row.kind} ${row.declaredType}${chanceSuffix}${rolloutSuffix}`,
168
240
  );
169
241
  }
170
242
  }
171
243
 
172
244
  async function runEnvWrite(
173
245
  operation: "create_only" | "upsert",
174
- name: string,
175
- value: string,
176
- options: EnvTargetOptions & {
177
- ab?: string;
178
- rollout?: string;
179
- function?: string;
180
- point?: Array<string>;
181
- chance?: string;
182
- visibility?: CliVisibility;
183
- type?: "string" | "boolean" | "int64" | "float" | "date" | "json";
184
- json?: boolean;
185
- },
246
+ name: string | undefined,
247
+ value: string | undefined,
248
+ options: EnvWriteOptions,
186
249
  ): Promise<void> {
187
- const local = await requireLocalSession();
188
- const target = await resolveTarget(options, local);
189
- const authProvider = createCliAuthProvider();
190
- const accessToken = await authProvider.getAccessToken();
250
+ const resolvedName = await promptForRequiredText(name, "What's the name of this variable?");
251
+ const resolvedValue = await promptForRequiredText(value, "What's the value of this variable?");
252
+ const resolvedOptions = await maybePromptVariant(options);
253
+ const { local, target, accessToken } = await resolveEnvAccess(resolvedOptions);
191
254
 
192
- if (options.ab !== undefined && options.rollout !== undefined) {
255
+ if (resolvedOptions.ab !== undefined && resolvedOptions.rollout !== undefined) {
193
256
  throw new Error("Use either --ab or --rollout, not both.");
194
257
  }
195
- const hasRolloutPoints = (options.point?.length ?? 0) > 0;
196
- if (options.rollout === undefined && (options.function !== undefined || hasRolloutPoints)) {
258
+ const hasRolloutPoints = (resolvedOptions.point?.length ?? 0) > 0;
259
+ if (
260
+ resolvedOptions.rollout === undefined &&
261
+ (resolvedOptions.function !== undefined || hasRolloutPoints)
262
+ ) {
197
263
  throw new Error("--function and --point can only be used together with --rollout.");
198
264
  }
199
- if (options.ab !== undefined && (options.function !== undefined || hasRolloutPoints)) {
265
+ if (resolvedOptions.ab !== undefined && (resolvedOptions.function !== undefined || hasRolloutPoints)) {
200
266
  throw new Error("--function and --point are only supported for --rollout, not --ab.");
201
267
  }
202
- if (options.rollout !== undefined && options.chance !== undefined) {
268
+ if (resolvedOptions.rollout !== undefined && resolvedOptions.chance !== undefined) {
203
269
  throw new Error("--chance only applies to --ab.");
204
270
  }
205
271
 
206
272
  const entry =
207
- options.rollout !== undefined
273
+ resolvedOptions.rollout !== undefined
208
274
  ? {
209
- name,
210
- visibility: parseVisibility(options.visibility),
275
+ name: resolvedName,
276
+ visibility: parseVisibility(resolvedOptions.visibility),
211
277
  kind: "rollout" as const,
212
- declaredType: options.type ?? "string",
213
- valueA: value,
214
- valueB: options.rollout,
215
- rolloutFunction: parseRolloutFunction(options.function),
216
- rolloutMilestones: parseRolloutMilestones(options.point),
278
+ declaredType: resolvedOptions.type ?? "string",
279
+ valueA: resolvedValue,
280
+ valueB: resolvedOptions.rollout,
281
+ rolloutFunction: parseRolloutFunction(resolvedOptions.function),
282
+ rolloutMilestones: parseRolloutMilestones(resolvedOptions.point),
217
283
  }
218
- : options.ab !== undefined
284
+ : resolvedOptions.ab !== undefined
219
285
  ? {
220
- name,
221
- visibility: parseVisibility(options.visibility),
286
+ name: resolvedName,
287
+ visibility: parseVisibility(resolvedOptions.visibility),
222
288
  kind: "ab_roll" as const,
223
- declaredType: options.type ?? "string",
224
- valueA: value,
225
- valueB: options.ab,
226
- chance: parseChance(options.chance),
289
+ declaredType: resolvedOptions.type ?? "string",
290
+ valueA: resolvedValue,
291
+ valueB: resolvedOptions.ab,
292
+ chance: parseChance(resolvedOptions.chance),
227
293
  }
228
294
  : {
229
- name,
230
- visibility: parseVisibility(options.visibility),
295
+ name: resolvedName,
296
+ visibility: parseVisibility(resolvedOptions.visibility),
231
297
  kind: "secret" as const,
232
- declaredType: options.type ?? "string",
233
- value,
298
+ declaredType: resolvedOptions.type ?? "string",
299
+ value: resolvedValue,
234
300
  };
235
301
 
236
- const result = await postJson<{
237
- createdCount: number;
238
- updatedCount: number;
239
- deletedCount: number;
240
- }>({
302
+ const result = await postJson({
241
303
  baseUrl: local.baseUrl,
242
304
  path: "/v1/env/write",
243
305
  accessToken,
@@ -249,9 +311,10 @@ async function runEnvWrite(
249
311
  entries: [entry],
250
312
  deletes: [],
251
313
  },
314
+ schema: EnvWriteResponseSchema,
252
315
  });
253
316
 
254
- if (options.json) {
317
+ if (resolvedOptions.json) {
255
318
  toJsonOutput(true, result);
256
319
  return;
257
320
  }
@@ -262,24 +325,18 @@ async function runEnvWrite(
262
325
  }
263
326
 
264
327
  async function runEnvDelete(
265
- name: string,
328
+ name: string | undefined,
266
329
  options: EnvTargetOptions & {
267
330
  yes?: boolean;
268
331
  ignoreMissing?: boolean;
269
332
  json?: boolean;
270
333
  },
271
334
  ): Promise<void> {
272
- const local = await requireLocalSession();
273
- const target = await resolveTarget(options, local);
274
- const authProvider = createCliAuthProvider();
275
- const accessToken = await authProvider.getAccessToken();
335
+ const resolvedName = await promptForRequiredText(name, "What's the name of the variable to delete?");
336
+ const { local, target, accessToken } = await resolveEnvAccess(options);
276
337
 
277
338
  if (!options.ignoreMissing) {
278
- const listed = await postJson<{
279
- variables: Array<{
280
- name: string;
281
- }>;
282
- }>({
339
+ const listed = await postJson({
283
340
  baseUrl: local.baseUrl,
284
341
  path: "/v1/env/list",
285
342
  accessToken,
@@ -288,10 +345,11 @@ async function runEnvDelete(
288
345
  projectSlug: target.projectSlug,
289
346
  stageSlug: target.stageSlug,
290
347
  },
348
+ schema: EnvListResponseSchema,
291
349
  });
292
- const exists = listed.variables.some((row) => row.name === name);
350
+ const exists = listed.variables.some((row) => row.name === resolvedName);
293
351
  if (!exists) {
294
- throw new Error(`Variable ${name} was not found in this stage.`);
352
+ throw new Error(`Variable ${resolvedName} was not found in this stage.`);
295
353
  }
296
354
  }
297
355
 
@@ -300,7 +358,7 @@ async function runEnvDelete(
300
358
  throw new Error("Deletion requires --yes in non-interactive mode.");
301
359
  }
302
360
  const confirmed = await confirm({
303
- message: `Delete variable ${name}?`,
361
+ message: `Delete variable ${resolvedName}?`,
304
362
  initialValue: false,
305
363
  });
306
364
  if (isCancel(confirmed)) {
@@ -312,11 +370,7 @@ async function runEnvDelete(
312
370
  }
313
371
  }
314
372
 
315
- const result = await postJson<{
316
- createdCount: number;
317
- updatedCount: number;
318
- deletedCount: number;
319
- }>({
373
+ const result = await postJson({
320
374
  baseUrl: local.baseUrl,
321
375
  path: "/v1/env/write",
322
376
  accessToken,
@@ -326,8 +380,9 @@ async function runEnvDelete(
326
380
  stageSlug: target.stageSlug,
327
381
  mode: "upsert",
328
382
  entries: [],
329
- deletes: [name],
383
+ deletes: [resolvedName],
330
384
  },
385
+ schema: EnvWriteResponseSchema,
331
386
  });
332
387
 
333
388
  if (options.json) {
@@ -347,20 +402,8 @@ async function runEnvPull(
347
402
  redact?: boolean;
348
403
  },
349
404
  ): Promise<void> {
350
- const local = await requireLocalSession();
351
- const target = await resolveTarget(options, local);
352
- const authProvider = createCliAuthProvider();
353
- const accessToken = await authProvider.getAccessToken();
354
-
355
- const response = await postJson<{
356
- values: Array<{
357
- name: string;
358
- kind: "secret" | "ab_roll" | "rollout";
359
- declaredType: "string" | "boolean" | "int64" | "float" | "date" | "json";
360
- value: string;
361
- }>;
362
- byName: Record<string, string>;
363
- }>({
405
+ const { local, target, accessToken } = await resolveEnvAccess(options);
406
+ const response = await postJson({
364
407
  baseUrl: local.baseUrl,
365
408
  path: "/v1/env/pull",
366
409
  accessToken,
@@ -371,6 +414,7 @@ async function runEnvPull(
371
414
  seed: options.seed,
372
415
  key: options.key,
373
416
  },
417
+ schema: EnvPullResponseSchema,
374
418
  });
375
419
 
376
420
  const format = options.format ?? "dotenv";
@@ -401,13 +445,13 @@ export function registerEnvCommands(program: Command): void {
401
445
  env
402
446
  .command("get")
403
447
  .description("Evaluate one variable")
404
- .argument("<name>", "Variable name")
448
+ .argument("[name]", "Variable name")
405
449
  .option("--seed <value>", "Deterministic seed")
406
450
  .option("--key <value>", "Deterministic key")
407
451
  .option("--json", "Machine-readable output", false),
408
452
  ).action(
409
453
  async (
410
- name: string,
454
+ name: string | undefined,
411
455
  options: EnvTargetOptions & { seed?: string; key?: string; json?: boolean },
412
456
  ) => {
413
457
  await runEnvGet(name, options);
@@ -418,14 +462,14 @@ export function registerEnvCommands(program: Command): void {
418
462
  env
419
463
  .command("get-many")
420
464
  .description("Evaluate a batch of variables")
421
- .requiredOption("--names <csv>", "Comma-separated variable names")
465
+ .option("--names <csv>", "Comma-separated variable names")
422
466
  .option("--seed <value>", "Deterministic seed")
423
467
  .option("--key <value>", "Deterministic key")
424
468
  .option("--json", "Machine-readable output", false),
425
469
  ).action(
426
470
  async (
427
471
  options: EnvTargetOptions & {
428
- names: string;
472
+ names?: string;
429
473
  seed?: string;
430
474
  key?: string;
431
475
  json?: boolean;
@@ -448,8 +492,8 @@ export function registerEnvCommands(program: Command): void {
448
492
  env
449
493
  .command("new")
450
494
  .description("Create one variable")
451
- .argument("<name>", "Variable name")
452
- .argument("<value>", "Variable value")
495
+ .argument("[name]", "Variable name")
496
+ .argument("[value]", "Variable value")
453
497
  .option("--ab <value-b>", "Second value for ab_roll")
454
498
  .option("--rollout <value-b>", "Second value for rollout")
455
499
  .option("--chance <number>", "A-branch probability between 0 and 1")
@@ -468,18 +512,9 @@ export function registerEnvCommands(program: Command): void {
468
512
  .option("--json", "Machine-readable output", false),
469
513
  ).action(
470
514
  async (
471
- name: string,
472
- value: string,
473
- options: EnvTargetOptions & {
474
- ab?: string;
475
- rollout?: string;
476
- function?: string;
477
- point?: Array<string>;
478
- chance?: string;
479
- visibility?: CliVisibility;
480
- type?: "string" | "boolean" | "int64" | "float" | "date" | "json";
481
- json?: boolean;
482
- },
515
+ name: string | undefined,
516
+ value: string | undefined,
517
+ options: EnvWriteOptions,
483
518
  ) => {
484
519
  await runEnvWrite("create_only", name, value, options);
485
520
  },
@@ -489,8 +524,8 @@ export function registerEnvCommands(program: Command): void {
489
524
  env
490
525
  .command("set")
491
526
  .description("Upsert one variable")
492
- .argument("<name>", "Variable name")
493
- .argument("<value>", "Variable value")
527
+ .argument("[name]", "Variable name")
528
+ .argument("[value]", "Variable value")
494
529
  .option("--ab <value-b>", "Second value for ab_roll")
495
530
  .option("--rollout <value-b>", "Second value for rollout")
496
531
  .option("--chance <number>", "A-branch probability between 0 and 1")
@@ -509,18 +544,9 @@ export function registerEnvCommands(program: Command): void {
509
544
  .option("--json", "Machine-readable output", false),
510
545
  ).action(
511
546
  async (
512
- name: string,
513
- value: string,
514
- options: EnvTargetOptions & {
515
- ab?: string;
516
- rollout?: string;
517
- function?: string;
518
- point?: Array<string>;
519
- chance?: string;
520
- visibility?: CliVisibility;
521
- type?: "string" | "boolean" | "int64" | "float" | "date" | "json";
522
- json?: boolean;
523
- },
547
+ name: string | undefined,
548
+ value: string | undefined,
549
+ options: EnvWriteOptions,
524
550
  ) => {
525
551
  await runEnvWrite("upsert", name, value, options);
526
552
  },
@@ -530,13 +556,13 @@ export function registerEnvCommands(program: Command): void {
530
556
  env
531
557
  .command("delete")
532
558
  .description("Delete one variable")
533
- .argument("<name>", "Variable name")
559
+ .argument("[name]", "Variable name")
534
560
  .option("--yes", "Skip confirmation", false)
535
561
  .option("--ignore-missing", "Do not fail when variable is missing", false)
536
562
  .option("--json", "Machine-readable output", false),
537
563
  ).action(
538
564
  async (
539
- name: string,
565
+ name: string | undefined,
540
566
  options: EnvTargetOptions & { yes?: boolean; ignoreMissing?: boolean; json?: boolean },
541
567
  ) => {
542
568
  await runEnvDelete(name, options);
@@ -0,0 +1,47 @@
1
+ import { writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ import { Command } from "commander";
5
+
6
+ import { type EnvTargetOptions } from "../command-utils.js";
7
+ import {
8
+ promptForOrganizationSlug,
9
+ promptForProjectSlug,
10
+ promptForStageSlug,
11
+ } from "./target-prompts.js";
12
+
13
+ async function runInit(options: EnvTargetOptions & { path?: string }) {
14
+ const orgSlug = await promptForOrganizationSlug(options.org);
15
+ const projectSlug = await promptForProjectSlug(orgSlug, options.project);
16
+ const stageSlug = await promptForStageSlug(orgSlug, projectSlug, options.stage);
17
+ const configPath = path.resolve(options.path?.trim() || "barekey.json");
18
+ const contents = `${JSON.stringify(
19
+ {
20
+ organization: orgSlug,
21
+ project: projectSlug,
22
+ environment: stageSlug,
23
+ config: {
24
+ mode: "centralized",
25
+ typegen: "semantic",
26
+ },
27
+ },
28
+ null,
29
+ 2,
30
+ )}\n`;
31
+
32
+ await writeFile(configPath, contents, "utf8");
33
+ console.log(`Wrote ${configPath}`);
34
+ }
35
+
36
+ export function registerInitCommand(program: Command): void {
37
+ program
38
+ .command("init")
39
+ .description("Create or update barekey.json for the current repo")
40
+ .option("--org <slug>", "Organization slug")
41
+ .option("--project <slug>", "Project slug")
42
+ .option("--stage <slug>", "Stage slug")
43
+ .option("--path <path>", "Config path", "barekey.json")
44
+ .action(async (options: EnvTargetOptions & { path?: string }) => {
45
+ await runInit(options);
46
+ });
47
+ }