@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.
Files changed (60) 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.js +6 -6
  5. package/dist/commands/audit.d.ts +2 -0
  6. package/dist/commands/audit.js +47 -0
  7. package/dist/commands/auth.js +31 -9
  8. package/dist/commands/billing.d.ts +2 -0
  9. package/dist/commands/billing.js +59 -0
  10. package/dist/commands/env.js +157 -125
  11. package/dist/commands/init.d.ts +2 -0
  12. package/dist/commands/init.js +32 -0
  13. package/dist/commands/org.d.ts +2 -0
  14. package/dist/commands/org.js +85 -0
  15. package/dist/commands/project.d.ts +2 -0
  16. package/dist/commands/project.js +99 -0
  17. package/dist/commands/stage.d.ts +2 -0
  18. package/dist/commands/stage.js +125 -0
  19. package/dist/commands/target-prompts.d.ts +184 -0
  20. package/dist/commands/target-prompts.js +312 -0
  21. package/dist/commands/typegen.d.ts +2 -2
  22. package/dist/commands/typegen.js +57 -32
  23. package/dist/constants.d.ts +1 -1
  24. package/dist/constants.js +1 -1
  25. package/dist/context/session-id.d.ts +11 -0
  26. package/dist/context/session-id.js +14 -0
  27. package/dist/contracts/index.d.ts +499 -0
  28. package/dist/contracts/index.js +313 -0
  29. package/dist/credentials-store.js +70 -11
  30. package/dist/http.d.ts +34 -0
  31. package/dist/http.js +56 -2
  32. package/dist/index.js +12 -0
  33. package/dist/runtime-config.js +14 -26
  34. package/dist/typegen/core.d.ts +45 -0
  35. package/dist/typegen/core.js +219 -0
  36. package/dist/types.d.ts +5 -3
  37. package/package.json +2 -2
  38. package/src/auth-provider.ts +8 -5
  39. package/src/command-utils.ts +6 -6
  40. package/src/commands/audit.ts +63 -0
  41. package/src/commands/auth.ts +45 -39
  42. package/src/commands/billing.ts +70 -0
  43. package/src/commands/env.ts +211 -218
  44. package/src/commands/init.ts +47 -0
  45. package/src/commands/org.ts +104 -0
  46. package/src/commands/project.ts +130 -0
  47. package/src/commands/stage.ts +167 -0
  48. package/src/commands/target-prompts.ts +357 -0
  49. package/src/commands/typegen.ts +71 -45
  50. package/src/constants.ts +1 -1
  51. package/src/context/session-id.ts +14 -0
  52. package/src/contracts/index.ts +376 -0
  53. package/src/credentials-store.ts +86 -12
  54. package/src/http.ts +78 -2
  55. package/src/index.ts +12 -0
  56. package/src/runtime-config.ts +19 -32
  57. package/src/typegen/core.ts +311 -0
  58. package/src/types.ts +5 -3
  59. package/test/command-utils.test.ts +47 -0
  60. package/test/credentials-store.test.ts +40 -0
@@ -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
+ }
@@ -0,0 +1,104 @@
1
+ import { text, isCancel, cancel } from "@clack/prompts";
2
+ import { Command } from "commander";
3
+
4
+ import { createCliAuthProvider } from "../auth-provider.js";
5
+ import { requireLocalSession, toJsonOutput } from "../command-utils.js";
6
+ import { OrganizationsResponseSchema } from "../contracts/index.js";
7
+ import { getJson, postJson } from "../http.js";
8
+
9
+ async function promptForName(name: string | undefined): Promise<string> {
10
+ const existing = name?.trim();
11
+ if (existing && existing.length > 0) {
12
+ return existing;
13
+ }
14
+ if (!process.stdout.isTTY) {
15
+ throw new Error("Organization name is required in non-interactive mode.");
16
+ }
17
+ const prompted = await text({
18
+ message: "What’s the name of this organization?",
19
+ validate: (value) => (value.trim().length > 0 ? undefined : "Name is required."),
20
+ });
21
+ if (isCancel(prompted)) {
22
+ cancel("Command canceled.");
23
+ process.exit(0);
24
+ }
25
+ return prompted.trim();
26
+ }
27
+
28
+ async function runOrgList(options: { json?: boolean }) {
29
+ const local = await requireLocalSession();
30
+ const authProvider = createCliAuthProvider();
31
+ const accessToken = await authProvider.getAccessToken();
32
+ const response = await getJson({
33
+ baseUrl: local.baseUrl,
34
+ path: "/v1/cli/orgs",
35
+ accessToken,
36
+ schema: OrganizationsResponseSchema,
37
+ });
38
+
39
+ if (options.json) {
40
+ toJsonOutput(true, response.organizations);
41
+ return;
42
+ }
43
+
44
+ if (response.organizations.length === 0) {
45
+ console.log("No organizations found.");
46
+ return;
47
+ }
48
+
49
+ for (const organization of response.organizations) {
50
+ console.log(`${organization.name} (${organization.slug}) ${organization.role}`);
51
+ }
52
+ }
53
+
54
+ async function runOrgCreate(
55
+ name: string | undefined,
56
+ options: {
57
+ slug?: string;
58
+ json?: boolean;
59
+ },
60
+ ) {
61
+ const resolvedName = await promptForName(name);
62
+ const local = await requireLocalSession();
63
+ const authProvider = createCliAuthProvider();
64
+ const accessToken = await authProvider.getAccessToken();
65
+ const response = await postJson({
66
+ baseUrl: local.baseUrl,
67
+ path: "/v1/cli/orgs/create",
68
+ accessToken,
69
+ payload: {
70
+ name: resolvedName,
71
+ slug: options.slug?.trim() || null,
72
+ },
73
+ });
74
+
75
+ if (options.json) {
76
+ toJsonOutput(true, response);
77
+ return;
78
+ }
79
+
80
+ const organization = (response as { organization: { name: string; slug: string } }).organization;
81
+ console.log(`Created organization ${organization.name} (${organization.slug}).`);
82
+ }
83
+
84
+ export function registerOrgCommands(program: Command): void {
85
+ const org = program.command("org").description("Organization management");
86
+
87
+ org
88
+ .command("list")
89
+ .description("List organizations available to the current user")
90
+ .option("--json", "Machine-readable output", false)
91
+ .action(async (options: { json?: boolean }) => {
92
+ await runOrgList(options);
93
+ });
94
+
95
+ org
96
+ .command("create")
97
+ .description("Create an organization")
98
+ .argument("[name]", "Organization name")
99
+ .option("--slug <slug>", "Organization slug")
100
+ .option("--json", "Machine-readable output", false)
101
+ .action(async (name: string | undefined, options: { slug?: string; json?: boolean }) => {
102
+ await runOrgCreate(name, options);
103
+ });
104
+ }
@@ -0,0 +1,130 @@
1
+ import { cancel, confirm, isCancel, text } from "@clack/prompts";
2
+ import { Command } from "commander";
3
+
4
+ import { toJsonOutput, type EnvTargetOptions } from "../command-utils.js";
5
+ import {
6
+ createProjectForOrganization,
7
+ deleteProjectForOrganization,
8
+ listProjectsForOrganization,
9
+ promptForOrganizationSlug,
10
+ promptForProjectSlug,
11
+ } from "./target-prompts.js";
12
+
13
+ async function promptForProjectName(name: string | undefined): Promise<string> {
14
+ const existing = name?.trim();
15
+ if (existing && existing.length > 0) {
16
+ return existing;
17
+ }
18
+ if (!process.stdout.isTTY) {
19
+ throw new Error("Project name is required in non-interactive mode.");
20
+ }
21
+ const prompted = await text({
22
+ message: "What’s the name of this project?",
23
+ validate: (value) => (value.trim().length > 0 ? undefined : "Project name is required."),
24
+ });
25
+ if (isCancel(prompted)) {
26
+ cancel("Command canceled.");
27
+ process.exit(0);
28
+ }
29
+ return prompted.trim();
30
+ }
31
+
32
+ async function runProjectList(options: EnvTargetOptions & { json?: boolean }) {
33
+ const orgSlug = await promptForOrganizationSlug(options.org);
34
+ const projects = await listProjectsForOrganization(orgSlug);
35
+
36
+ if (options.json) {
37
+ toJsonOutput(true, projects);
38
+ return;
39
+ }
40
+
41
+ if (projects.length === 0) {
42
+ console.log("No projects found.");
43
+ return;
44
+ }
45
+
46
+ for (const project of projects) {
47
+ console.log(`${project.name} (${project.slug}) secrets=${project.secretCount}`);
48
+ }
49
+ }
50
+
51
+ async function runProjectCreate(
52
+ name: string | undefined,
53
+ options: EnvTargetOptions & { json?: boolean },
54
+ ) {
55
+ const orgSlug = await promptForOrganizationSlug(options.org);
56
+ const projectName = await promptForProjectName(name);
57
+ const project = await createProjectForOrganization(orgSlug, projectName);
58
+
59
+ if (options.json) {
60
+ toJsonOutput(true, project);
61
+ return;
62
+ }
63
+
64
+ console.log(`Created project ${project.name} (${project.slug}).`);
65
+ }
66
+
67
+ export function registerProjectCommands(program: Command): void {
68
+ const project = program.command("project").description("Project management");
69
+
70
+ project
71
+ .command("list")
72
+ .description("List projects in an organization")
73
+ .option("--org <slug>", "Organization slug")
74
+ .option("--json", "Machine-readable output", false)
75
+ .action(async (options: EnvTargetOptions & { json?: boolean }) => {
76
+ await runProjectList(options);
77
+ });
78
+
79
+ project
80
+ .command("create")
81
+ .description("Create a project")
82
+ .argument("[name]", "Project name")
83
+ .option("--org <slug>", "Organization slug")
84
+ .option("--json", "Machine-readable output", false)
85
+ .action(async (name: string | undefined, options: EnvTargetOptions & { json?: boolean }) => {
86
+ await runProjectCreate(name, options);
87
+ });
88
+
89
+ project
90
+ .command("delete")
91
+ .description("Delete a project")
92
+ .argument("[slug]", "Project slug")
93
+ .option("--org <slug>", "Organization slug")
94
+ .option("--yes", "Skip confirmation prompt", false)
95
+ .option("--json", "Machine-readable output", false)
96
+ .action(
97
+ async (
98
+ slug: string | undefined,
99
+ options: EnvTargetOptions & { yes?: boolean; json?: boolean },
100
+ ) => {
101
+ const orgSlug = await promptForOrganizationSlug(options.org);
102
+ const projectSlug = await promptForProjectSlug(orgSlug, slug);
103
+
104
+ if (!options.yes) {
105
+ if (!process.stdout.isTTY) {
106
+ throw new Error("Project deletion requires --yes in non-interactive mode.");
107
+ }
108
+ const confirmed = await confirm({
109
+ message: `Delete project ${projectSlug}?`,
110
+ initialValue: false,
111
+ });
112
+ if (isCancel(confirmed)) {
113
+ cancel("Command canceled.");
114
+ process.exit(0);
115
+ }
116
+ if (!confirmed) {
117
+ throw new Error("Delete canceled.");
118
+ }
119
+ }
120
+
121
+ const response = await deleteProjectForOrganization(orgSlug, projectSlug);
122
+ if (options.json) {
123
+ toJsonOutput(true, response);
124
+ return;
125
+ }
126
+
127
+ console.log(`Deleted project ${response.deletedProjectSlug}.`);
128
+ },
129
+ );
130
+ }
@@ -0,0 +1,167 @@
1
+ import { cancel, confirm, isCancel, text } from "@clack/prompts";
2
+ import { Command } from "commander";
3
+
4
+ import { toJsonOutput, type EnvTargetOptions } from "../command-utils.js";
5
+ import {
6
+ createStageForProject,
7
+ deleteStageForProject,
8
+ listStagesForProject,
9
+ promptForOrganizationSlug,
10
+ promptForProjectSlug,
11
+ promptForStageSlug,
12
+ renameStageForProject,
13
+ } from "./target-prompts.js";
14
+
15
+ async function promptForStageName(name: string | undefined): Promise<string> {
16
+ const existing = name?.trim();
17
+ if (existing && existing.length > 0) {
18
+ return existing;
19
+ }
20
+ if (!process.stdout.isTTY) {
21
+ throw new Error("Stage name is required in non-interactive mode.");
22
+ }
23
+ const prompted = await text({
24
+ message: "What’s the name of this stage?",
25
+ validate: (value) => (value.trim().length > 0 ? undefined : "Stage name is required."),
26
+ });
27
+ if (isCancel(prompted)) {
28
+ cancel("Command canceled.");
29
+ process.exit(0);
30
+ }
31
+ return prompted.trim();
32
+ }
33
+
34
+ async function runStageList(options: EnvTargetOptions & { json?: boolean }) {
35
+ const orgSlug = await promptForOrganizationSlug(options.org);
36
+ const projectSlug = await promptForProjectSlug(orgSlug, options.project);
37
+ const stages = await listStagesForProject(orgSlug, projectSlug);
38
+
39
+ if (options.json) {
40
+ toJsonOutput(true, stages);
41
+ return;
42
+ }
43
+
44
+ if (stages.length === 0) {
45
+ console.log("No stages found.");
46
+ return;
47
+ }
48
+
49
+ for (const stage of stages) {
50
+ console.log(`${stage.name} (${stage.slug}) variables=${stage.variableCount}`);
51
+ }
52
+ }
53
+
54
+ async function runStageCreate(
55
+ name: string | undefined,
56
+ options: EnvTargetOptions & { json?: boolean },
57
+ ) {
58
+ const orgSlug = await promptForOrganizationSlug(options.org);
59
+ const projectSlug = await promptForProjectSlug(orgSlug, options.project);
60
+ const stageName = await promptForStageName(name);
61
+ const stage = await createStageForProject(orgSlug, projectSlug, stageName);
62
+
63
+ if (options.json) {
64
+ toJsonOutput(true, stage);
65
+ return;
66
+ }
67
+
68
+ console.log(`Created stage ${stage.name} (${stage.slug}).`);
69
+ }
70
+
71
+ export function registerStageCommands(program: Command): void {
72
+ const stage = program.command("stage").description("Stage management");
73
+
74
+ stage
75
+ .command("list")
76
+ .description("List stages in a project")
77
+ .option("--org <slug>", "Organization slug")
78
+ .option("--project <slug>", "Project slug")
79
+ .option("--json", "Machine-readable output", false)
80
+ .action(async (options: EnvTargetOptions & { json?: boolean }) => {
81
+ await runStageList(options);
82
+ });
83
+
84
+ stage
85
+ .command("create")
86
+ .description("Create a stage")
87
+ .argument("[name]", "Stage name")
88
+ .option("--org <slug>", "Organization slug")
89
+ .option("--project <slug>", "Project slug")
90
+ .option("--json", "Machine-readable output", false)
91
+ .action(async (name: string | undefined, options: EnvTargetOptions & { json?: boolean }) => {
92
+ await runStageCreate(name, options);
93
+ });
94
+
95
+ stage
96
+ .command("rename")
97
+ .description("Rename a stage")
98
+ .argument("[slug]", "Stage slug")
99
+ .argument("[name]", "New stage display name")
100
+ .option("--org <slug>", "Organization slug")
101
+ .option("--project <slug>", "Project slug")
102
+ .option("--json", "Machine-readable output", false)
103
+ .action(
104
+ async (
105
+ slug: string | undefined,
106
+ name: string | undefined,
107
+ options: EnvTargetOptions & { json?: boolean },
108
+ ) => {
109
+ const orgSlug = await promptForOrganizationSlug(options.org);
110
+ const projectSlug = await promptForProjectSlug(orgSlug, options.project);
111
+ const stageSlug = await promptForStageSlug(orgSlug, projectSlug, slug);
112
+ const stageName = await promptForStageName(name);
113
+ const stage = await renameStageForProject(orgSlug, projectSlug, stageSlug, stageName);
114
+
115
+ if (options.json) {
116
+ toJsonOutput(true, stage);
117
+ return;
118
+ }
119
+
120
+ console.log(`Renamed stage ${stage.slug} to ${stage.name}.`);
121
+ },
122
+ );
123
+
124
+ stage
125
+ .command("delete")
126
+ .description("Delete a stage")
127
+ .argument("[slug]", "Stage slug")
128
+ .option("--org <slug>", "Organization slug")
129
+ .option("--project <slug>", "Project slug")
130
+ .option("--yes", "Skip confirmation prompt", false)
131
+ .option("--json", "Machine-readable output", false)
132
+ .action(
133
+ async (
134
+ slug: string | undefined,
135
+ options: EnvTargetOptions & { yes?: boolean; json?: boolean },
136
+ ) => {
137
+ const orgSlug = await promptForOrganizationSlug(options.org);
138
+ const projectSlug = await promptForProjectSlug(orgSlug, options.project);
139
+ const stageSlug = await promptForStageSlug(orgSlug, projectSlug, slug);
140
+
141
+ if (!options.yes) {
142
+ if (!process.stdout.isTTY) {
143
+ throw new Error("Stage deletion requires --yes in non-interactive mode.");
144
+ }
145
+ const confirmed = await confirm({
146
+ message: `Delete stage ${stageSlug}?`,
147
+ initialValue: false,
148
+ });
149
+ if (isCancel(confirmed)) {
150
+ cancel("Command canceled.");
151
+ process.exit(0);
152
+ }
153
+ if (!confirmed) {
154
+ throw new Error("Delete canceled.");
155
+ }
156
+ }
157
+
158
+ const response = await deleteStageForProject(orgSlug, projectSlug, stageSlug);
159
+ if (options.json) {
160
+ toJsonOutput(true, response);
161
+ return;
162
+ }
163
+
164
+ console.log(`Deleted stage ${response.deletedStageSlug}.`);
165
+ },
166
+ );
167
+ }