fix: remove dead github-client.ts (never imported) (#990)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
TÂCHES 2026-03-17 17:05:44 -06:00 committed by GitHub
parent 482bbf678b
commit 2df7a2320b
2 changed files with 0 additions and 386 deletions

View file

@ -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<RepoInfo | null> {
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<PullRequestResult> {
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<PR> {
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<Review[]> {
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}`,
);
}
}

View file

@ -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);
});
});