Skip to content

Commit b05bb0c

Browse files
committed
(feat) (openai/gpt-5.5, reviewed T, tested T) resolve GitHub repo from origin
1 parent 2417d21 commit b05bb0c

3 files changed

Lines changed: 97 additions & 4 deletions

File tree

cli/src/github/repo.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { Octokit } from "@octokit/core";
2+
3+
import { getCommandOutput } from "@/utils/getCommandOutput.js";
4+
5+
// standard github REST API headers
6+
const HEADERS = {
7+
accept: "application/vnd.github+json",
8+
"X-Github-Api-Version": "2026-03-10",
9+
}
10+
11+
export type GithubRepo = {
12+
repoName: string;
13+
repoOwnerName: string;
14+
}
15+
16+
export async function getLocalGithubRepo(client: Octokit): Promise<GithubRepo> {
17+
let isInsideGitRepo;
18+
try { isInsideGitRepo = await getCommandOutput("git", ["rev-parse", "--is-inside-work-tree"]); } catch (_) { throw new Error("not inside a git repository"); }
19+
if (isInsideGitRepo !== "true") throw new Error("not inside a git repository");
20+
21+
let originUrl;
22+
try { originUrl = await getCommandOutput("git", ["remote", "get-url", "origin"]); } catch (_) { throw new Error("git remote origin not found"); }
23+
24+
const repo = parseGithubRemote(originUrl);
25+
const { data } = await client.request(
26+
"GET /repos/{owner}/{repo}",
27+
{ owner: repo.repoOwnerName, repo: repo.repoName, headers: HEADERS},
28+
);
29+
30+
return { repoName: data.name, repoOwnerName: data.owner.login };
31+
}
32+
33+
export function parseGithubRemote(originUrl: string): GithubRepo {
34+
const sshRemote = originUrl.match(/^git@github\.com:([^/]+)\/(.+)$/);
35+
if (sshRemote) return parseGithubPath(`${sshRemote[1]}/${sshRemote[2]}`);
36+
37+
try {
38+
const url = new URL(originUrl);
39+
if (url.hostname !== "github.com") throw new Error("origin remote is not a github repository");
40+
return parseGithubPath(url.pathname);
41+
} catch (_) {
42+
throw new Error("origin remote is not a github repository");
43+
}
44+
}
45+
46+
function parseGithubPath(pathname: string): GithubRepo {
47+
const parts = pathname.replace(/^\/+|\/+$/g, "").split("/");
48+
const repoOwnerName = parts[0];
49+
const repoName = parts[1]?.replace(/\.git$/, "");
50+
51+
if (!repoOwnerName || !repoName || parts.length !== 2) {
52+
throw new Error("origin remote is not a github repository");
53+
}
54+
55+
return { repoName, repoOwnerName };
56+
}

cli/src/index.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { importAbi } from "@/utils/importAbi.js";
1313
import packageJson from "../package.json" with { type: "json" };
1414
import { b64urlToHex, jwtKid, parseJwt, requestGithubOidcToken } from "@/github/oidc.js";
1515
import { getOctokit } from "./github/auth.js";
16+
import { getLocalGithubRepo } from "./github/repo.js";
1617
import { getRepoSecretMetadata, setRepoSecret } from "./github/secrets.js";
1718
import { getRepoVariableMetadata, setRepoVariable } from "./github/vars.js";
1819

@@ -187,7 +188,8 @@ function addGithubSecretsCommand(program: Command): void {
187188
let data;
188189
try {
189190
octokit = await getOctokit();
190-
data = await getRepoSecretMetadata(octokit, "fcf", "freecodexyz", secretName);
191+
const repo = await getLocalGithubRepo(octokit);
192+
data = await getRepoSecretMetadata(octokit, repo.repoName, repo.repoOwnerName, secretName);
191193
console.log(JSON.stringify(data, null, 2));
192194
} catch (err) { die(err); }
193195
});
@@ -200,7 +202,8 @@ function addGithubSecretsCommand(program: Command): void {
200202
let octokit;
201203
try {
202204
octokit = await getOctokit();
203-
await setRepoSecret(octokit, "fcf", "freecodexyz", secretName, value);
205+
const repo = await getLocalGithubRepo(octokit);
206+
await setRepoSecret(octokit, repo.repoName, repo.repoOwnerName, secretName, value);
204207
console.log(`secret set: ${secretName}`);
205208
} catch (err) { die(err); }
206209
});
@@ -217,7 +220,8 @@ function addGithubVarsCommand(program: Command): void {
217220
let data;
218221
try {
219222
octokit = await getOctokit();
220-
data = await getRepoVariableMetadata(octokit, "fcf", "freecodexyz", varName);
223+
const repo = await getLocalGithubRepo(octokit);
224+
data = await getRepoVariableMetadata(octokit, repo.repoName, repo.repoOwnerName, varName);
221225
console.log(JSON.stringify(data, null, 2));
222226
} catch (err) { die(err); }
223227
});
@@ -230,7 +234,8 @@ function addGithubVarsCommand(program: Command): void {
230234
let octokit;
231235
try {
232236
octokit = await getOctokit();
233-
await setRepoVariable(octokit, "fcf", "freecodexyz", varName, value);
237+
const repo = await getLocalGithubRepo(octokit);
238+
await setRepoVariable(octokit, repo.repoName, repo.repoOwnerName, varName, value);
234239
console.log(`variable set: ${varName}`);
235240
} catch (err) { die(err); }
236241
});

cli/test/repo.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { test, expect } from "vitest";
2+
3+
import { parseGithubRemote } from "@/github/repo.js";
4+
5+
test("parses https github origin", () => {
6+
expect(parseGithubRemote("https://github.com/freecodexyz/fcf.git")).toEqual({
7+
repoName: "fcf",
8+
repoOwnerName: "freecodexyz",
9+
});
10+
});
11+
12+
test("parses ssh github origin", () => {
13+
expect(parseGithubRemote("git@github.com:freecodexyz/fcf.git")).toEqual({
14+
repoName: "fcf",
15+
repoOwnerName: "freecodexyz",
16+
});
17+
});
18+
19+
test("parses ssh url github origin", () => {
20+
expect(parseGithubRemote("ssh://git@github.com/freecodexyz/fcf.git")).toEqual({
21+
repoName: "fcf",
22+
repoOwnerName: "freecodexyz",
23+
});
24+
});
25+
26+
test("rejects non github origin", () => {
27+
expect(() => parseGithubRemote("https://gitlab.com/freecodexyz/fcf.git")).toThrow("origin remote is not a github repository");
28+
});
29+
30+
test("rejects malformed github origin", () => {
31+
expect(() => parseGithubRemote("https://github.com/freecodexyz")).toThrow("origin remote is not a github repository");
32+
});

0 commit comments

Comments
 (0)