@barekey/cli 0.1.4 → 0.3.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/AGENTS.md CHANGED
@@ -7,3 +7,21 @@
7
7
  - Use `bun run <script>` for project scripts.
8
8
  - Use `bun test` for tests.
9
9
  - Do not use `npm` or commit `package-lock.json`.
10
+
11
+ ## Releases
12
+
13
+ ### CLI update ritual
14
+
15
+ - If CLI work changes the shipped CLI behavior, commands, output, flags, package exports, or published artifacts, bump the CLI version before merging to `master`.
16
+ - Keep CLI versions aligned in both `package.json` and `src/constants.ts`.
17
+ - Use semantic versioning for the CLI bump: `patch` for backward-compatible fixes, `minor` for backward-compatible features, `major` for breaking changes.
18
+ - If the CLI depends on a newly released SDK feature or fix, update `@barekey/sdk` in `package.json`, run `bun install`, and commit the resulting `bun.lock` changes too.
19
+ - Before pushing, run:
20
+ - `bun test test`
21
+ - `bun run typecheck`
22
+ - `bun run build`
23
+
24
+ ### SDK handoff from CLI
25
+
26
+ - If work includes merging changes to `/home/sander/barekey/sdk`, make sure the SDK repo is version-bumped first whenever its surface, generated types, runtime behavior, or published artifacts changed.
27
+ - After that SDK merge, update the CLI dependency to the new SDK version if the CLI consumes the changed behavior.
package/README.md CHANGED
@@ -25,6 +25,8 @@ barekey env new FEATURE_FLAG true --type boolean --org acme --project api --stag
25
25
  barekey env set CHECKOUT_COPY original --ab redesign --chance 0.25 --org acme --project api --stage development
26
26
  barekey env delete FEATURE_FLAG --yes --org acme --project api --stage development
27
27
  barekey env get-many --names DATABASE_URL,FEATURE_FLAG --org acme --project api --stage development
28
+ barekey typegen --org acme --project api --stage development
29
+ barekey typegen --watch --org acme --project api --stage development
28
30
  ```
29
31
 
30
32
  ## Development
package/bun.lock CHANGED
@@ -5,7 +5,7 @@
5
5
  "": {
6
6
  "name": "@barekey/cli",
7
7
  "dependencies": {
8
- "@barekey/sdk": "^0.1.3",
8
+ "@barekey/sdk": "^0.2.0",
9
9
  "@clack/prompts": "^0.11.0",
10
10
  "commander": "^14.0.1",
11
11
  "open": "^10.2.0",
@@ -18,7 +18,7 @@
18
18
  },
19
19
  },
20
20
  "packages": {
21
- "@barekey/sdk": ["@barekey/sdk@0.1.3", "", {}, "sha512-Y5defUYsa6r6GnfR7i0zNnKVI6gXp/GJihVO7+kpijCA61Hnf5hheBTZQyEUma+G1U7MxLcQM/g+FNSHSOXeYQ=="],
21
+ "@barekey/sdk": ["@barekey/sdk@0.2.0", "", {}, "sha512-vSbxzJ6ZqR8ThBqe6DIweoQliF6nCfuDvUnuKHFFqBVagkcT26goZekD9NXfJBb/Obip9rdJvKG9zfFTV1UBgQ=="],
22
22
 
23
23
  "@clack/core": ["@clack/core@0.5.0", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow=="],
24
24
 
@@ -1,2 +1,11 @@
1
+ import type { BarekeyTypegenResult } from "@barekey/sdk/server";
1
2
  import { Command } from "commander";
3
+ export declare function formatTypegenResultMessage(result: BarekeyTypegenResult): string;
4
+ export declare function formatTypegenWatchStartedMessage(input: {
5
+ organization: string;
6
+ project: string;
7
+ environment: string;
8
+ intervalMs: number;
9
+ }): string;
10
+ export declare function parseTypegenWatchInterval(value: string | undefined): number;
2
11
  export declare function registerTypegenCommand(program: Command): void;
@@ -1,25 +1,114 @@
1
1
  import { BarekeyClient } from "@barekey/sdk/server";
2
+ import pc from "picocolors";
2
3
  import { addTargetOptions, requireLocalSession, resolveTarget, } from "../command-utils.js";
3
- async function runTypegen(options) {
4
+ const DEFAULT_TYPEGEN_WATCH_INTERVAL_MS = 5_000;
5
+ export function formatTypegenResultMessage(result) {
6
+ const title = result.written
7
+ ? pc.green(pc.bold("Typegen complete"))
8
+ : pc.cyan(pc.bold("Typegen already up to date"));
9
+ const detail = result.written
10
+ ? "Fresh SDK types are ready in your installed @barekey/sdk package."
11
+ : "Your installed @barekey/sdk package already has the latest generated types.";
12
+ return [
13
+ title,
14
+ detail,
15
+ `${pc.bold("Server types")}: ${result.serverPath}`,
16
+ `${pc.bold("Public types")}: ${result.publicPath}`,
17
+ ].join("\n");
18
+ }
19
+ export function formatTypegenWatchStartedMessage(input) {
20
+ return [
21
+ pc.cyan(pc.bold("Watching Barekey typegen")),
22
+ `Target: ${pc.bold(`${input.organization}/${input.project}@${input.environment}`)}`,
23
+ `Polling every ${pc.bold(`${input.intervalMs}ms`)}. Press ${pc.bold("Ctrl+C")} to stop.`,
24
+ ].join("\n");
25
+ }
26
+ export function parseTypegenWatchInterval(value) {
27
+ if (value === undefined) {
28
+ return DEFAULT_TYPEGEN_WATCH_INTERVAL_MS;
29
+ }
30
+ const parsed = Number.parseInt(value, 10);
31
+ if (!Number.isFinite(parsed) || parsed <= 0) {
32
+ throw new Error("--interval must be a positive integer number of milliseconds.");
33
+ }
34
+ return parsed;
35
+ }
36
+ function sleep(milliseconds) {
37
+ return new Promise((resolve) => {
38
+ setTimeout(resolve, milliseconds);
39
+ });
40
+ }
41
+ async function resolveTypegenClient(options) {
4
42
  const local = await requireLocalSession();
5
43
  const target = await resolveTarget(options, local);
6
44
  const organization = target.orgSlug ?? local.credentials.orgSlug;
7
45
  if (!organization || organization.trim().length === 0) {
8
46
  throw new Error("Organization slug is required.");
9
47
  }
10
- const client = new BarekeyClient({
48
+ return {
49
+ client: new BarekeyClient({
50
+ organization,
51
+ project: target.projectSlug,
52
+ environment: target.stageSlug,
53
+ typegen: false,
54
+ }),
11
55
  organization,
12
56
  project: target.projectSlug,
13
57
  environment: target.stageSlug,
14
- typegen: false,
15
- });
58
+ };
59
+ }
60
+ async function runTypegen(options) {
61
+ const { client } = await resolveTypegenClient(options);
16
62
  const result = await client.typegen();
17
- console.log(`${result.written ? "Updated" : "Unchanged"} ${result.path}`);
63
+ console.log(formatTypegenResultMessage(result));
64
+ }
65
+ async function runTypegenWatch(options) {
66
+ const intervalMs = parseTypegenWatchInterval(options.interval);
67
+ const { client, organization, project, environment } = await resolveTypegenClient(options);
68
+ console.log(formatTypegenWatchStartedMessage({
69
+ organization,
70
+ project,
71
+ environment,
72
+ intervalMs,
73
+ }));
74
+ let stopped = false;
75
+ const stop = () => {
76
+ stopped = true;
77
+ };
78
+ process.once("SIGINT", stop);
79
+ process.once("SIGTERM", stop);
80
+ try {
81
+ let isFirstRun = true;
82
+ while (!stopped) {
83
+ const result = await client.typegen();
84
+ if (isFirstRun || result.written) {
85
+ if (!isFirstRun) {
86
+ console.log("");
87
+ }
88
+ console.log(formatTypegenResultMessage(result));
89
+ }
90
+ isFirstRun = false;
91
+ if (stopped) {
92
+ break;
93
+ }
94
+ await sleep(intervalMs);
95
+ }
96
+ }
97
+ finally {
98
+ process.removeListener("SIGINT", stop);
99
+ process.removeListener("SIGTERM", stop);
100
+ }
18
101
  }
19
102
  export function registerTypegenCommand(program) {
20
103
  addTargetOptions(program
21
104
  .command("typegen")
22
- .description("Generate Barekey SDK types in the installed @barekey/sdk module")).action(async (options) => {
105
+ .description("Generate Barekey SDK types in the installed @barekey/sdk module")
106
+ .option("--watch", "Keep generated types up to date during development", false)
107
+ .option("--interval <ms>", "Polling interval for --watch in milliseconds")).action(async (options) => {
108
+ if (options.watch) {
109
+ await runTypegenWatch(options);
110
+ return;
111
+ }
23
112
  await runTypegen(options);
24
113
  });
25
114
  }
@@ -1,4 +1,4 @@
1
1
  export declare const CLI_NAME = "barekey";
2
2
  export declare const CLI_DESCRIPTION = "Barekey CLI";
3
- export declare const CLI_VERSION = "0.2.0";
3
+ export declare const CLI_VERSION = "0.3.0";
4
4
  export declare const DEFAULT_BAREKEY_API_URL = "https://api.barekey.dev";
package/dist/constants.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export const CLI_NAME = "barekey";
2
2
  export const CLI_DESCRIPTION = "Barekey CLI";
3
- export const CLI_VERSION = "0.2.0";
3
+ export const CLI_VERSION = "0.3.0";
4
4
  export const DEFAULT_BAREKEY_API_URL = "https://api.barekey.dev";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barekey/cli",
3
- "version": "0.1.4",
3
+ "version": "0.3.0",
4
4
  "description": "Barekey command line interface",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,7 +15,7 @@
15
15
  "packageManager": "bun@1.2.22",
16
16
  "dependencies": {
17
17
  "@clack/prompts": "^0.11.0",
18
- "@barekey/sdk": "^0.1.3",
18
+ "@barekey/sdk": "^0.2.0",
19
19
  "commander": "^14.0.1",
20
20
  "open": "^10.2.0",
21
21
  "picocolors": "^1.1.1"
@@ -1,5 +1,7 @@
1
1
  import { BarekeyClient } from "@barekey/sdk/server";
2
+ import type { BarekeyTypegenResult } from "@barekey/sdk/server";
2
3
  import { Command } from "commander";
4
+ import pc from "picocolors";
3
5
 
4
6
  import {
5
7
  addTargetOptions,
@@ -8,7 +10,62 @@ import {
8
10
  type EnvTargetOptions,
9
11
  } from "../command-utils.js";
10
12
 
11
- async function runTypegen(options: EnvTargetOptions): Promise<void> {
13
+ const DEFAULT_TYPEGEN_WATCH_INTERVAL_MS = 5_000;
14
+
15
+ export function formatTypegenResultMessage(result: BarekeyTypegenResult): string {
16
+ const title = result.written
17
+ ? pc.green(pc.bold("Typegen complete"))
18
+ : pc.cyan(pc.bold("Typegen already up to date"));
19
+ const detail = result.written
20
+ ? "Fresh SDK types are ready in your installed @barekey/sdk package."
21
+ : "Your installed @barekey/sdk package already has the latest generated types.";
22
+
23
+ return [
24
+ title,
25
+ detail,
26
+ `${pc.bold("Server types")}: ${result.serverPath}`,
27
+ `${pc.bold("Public types")}: ${result.publicPath}`,
28
+ ].join("\n");
29
+ }
30
+
31
+ export function formatTypegenWatchStartedMessage(input: {
32
+ organization: string;
33
+ project: string;
34
+ environment: string;
35
+ intervalMs: number;
36
+ }): string {
37
+ return [
38
+ pc.cyan(pc.bold("Watching Barekey typegen")),
39
+ `Target: ${pc.bold(`${input.organization}/${input.project}@${input.environment}`)}`,
40
+ `Polling every ${pc.bold(`${input.intervalMs}ms`)}. Press ${pc.bold("Ctrl+C")} to stop.`,
41
+ ].join("\n");
42
+ }
43
+
44
+ export function parseTypegenWatchInterval(value: string | undefined): number {
45
+ if (value === undefined) {
46
+ return DEFAULT_TYPEGEN_WATCH_INTERVAL_MS;
47
+ }
48
+
49
+ const parsed = Number.parseInt(value, 10);
50
+ if (!Number.isFinite(parsed) || parsed <= 0) {
51
+ throw new Error("--interval must be a positive integer number of milliseconds.");
52
+ }
53
+
54
+ return parsed;
55
+ }
56
+
57
+ function sleep(milliseconds: number): Promise<void> {
58
+ return new Promise((resolve) => {
59
+ setTimeout(resolve, milliseconds);
60
+ });
61
+ }
62
+
63
+ async function resolveTypegenClient(options: EnvTargetOptions): Promise<{
64
+ client: BarekeyClient;
65
+ organization: string;
66
+ project: string;
67
+ environment: string;
68
+ }> {
12
69
  const local = await requireLocalSession();
13
70
  const target = await resolveTarget(options, local);
14
71
  const organization = target.orgSlug ?? local.credentials.orgSlug;
@@ -16,23 +73,90 @@ async function runTypegen(options: EnvTargetOptions): Promise<void> {
16
73
  throw new Error("Organization slug is required.");
17
74
  }
18
75
 
19
- const client = new BarekeyClient({
76
+ return {
77
+ client: new BarekeyClient({
78
+ organization,
79
+ project: target.projectSlug,
80
+ environment: target.stageSlug,
81
+ typegen: false,
82
+ }),
20
83
  organization,
21
84
  project: target.projectSlug,
22
85
  environment: target.stageSlug,
23
- typegen: false,
24
- });
86
+ };
87
+ }
88
+
89
+ async function runTypegen(options: EnvTargetOptions): Promise<void> {
90
+ const { client } = await resolveTypegenClient(options);
25
91
  const result = await client.typegen();
26
92
 
27
- console.log(`${result.written ? "Updated" : "Unchanged"} ${result.path}`);
93
+ console.log(formatTypegenResultMessage(result));
94
+ }
95
+
96
+ async function runTypegenWatch(
97
+ options: EnvTargetOptions & {
98
+ interval?: string;
99
+ },
100
+ ): Promise<void> {
101
+ const intervalMs = parseTypegenWatchInterval(options.interval);
102
+ const { client, organization, project, environment } = await resolveTypegenClient(options);
103
+
104
+ console.log(
105
+ formatTypegenWatchStartedMessage({
106
+ organization,
107
+ project,
108
+ environment,
109
+ intervalMs,
110
+ }),
111
+ );
112
+
113
+ let stopped = false;
114
+ const stop = () => {
115
+ stopped = true;
116
+ };
117
+ process.once("SIGINT", stop);
118
+ process.once("SIGTERM", stop);
119
+
120
+ try {
121
+ let isFirstRun = true;
122
+ while (!stopped) {
123
+ const result = await client.typegen();
124
+ if (isFirstRun || result.written) {
125
+ if (!isFirstRun) {
126
+ console.log("");
127
+ }
128
+ console.log(formatTypegenResultMessage(result));
129
+ }
130
+ isFirstRun = false;
131
+
132
+ if (stopped) {
133
+ break;
134
+ }
135
+
136
+ await sleep(intervalMs);
137
+ }
138
+ } finally {
139
+ process.removeListener("SIGINT", stop);
140
+ process.removeListener("SIGTERM", stop);
141
+ }
28
142
  }
29
143
 
30
144
  export function registerTypegenCommand(program: Command): void {
31
145
  addTargetOptions(
32
146
  program
33
147
  .command("typegen")
34
- .description("Generate Barekey SDK types in the installed @barekey/sdk module"),
35
- ).action(async (options: EnvTargetOptions) => {
148
+ .description("Generate Barekey SDK types in the installed @barekey/sdk module")
149
+ .option("--watch", "Keep generated types up to date during development", false)
150
+ .option(
151
+ "--interval <ms>",
152
+ "Polling interval for --watch in milliseconds",
153
+ ),
154
+ ).action(async (options: EnvTargetOptions & { watch?: boolean; interval?: string }) => {
155
+ if (options.watch) {
156
+ await runTypegenWatch(options);
157
+ return;
158
+ }
159
+
36
160
  await runTypegen(options);
37
161
  });
38
162
  }
package/src/constants.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export const CLI_NAME = "barekey";
2
2
  export const CLI_DESCRIPTION = "Barekey CLI";
3
- export const CLI_VERSION = "0.2.0";
3
+ export const CLI_VERSION = "0.3.0";
4
4
  export const DEFAULT_BAREKEY_API_URL = "https://api.barekey.dev";
@@ -0,0 +1,74 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ formatTypegenResultMessage,
5
+ formatTypegenWatchStartedMessage,
6
+ parseTypegenWatchInterval,
7
+ } from "../src/commands/typegen";
8
+
9
+ describe("formatTypegenResultMessage", () => {
10
+ test("renders a helpful success message when files were updated", () => {
11
+ const message = formatTypegenResultMessage({
12
+ written: true,
13
+ path: "/tmp/generated.server.d.ts",
14
+ serverPath: "/tmp/generated.server.d.ts",
15
+ publicPath: "/tmp/generated.public.d.ts",
16
+ manifestVersion: "manifest-1",
17
+ });
18
+
19
+ expect(message).toContain("Typegen complete");
20
+ expect(message).toContain("Fresh SDK types are ready");
21
+ expect(message).toContain("Server types");
22
+ expect(message).toContain("/tmp/generated.server.d.ts");
23
+ expect(message).toContain("Public types");
24
+ expect(message).toContain("/tmp/generated.public.d.ts");
25
+ });
26
+
27
+ test("renders a calm message when nothing changed", () => {
28
+ const message = formatTypegenResultMessage({
29
+ written: false,
30
+ path: "/tmp/generated.server.d.ts",
31
+ serverPath: "/tmp/generated.server.d.ts",
32
+ publicPath: "/tmp/generated.public.d.ts",
33
+ manifestVersion: "manifest-1",
34
+ });
35
+
36
+ expect(message).toContain("Typegen already up to date");
37
+ expect(message).toContain("already has the latest generated types");
38
+ expect(message).toContain("/tmp/generated.server.d.ts");
39
+ expect(message).toContain("/tmp/generated.public.d.ts");
40
+ });
41
+
42
+ test("renders a watch banner with target details", () => {
43
+ const message = formatTypegenWatchStartedMessage({
44
+ organization: "acme",
45
+ project: "web",
46
+ environment: "development",
47
+ intervalMs: 5000,
48
+ });
49
+
50
+ expect(message).toContain("Watching Barekey typegen");
51
+ expect(message).toContain("acme/web@development");
52
+ expect(message).toContain("5000ms");
53
+ expect(message).toContain("Ctrl+C");
54
+ });
55
+ });
56
+
57
+ describe("parseTypegenWatchInterval", () => {
58
+ test("defaults to five seconds", () => {
59
+ expect(parseTypegenWatchInterval(undefined)).toBe(5000);
60
+ });
61
+
62
+ test("accepts a positive integer number of milliseconds", () => {
63
+ expect(parseTypegenWatchInterval("1500")).toBe(1500);
64
+ });
65
+
66
+ test("rejects invalid watch intervals", () => {
67
+ expect(() => parseTypegenWatchInterval("0")).toThrow(
68
+ "--interval must be a positive integer number of milliseconds.",
69
+ );
70
+ expect(() => parseTypegenWatchInterval("abc")).toThrow(
71
+ "--interval must be a positive integer number of milliseconds.",
72
+ );
73
+ });
74
+ });