summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorAiden Cline <[email protected]>2025-12-16 11:19:08 -0600
committerAiden Cline <[email protected]>2025-12-16 11:19:18 -0600
commit3ac42e96326d93d50b681cbae3b92a2ad1841e8c (patch)
treea95a4202d6f5e641c4071aab4c3c734b14f92a2d /packages
parent9c26bb7c6c7d326ee11cdbd27586ee508bdff581 (diff)
downloadopencode-3ac42e96326d93d50b681cbae3b92a2ad1841e8c.tar.gz
opencode-3ac42e96326d93d50b681cbae3b92a2ad1841e8c.zip
fix: github install cmd if repo has . in it
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/cli/cmd/github.ts25
-rw-r--r--packages/opencode/test/cli/github-remote.test.ts80
2 files changed, 95 insertions, 10 deletions
diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts
index 480a38230..c7d403395 100644
--- a/packages/opencode/src/cli/cmd/github.ts
+++ b/packages/opencode/src/cli/cmd/github.ts
@@ -128,6 +128,19 @@ const AGENT_USERNAME = "opencode-agent[bot]"
const AGENT_REACTION = "eyes"
const WORKFLOW_FILE = ".github/workflows/opencode.yml"
+// Parses GitHub remote URLs in various formats:
+// - https://github.com/owner/repo.git
+// - https://github.com/owner/repo
+// - [email protected]:owner/repo.git
+// - [email protected]:owner/repo
+// - ssh://[email protected]/owner/repo.git
+// - ssh://[email protected]/owner/repo
+export function parseGitHubRemote(url: string): { owner: string; repo: string } | null {
+ const match = url.match(/^(?:(?:https?|ssh):\/\/)?(?:git@)?github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?$/)
+ if (!match) return null
+ return { owner: match[1], repo: match[2] }
+}
+
export const GithubCommand = cmd({
command: "github",
describe: "manage GitHub agent",
@@ -197,20 +210,12 @@ export const GithubInstallCommand = cmd({
// Get repo info
const info = (await $`git remote get-url origin`.quiet().nothrow().text()).trim()
- // match https or git pattern
- // ie. https://github.com/sst/opencode.git
- // ie. https://github.com/sst/opencode
- // ie. [email protected]:sst/opencode.git
- // ie. [email protected]:sst/opencode
- // ie. ssh://[email protected]/sst/opencode.git
- // ie. ssh://[email protected]/sst/opencode
- const parsed = info.match(/^(?:(?:https?|ssh):\/\/)?(?:git@)?github\.com[:/]([^/]+)\/([^/.]+?)(?:\.git)?$/)
+ const parsed = parseGitHubRemote(info)
if (!parsed) {
prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
throw new UI.CancelledError()
}
- const [, owner, repo] = parsed
- return { owner, repo, root: Instance.worktree }
+ return { owner: parsed.owner, repo: parsed.repo, root: Instance.worktree }
}
async function promptProvider() {
diff --git a/packages/opencode/test/cli/github-remote.test.ts b/packages/opencode/test/cli/github-remote.test.ts
new file mode 100644
index 000000000..80102d986
--- /dev/null
+++ b/packages/opencode/test/cli/github-remote.test.ts
@@ -0,0 +1,80 @@
+import { test, expect } from "bun:test"
+import { parseGitHubRemote } from "../../src/cli/cmd/github"
+
+test("parses https URL with .git suffix", () => {
+ expect(parseGitHubRemote("https://github.com/sst/opencode.git")).toEqual({ owner: "sst", repo: "opencode" })
+})
+
+test("parses https URL without .git suffix", () => {
+ expect(parseGitHubRemote("https://github.com/sst/opencode")).toEqual({ owner: "sst", repo: "opencode" })
+})
+
+test("parses git@ URL with .git suffix", () => {
+ expect(parseGitHubRemote("[email protected]:sst/opencode.git")).toEqual({ owner: "sst", repo: "opencode" })
+})
+
+test("parses git@ URL without .git suffix", () => {
+ expect(parseGitHubRemote("[email protected]:sst/opencode")).toEqual({ owner: "sst", repo: "opencode" })
+})
+
+test("parses ssh:// URL with .git suffix", () => {
+ expect(parseGitHubRemote("ssh://[email protected]/sst/opencode.git")).toEqual({ owner: "sst", repo: "opencode" })
+})
+
+test("parses ssh:// URL without .git suffix", () => {
+ expect(parseGitHubRemote("ssh://[email protected]/sst/opencode")).toEqual({ owner: "sst", repo: "opencode" })
+})
+
+test("parses http URL", () => {
+ expect(parseGitHubRemote("http://github.com/owner/repo")).toEqual({ owner: "owner", repo: "repo" })
+})
+
+test("parses URL with hyphenated owner and repo names", () => {
+ expect(parseGitHubRemote("https://github.com/my-org/my-repo.git")).toEqual({ owner: "my-org", repo: "my-repo" })
+})
+
+test("parses URL with underscores in names", () => {
+ expect(parseGitHubRemote("[email protected]:my_org/my_repo.git")).toEqual({ owner: "my_org", repo: "my_repo" })
+})
+
+test("parses URL with numbers in names", () => {
+ expect(parseGitHubRemote("https://github.com/org123/repo456")).toEqual({ owner: "org123", repo: "repo456" })
+})
+
+test("parses repos with dots in the name", () => {
+ expect(parseGitHubRemote("https://github.com/socketio/socket.io.git")).toEqual({
+ owner: "socketio",
+ repo: "socket.io",
+ })
+ expect(parseGitHubRemote("https://github.com/vuejs/vue.js")).toEqual({
+ owner: "vuejs",
+ repo: "vue.js",
+ })
+ expect(parseGitHubRemote("[email protected]:mrdoob/three.js.git")).toEqual({
+ owner: "mrdoob",
+ repo: "three.js",
+ })
+ expect(parseGitHubRemote("https://github.com/jashkenas/backbone.git")).toEqual({
+ owner: "jashkenas",
+ repo: "backbone",
+ })
+})
+
+test("returns null for non-github URLs", () => {
+ expect(parseGitHubRemote("https://gitlab.com/owner/repo.git")).toBeNull()
+ expect(parseGitHubRemote("[email protected]:owner/repo.git")).toBeNull()
+ expect(parseGitHubRemote("https://bitbucket.org/owner/repo")).toBeNull()
+})
+
+test("returns null for invalid URLs", () => {
+ expect(parseGitHubRemote("not-a-url")).toBeNull()
+ expect(parseGitHubRemote("")).toBeNull()
+ expect(parseGitHubRemote("github.com")).toBeNull()
+ expect(parseGitHubRemote("https://github.com/")).toBeNull()
+ expect(parseGitHubRemote("https://github.com/owner")).toBeNull()
+})
+
+test("returns null for URLs with extra path segments", () => {
+ expect(parseGitHubRemote("https://github.com/owner/repo/tree/main")).toBeNull()
+ expect(parseGitHubRemote("https://github.com/owner/repo/blob/main/file.ts")).toBeNull()
+})