From 2df7a2320b08fb62834aaf50d0e0c4b0f3e36728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=82CHES?= Date: Tue, 17 Mar 2026 17:05:44 -0600 Subject: [PATCH] fix: remove dead github-client.ts (never imported) (#990) Co-authored-by: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/github-client.ts | 235 ------------------ src/tests/github-client.test.ts | 151 ----------- 2 files changed, 386 deletions(-) delete mode 100644 src/resources/extensions/gsd/github-client.ts delete mode 100644 src/tests/github-client.test.ts diff --git a/src/resources/extensions/gsd/github-client.ts b/src/resources/extensions/gsd/github-client.ts deleted file mode 100644 index 54b64fe37..000000000 --- a/src/resources/extensions/gsd/github-client.ts +++ /dev/null @@ -1,235 +0,0 @@ -/** - * GSD GitHub Client - * - * Standalone utility for interacting with GitHub's API via Octokit. - * Provides helpers for PR creation, review reading, and issue management. - * Can be used by other extensions that need GitHub integration. - */ - -import { execSync } from "node:child_process"; -import { Octokit } from "@octokit/rest"; - -// ─── Types ───────────────────────────────────────────────────────────────── - -export interface RepoInfo { - owner: string; - repo: string; -} - -export interface PullRequestOptions { - owner: string; - repo: string; - title: string; - body: string; - head: string; - base: string; -} - -export interface PullRequestResult { - number: number; - url: string; -} - -export interface PR { - number: number; - title: string; - body: string | null; - state: string; - head: { ref: string; sha: string }; - base: { ref: string }; - url: string; - user: { login: string } | null; -} - -export interface Review { - id: number; - user: { login: string } | null; - state: string; - body: string | null; - submitted_at: string | null; -} - -export interface IssueCommentOptions { - owner: string; - repo: string; - number: number; - body: string; -} - -// ─── Remote URL Parsing ──────────────────────────────────────────────────── - -/** - * Parse a GitHub owner/repo from a git remote URL. - * Supports both HTTPS and SSH formats: - * https://github.com/owner/repo.git - * git@github.com:owner/repo.git - * https://github.com/owner/repo - * ssh://git@github.com/owner/repo.git - */ -export function parseRemoteUrl(url: string): RepoInfo | null { - // SSH format: git@github.com:owner/repo.git - const sshMatch = url.match(/^git@github\.com:([^/]+)\/([^/.]+?)(?:\.git)?$/); - if (sshMatch) { - return { owner: sshMatch[1], repo: sshMatch[2] }; - } - - // HTTPS or ssh:// format - const httpsMatch = url.match( - /(?:https?|ssh):\/\/(?:[^@]+@)?github\.com\/([^/]+)\/([^/.]+?)(?:\.git)?$/, - ); - if (httpsMatch) { - return { owner: httpsMatch[1], repo: httpsMatch[2] }; - } - - return null; -} - -// ─── Client Creation ─────────────────────────────────────────────────────── - -/** - * Create an authenticated Octokit client. - * Uses the provided token, or falls back to GITHUB_TOKEN / GH_TOKEN env vars. - * Returns null if no token is available. - */ -export function createGitHubClient(token?: string): Octokit | null { - const auth = token || process.env.GITHUB_TOKEN || process.env.GH_TOKEN; - if (!auth) { - return null; - } - return new Octokit({ auth }); -} - -// ─── Repository Info ─────────────────────────────────────────────────────── - -/** - * Detect the GitHub owner/repo from the git remote in the given working directory. - */ -export async function getRepoInfo(cwd: string): Promise { - try { - const url = execSync("git config --get remote.origin.url", { - cwd, - encoding: "utf-8", - stdio: ["ignore", "pipe", "pipe"], - }).trim(); - - if (!url) return null; - return parseRemoteUrl(url); - } catch { - return null; - } -} - -// ─── Pull Request Operations ─────────────────────────────────────────────── - -/** - * Create a pull request on GitHub. - */ -export async function createPullRequest( - client: Octokit, - options: PullRequestOptions, -): Promise { - try { - const { data } = await client.pulls.create({ - owner: options.owner, - repo: options.repo, - title: options.title, - body: options.body, - head: options.head, - base: options.base, - }); - return { number: data.number, url: data.html_url }; - } catch (error: unknown) { - const message = - error instanceof Error ? error.message : "Unknown error"; - throw new Error( - `Failed to create pull request for ${options.owner}/${options.repo}: ${message}`, - ); - } -} - -/** - * Fetch a single pull request by number. - */ -export async function getPullRequest( - client: Octokit, - options: { owner: string; repo: string; number: number }, -): Promise { - try { - const { data } = await client.pulls.get({ - owner: options.owner, - repo: options.repo, - pull_number: options.number, - }); - return { - number: data.number, - title: data.title, - body: data.body, - state: data.state, - head: { ref: data.head.ref, sha: data.head.sha }, - base: { ref: data.base.ref }, - url: data.html_url, - user: data.user ? { login: data.user.login } : null, - }; - } catch (error: unknown) { - const message = - error instanceof Error ? error.message : "Unknown error"; - throw new Error( - `Failed to get pull request #${options.number} for ${options.owner}/${options.repo}: ${message}`, - ); - } -} - -/** - * List reviews on a pull request. - */ -export async function listPullRequestReviews( - client: Octokit, - options: { owner: string; repo: string; number: number }, -): Promise { - try { - const { data } = await client.pulls.listReviews({ - owner: options.owner, - repo: options.repo, - pull_number: options.number, - }); - return data.map((review) => ({ - id: review.id, - user: review.user ? { login: review.user.login } : null, - state: review.state, - body: review.body, - submitted_at: review.submitted_at ?? null, - })); - } catch (error: unknown) { - const message = - error instanceof Error ? error.message : "Unknown error"; - throw new Error( - `Failed to list reviews for PR #${options.number} in ${options.owner}/${options.repo}: ${message}`, - ); - } -} - -// ─── Issue Comments ──────────────────────────────────────────────────────── - -/** - * Create a comment on an issue or pull request. - */ -export async function createIssueComment( - client: Octokit, - options: IssueCommentOptions, -): Promise<{ id: number }> { - try { - const { data } = await client.issues.createComment({ - owner: options.owner, - repo: options.repo, - issue_number: options.number, - body: options.body, - }); - return { id: data.id }; - } catch (error: unknown) { - const message = - error instanceof Error ? error.message : "Unknown error"; - throw new Error( - `Failed to create comment on issue #${options.number} in ${options.owner}/${options.repo}: ${message}`, - ); - } -} diff --git a/src/tests/github-client.test.ts b/src/tests/github-client.test.ts deleted file mode 100644 index 7d9fc5708..000000000 --- a/src/tests/github-client.test.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { describe, it } from "node:test"; -import assert from "node:assert/strict"; -import { - parseRemoteUrl, - createGitHubClient, - getRepoInfo, -} from "../resources/extensions/gsd/github-client.ts"; - -describe("parseRemoteUrl — extracts owner/repo from git remote URLs", () => { - it("parses HTTPS URL with .git suffix", () => { - const result = parseRemoteUrl("https://github.com/octocat/hello-world.git"); - assert.deepEqual(result, { owner: "octocat", repo: "hello-world" }); - }); - - it("parses HTTPS URL without .git suffix", () => { - const result = parseRemoteUrl("https://github.com/octocat/hello-world"); - assert.deepEqual(result, { owner: "octocat", repo: "hello-world" }); - }); - - it("parses SSH URL with .git suffix", () => { - const result = parseRemoteUrl("git@github.com:octocat/hello-world.git"); - assert.deepEqual(result, { owner: "octocat", repo: "hello-world" }); - }); - - it("parses SSH URL without .git suffix", () => { - const result = parseRemoteUrl("git@github.com:octocat/hello-world"); - assert.deepEqual(result, { owner: "octocat", repo: "hello-world" }); - }); - - it("parses ssh:// protocol URL", () => { - const result = parseRemoteUrl( - "ssh://git@github.com/octocat/hello-world.git", - ); - assert.deepEqual(result, { owner: "octocat", repo: "hello-world" }); - }); - - it("handles repos with hyphens and underscores", () => { - const result = parseRemoteUrl( - "https://github.com/my-org/my_cool-repo.git", - ); - assert.deepEqual(result, { owner: "my-org", repo: "my_cool-repo" }); - }); - - it("returns null for non-GitHub URLs", () => { - const result = parseRemoteUrl("https://gitlab.com/owner/repo.git"); - assert.equal(result, null); - }); - - it("returns null for malformed URLs", () => { - assert.equal(parseRemoteUrl("not-a-url"), null); - assert.equal(parseRemoteUrl(""), null); - }); - - it("returns null for bare paths", () => { - assert.equal(parseRemoteUrl("/home/user/repo.git"), null); - }); -}); - -describe("createGitHubClient — Octokit instantiation", () => { - it("returns null when no token is provided and env vars are unset", () => { - const origGH = process.env.GITHUB_TOKEN; - const origGH2 = process.env.GH_TOKEN; - delete process.env.GITHUB_TOKEN; - delete process.env.GH_TOKEN; - - try { - const client = createGitHubClient(); - assert.equal(client, null); - } finally { - if (origGH !== undefined) process.env.GITHUB_TOKEN = origGH; - if (origGH2 !== undefined) process.env.GH_TOKEN = origGH2; - } - }); - - it("creates a client when a token is provided directly", () => { - const client = createGitHubClient("ghp_test123"); - assert.notEqual(client, null); - assert.equal(typeof client!.pulls, "object"); - assert.equal(typeof client!.issues, "object"); - }); - - it("creates a client from GITHUB_TOKEN env var", () => { - const origGH = process.env.GITHUB_TOKEN; - const origGH2 = process.env.GH_TOKEN; - delete process.env.GH_TOKEN; - process.env.GITHUB_TOKEN = "ghp_env_test"; - - try { - const client = createGitHubClient(); - assert.notEqual(client, null); - } finally { - if (origGH !== undefined) { - process.env.GITHUB_TOKEN = origGH; - } else { - delete process.env.GITHUB_TOKEN; - } - if (origGH2 !== undefined) process.env.GH_TOKEN = origGH2; - } - }); - - it("creates a client from GH_TOKEN env var", () => { - const origGH = process.env.GITHUB_TOKEN; - const origGH2 = process.env.GH_TOKEN; - delete process.env.GITHUB_TOKEN; - process.env.GH_TOKEN = "ghp_gh_token_test"; - - try { - const client = createGitHubClient(); - assert.notEqual(client, null); - } finally { - if (origGH !== undefined) process.env.GITHUB_TOKEN = origGH; - if (origGH2 !== undefined) { - process.env.GH_TOKEN = origGH2; - } else { - delete process.env.GH_TOKEN; - } - } - }); - - it("prefers explicit token over env vars", () => { - const origGH = process.env.GITHUB_TOKEN; - process.env.GITHUB_TOKEN = "ghp_from_env"; - - try { - const client = createGitHubClient("ghp_explicit"); - assert.notEqual(client, null); - } finally { - if (origGH !== undefined) { - process.env.GITHUB_TOKEN = origGH; - } else { - delete process.env.GITHUB_TOKEN; - } - } - }); -}); - -describe("getRepoInfo — detects repo from git working directory", () => { - it("returns owner/repo for the current repository", async () => { - const info = await getRepoInfo(process.cwd()); - // Verifies getRepoInfo can parse the origin remote — owner varies - // depending on whether this is the upstream repo or a fork. - assert.notEqual(info, null); - assert.ok(info!.owner.length > 0, "owner should be non-empty"); - assert.ok(info!.repo.toLowerCase().includes("gsd-2"), "repo should contain gsd-2"); - }); - - it("returns null for a non-git directory", async () => { - const info = await getRepoInfo("/tmp"); - assert.equal(info, null); - }); -});