@towles/tool 0.0.66 → 0.0.68

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.
@@ -1,35 +1,32 @@
1
1
  import { beforeEach, describe, expect, it, vi } from "vitest";
2
- import { x } from "tinyexec";
3
- import { getIssues, isGithubCliInstalled } from "./gh-cli-wrapper";
4
2
 
5
- vi.mock("tinyexec", () => ({
6
- x: vi.fn().mockResolvedValue({ stdout: "[]" }),
7
- }));
3
+ import type { XFn } from "./gh-cli-wrapper";
4
+ import { getIssues, isGithubCliInstalled } from "./gh-cli-wrapper";
8
5
 
9
- const mockX = vi.mocked(x);
6
+ const mockX: XFn = vi.fn().mockResolvedValue({ stdout: "[]", stderr: "", exitCode: 0 });
10
7
 
11
8
  describe("gh-cli-wrapper", () => {
12
9
  beforeEach(() => {
13
10
  vi.clearAllMocks();
14
- mockX.mockResolvedValue({ stdout: "[]" } as never);
11
+ vi.mocked(mockX).mockResolvedValue({ stdout: "[]", stderr: "", exitCode: 0 });
15
12
  });
16
13
 
17
14
  describe("getIssues", () => {
18
15
  it("passes --label flag when label provided", async () => {
19
- await getIssues({ cwd: ".", label: "auto-claude" });
16
+ await getIssues({ cwd: ".", label: "auto-claude", exec: mockX });
20
17
 
21
18
  expect(mockX).toHaveBeenCalledWith("gh", expect.arrayContaining(["--label", "auto-claude"]));
22
19
  });
23
20
 
24
21
  it("does not pass --label flag when label not provided", async () => {
25
- await getIssues({ cwd: "." });
22
+ await getIssues({ cwd: ".", exec: mockX });
26
23
 
27
- const args = mockX.mock.calls[0]![1] as string[];
24
+ const args = vi.mocked(mockX).mock.calls[0]![1] as string[];
28
25
  expect(args).not.toContain("--label");
29
26
  });
30
27
 
31
28
  it("passes --assignee @me flag when assignedToMe is true", async () => {
32
- await getIssues({ cwd: ".", assignedToMe: true });
29
+ await getIssues({ cwd: ".", assignedToMe: true, exec: mockX });
33
30
 
34
31
  expect(mockX).toHaveBeenCalledWith("gh", expect.arrayContaining(["--assignee", "@me"]));
35
32
  });
@@ -37,16 +34,20 @@ describe("gh-cli-wrapper", () => {
37
34
 
38
35
  describe("isGithubCliInstalled", () => {
39
36
  it("returns true when gh CLI outputs expected string", async () => {
40
- mockX.mockResolvedValue({ stdout: "gh version 2.0.0 (https://github.com/cli/cli)" } as never);
37
+ vi.mocked(mockX).mockResolvedValue({
38
+ stdout: "gh version 2.0.0 (https://github.com/cli/cli)",
39
+ stderr: "",
40
+ exitCode: 0,
41
+ });
41
42
 
42
- const result = await isGithubCliInstalled();
43
+ const result = await isGithubCliInstalled(mockX);
43
44
  expect(result).toBe(true);
44
45
  });
45
46
 
46
47
  it("returns false when gh CLI is not available", async () => {
47
- mockX.mockRejectedValue(new Error("command not found"));
48
+ vi.mocked(mockX).mockRejectedValue(new Error("command not found"));
48
49
 
49
- const result = await isGithubCliInstalled();
50
+ const result = await isGithubCliInstalled(mockX);
50
51
  expect(result).toBe(false);
51
52
  });
52
53
  });
@@ -1,11 +1,18 @@
1
1
  import stripAnsi from "strip-ansi";
2
2
  import { x } from "tinyexec";
3
+ import type { Output } from "tinyexec";
3
4
 
4
5
  import { execSafe } from "./exec.js";
5
6
 
6
- export async function isGithubCliInstalled(): Promise<boolean> {
7
+ export type XFn = (
8
+ cmd: string,
9
+ args?: string[],
10
+ opts?: Record<string, unknown>,
11
+ ) => PromiseLike<Output>;
12
+
13
+ export async function isGithubCliInstalled(exec: XFn = x as XFn): Promise<boolean> {
7
14
  try {
8
- const proc = await x("gh", ["--version"]);
15
+ const proc = await exec("gh", ["--version"]);
9
16
  return proc.stdout.includes("https://github.com/cli/cli");
10
17
  } catch {
11
18
  // gh CLI not installed or not accessible
@@ -18,8 +25,12 @@ export async function gh<T = unknown>(args: string[]): Promise<T> {
18
25
  return JSON.parse(result.stdout.trim()) as T;
19
26
  }
20
27
 
21
- export async function ghRaw(args: string[]): Promise<string> {
22
- const result = await execSafe("gh", args);
28
+ export async function ghRaw(
29
+ args: string[],
30
+ exec?: (cmd: string, args: string[]) => Promise<{ stdout: string; ok: boolean }>,
31
+ ): Promise<string> {
32
+ const execFn = exec ?? execSafe;
33
+ const result = await execFn("gh", args);
23
34
  return result.stdout;
24
35
  }
25
36
 
@@ -37,9 +48,11 @@ export async function getIssues({
37
48
  assignedToMe,
38
49
  cwd,
39
50
  label,
51
+ exec = x as XFn,
40
52
  }: {
41
53
  assignedToMe?: boolean;
42
54
  cwd: string;
55
+ exec?: XFn;
43
56
  label?: string;
44
57
  }): Promise<Issue[]> {
45
58
  const args = ["issue", "list", "--json", "labels,number,title,state"];
@@ -52,7 +65,7 @@ export async function getIssues({
52
65
  args.push("--label", label);
53
66
  }
54
67
 
55
- const result = await x("gh", args);
68
+ const result = await exec("gh", args);
56
69
  // Setting NO_COLOR=1 didn't remove colors so had to use stripAnsi
57
70
  const stripped = stripAnsi(result.stdout);
58
71