Просмотр исходного кода

fix: github install cmd if repo has . in it

Aiden Cline 2 месяцев назад
Родитель
Сommit
3ac42e9632

+ 3 - 3
.opencode/bun.lock

@@ -5,7 +5,7 @@
     "": {
       "dependencies": {
         "@octokit/rest": "^22.0.1",
-        "@opencode-ai/plugin": "0.0.0-dev-202512161535",
+        "@opencode-ai/plugin": "0.0.0-dev-202512161610",
       },
     },
   },
@@ -34,9 +34,9 @@
 
     "@octokit/types": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
 
-    "@opencode-ai/plugin": ["@opencode-ai/[email protected]535", "", { "dependencies": { "@opencode-ai/sdk": "0.0.0-dev-202512161535", "zod": "4.1.8" } }, "sha512-aHPm0T9EtKUYs5mTZKpYwcDTk3jP/YMSZGPfCAwruKitnktRFu0TxJQdEJwDfUokI4f1nkoGDkBgsbHw+pBHsA=="],
+    "@opencode-ai/plugin": ["@opencode-ai/[email protected]610", "", { "dependencies": { "@opencode-ai/sdk": "0.0.0-dev-202512161610", "zod": "4.1.8" } }, "sha512-5TDOK75WgWeS/Lul+6OkDT0ESYAFhemCD67OjFcNCONpVgicqoiAgDunmQ2TpsZ+bl0S5kxw4wFGKkFjzBIZ2g=="],
 
-    "@opencode-ai/sdk": ["@opencode-ai/[email protected]535", "", {}, "sha512-koVbuyuhNnEWMJtkIxSTcg8HQ34c4ShvBHv4dwebvVB2+ftjN/wcqPDx4RAwaxyFaY050qf1qobHHMXWWzDRwQ=="],
+    "@opencode-ai/sdk": ["@opencode-ai/[email protected]610", "", {}, "sha512-bnAwQ4DNdHqSoqMJfnZbH16qp0WnFSJpYWTmOdr/9hRu5SDjdmPx/QUlZGBg0yovuHJXqd1Fb/FLgljZ9QqGRA=="],
 
     "before-after-hook": ["[email protected]", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="],
 

+ 1 - 1
.opencode/package.json

@@ -1,6 +1,6 @@
 {
   "dependencies": {
     "@octokit/rest": "^22.0.1",
-    "@opencode-ai/plugin": "0.0.0-dev-202512161535"
+    "@opencode-ai/plugin": "0.0.0-dev-202512161610"
   }
 }

+ 15 - 10
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() {

+ 80 - 0
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()
+})