@barekey/cli 0.2.0 → 0.3.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/AGENTS.md CHANGED
@@ -10,5 +10,18 @@
10
10
 
11
11
  ## Releases
12
12
 
13
- - If work includes merging changes to `/home/sander/barekey/sdk` on `master`, make sure the SDK `package.json` version is bumped before or with that merge whenever the SDK surface, generated types, runtime behavior, or published artifacts changed.
14
- - Use semantic versioning for SDK version bumps: `patch` for backward-compatible fixes, `minor` for backward-compatible features, `major` for breaking changes.
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.2.0",
8
+ "@barekey/sdk": "^0.3.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.2.0", "", {}, "sha512-vSbxzJ6ZqR8ThBqe6DIweoQliF6nCfuDvUnuKHFFqBVagkcT26goZekD9NXfJBb/Obip9rdJvKG9zfFTV1UBgQ=="],
21
+ "@barekey/sdk": ["@barekey/sdk@0.3.0", "", {}, "sha512-DInoqUoxHXcVxoXMj+FWKQFQIErj6VnV7fPiP2g0/vIadwMDueh6S7DkgD6L0hpZvuB2qGghURjSnjr0+cQX/Q=="],
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,4 +1,11 @@
1
1
  import type { BarekeyTypegenResult } from "@barekey/sdk/server";
2
2
  import { Command } from "commander";
3
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;
4
11
  export declare function registerTypegenCommand(program: Command): void;
@@ -1,6 +1,7 @@
1
1
  import { BarekeyClient } from "@barekey/sdk/server";
2
2
  import pc from "picocolors";
3
3
  import { addTargetOptions, requireLocalSession, resolveTarget, } from "../command-utils.js";
4
+ const DEFAULT_TYPEGEN_WATCH_INTERVAL_MS = 5_000;
4
5
  export function formatTypegenResultMessage(result) {
5
6
  const title = result.written
6
7
  ? pc.green(pc.bold("Typegen complete"))
@@ -15,26 +16,99 @@ export function formatTypegenResultMessage(result) {
15
16
  `${pc.bold("Public types")}: ${result.publicPath}`,
16
17
  ].join("\n");
17
18
  }
18
- async function runTypegen(options) {
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) {
19
42
  const local = await requireLocalSession();
20
43
  const target = await resolveTarget(options, local);
21
44
  const organization = target.orgSlug ?? local.credentials.orgSlug;
22
45
  if (!organization || organization.trim().length === 0) {
23
46
  throw new Error("Organization slug is required.");
24
47
  }
25
- const client = new BarekeyClient({
48
+ return {
49
+ client: new BarekeyClient({
50
+ organization,
51
+ project: target.projectSlug,
52
+ environment: target.stageSlug,
53
+ typegen: false,
54
+ }),
26
55
  organization,
27
56
  project: target.projectSlug,
28
57
  environment: target.stageSlug,
29
- typegen: false,
30
- });
58
+ };
59
+ }
60
+ async function runTypegen(options) {
61
+ const { client } = await resolveTypegenClient(options);
31
62
  const result = await client.typegen();
32
63
  console.log(formatTypegenResultMessage(result));
33
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
+ }
101
+ }
34
102
  export function registerTypegenCommand(program) {
35
103
  addTargetOptions(program
36
104
  .command("typegen")
37
- .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
+ }
38
112
  await runTypegen(options);
39
113
  });
40
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.1";
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.1";
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.2.0",
3
+ "version": "0.3.1",
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.2.0",
18
+ "@barekey/sdk": "^0.3.0",
19
19
  "commander": "^14.0.1",
20
20
  "open": "^10.2.0",
21
21
  "picocolors": "^1.1.1"
@@ -10,6 +10,8 @@ import {
10
10
  type EnvTargetOptions,
11
11
  } from "../command-utils.js";
12
12
 
13
+ const DEFAULT_TYPEGEN_WATCH_INTERVAL_MS = 5_000;
14
+
13
15
  export function formatTypegenResultMessage(result: BarekeyTypegenResult): string {
14
16
  const title = result.written
15
17
  ? pc.green(pc.bold("Typegen complete"))
@@ -26,7 +28,44 @@ export function formatTypegenResultMessage(result: BarekeyTypegenResult): string
26
28
  ].join("\n");
27
29
  }
28
30
 
29
- async function runTypegen(options: EnvTargetOptions): Promise<void> {
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
+ }> {
30
69
  const local = await requireLocalSession();
31
70
  const target = await resolveTarget(options, local);
32
71
  const organization = target.orgSlug ?? local.credentials.orgSlug;
@@ -34,23 +73,90 @@ async function runTypegen(options: EnvTargetOptions): Promise<void> {
34
73
  throw new Error("Organization slug is required.");
35
74
  }
36
75
 
37
- const client = new BarekeyClient({
76
+ return {
77
+ client: new BarekeyClient({
78
+ organization,
79
+ project: target.projectSlug,
80
+ environment: target.stageSlug,
81
+ typegen: false,
82
+ }),
38
83
  organization,
39
84
  project: target.projectSlug,
40
85
  environment: target.stageSlug,
41
- typegen: false,
42
- });
86
+ };
87
+ }
88
+
89
+ async function runTypegen(options: EnvTargetOptions): Promise<void> {
90
+ const { client } = await resolveTypegenClient(options);
43
91
  const result = await client.typegen();
44
92
 
45
93
  console.log(formatTypegenResultMessage(result));
46
94
  }
47
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
+ }
142
+ }
143
+
48
144
  export function registerTypegenCommand(program: Command): void {
49
145
  addTargetOptions(
50
146
  program
51
147
  .command("typegen")
52
- .description("Generate Barekey SDK types in the installed @barekey/sdk module"),
53
- ).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
+
54
160
  await runTypegen(options);
55
161
  });
56
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.1";
4
4
  export const DEFAULT_BAREKEY_API_URL = "https://api.barekey.dev";
@@ -1,6 +1,10 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
- import { formatTypegenResultMessage } from "../src/commands/typegen";
3
+ import {
4
+ formatTypegenResultMessage,
5
+ formatTypegenWatchStartedMessage,
6
+ parseTypegenWatchInterval,
7
+ } from "../src/commands/typegen";
4
8
 
5
9
  describe("formatTypegenResultMessage", () => {
6
10
  test("renders a helpful success message when files were updated", () => {
@@ -34,4 +38,37 @@ describe("formatTypegenResultMessage", () => {
34
38
  expect(message).toContain("/tmp/generated.server.d.ts");
35
39
  expect(message).toContain("/tmp/generated.public.d.ts");
36
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
+ });
37
74
  });