Skip to content

Commit aee552c

Browse files
authored
fix(repository): type expected reference failures (anomalyco#28880)
1 parent 4f6eaf8 commit aee552c

3 files changed

Lines changed: 80 additions & 13 deletions

File tree

packages/opencode/src/reference/repository-cache.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ import {
77
repositoryCachePath,
88
sameRepositoryReference,
99
parseRepositoryReference,
10+
parseRemoteRepositoryReference,
1011
validateRepositoryBranch,
11-
isRemoteRepositoryReference,
12+
InvalidRepositoryBranchError,
13+
InvalidRepositoryReferenceError,
14+
UnsupportedLocalRepositoryError,
1215
type RemoteReference,
1316
} from "@/util/repository"
1417

@@ -138,23 +141,26 @@ export function isError(error: unknown): error is Error {
138141
}
139142

140143
export const parseRemoteReference = Effect.fn("RepositoryCache.parseRemoteReference")(function* (repository: string) {
141-
const reference = parseRepositoryReference(repository)
142-
if (!reference) {
144+
try {
145+
return parseRemoteRepositoryReference(repository)
146+
} catch (error) {
147+
if (error instanceof InvalidRepositoryReferenceError || error instanceof UnsupportedLocalRepositoryError) {
148+
return yield* new InvalidRepositoryError({ repository: error.repository, message: error.message })
149+
}
143150
return yield* new InvalidRepositoryError({
144151
repository,
145-
message: "Repository must be a git URL, host/path reference, or GitHub owner/repo shorthand",
152+
message: errorMessage(error),
146153
})
147154
}
148-
if (!isRemoteRepositoryReference(reference)) {
149-
return yield* new InvalidRepositoryError({ repository, message: "Local file repositories are not supported" })
150-
}
151-
return reference
152155
})
153156

154157
export const validateBranch = Effect.fn("RepositoryCache.validateBranch")(function* (branch: string) {
155158
try {
156159
validateRepositoryBranch(branch)
157160
} catch (error) {
161+
if (error instanceof InvalidRepositoryBranchError) {
162+
return yield* new InvalidBranchError({ branch: error.branch, message: error.message })
163+
}
158164
return yield* new InvalidBranchError({ branch, message: errorMessage(error) })
159165
}
160166
})

packages/opencode/src/util/repository.ts

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import path from "path"
22
import { fileURLToPath } from "url"
3+
import { Schema } from "effect"
34
import { Global } from "@opencode-ai/core/global"
45

56
type BaseReference = {
@@ -23,6 +24,43 @@ export type FileReference = BaseReference & {
2324

2425
export type Reference = RemoteReference | FileReference
2526

27+
export class InvalidRepositoryReferenceError extends Schema.TaggedErrorClass<InvalidRepositoryReferenceError>()(
28+
"RepositoryInvalidReferenceError",
29+
{
30+
repository: Schema.String,
31+
message: Schema.String,
32+
},
33+
) {}
34+
35+
export class UnsupportedLocalRepositoryError extends Schema.TaggedErrorClass<UnsupportedLocalRepositoryError>()(
36+
"RepositoryUnsupportedLocalRepositoryError",
37+
{
38+
repository: Schema.String,
39+
message: Schema.String,
40+
},
41+
) {}
42+
43+
export class InvalidRepositoryBranchError extends Schema.TaggedErrorClass<InvalidRepositoryBranchError>()(
44+
"RepositoryInvalidBranchError",
45+
{
46+
branch: Schema.String,
47+
message: Schema.String,
48+
},
49+
) {}
50+
51+
export type RepositoryError =
52+
| InvalidRepositoryReferenceError
53+
| UnsupportedLocalRepositoryError
54+
| InvalidRepositoryBranchError
55+
56+
export function isRepositoryError(error: unknown): error is RepositoryError {
57+
return (
58+
error instanceof InvalidRepositoryReferenceError ||
59+
error instanceof UnsupportedLocalRepositoryError ||
60+
error instanceof InvalidRepositoryBranchError
61+
)
62+
}
63+
2664
function normalizeRepositoryInput(input: string) {
2765
return input
2866
.trim()
@@ -147,16 +185,27 @@ export function isRemoteRepositoryReference(reference: Reference): reference is
147185

148186
export function parseRemoteRepositoryReference(input: string) {
149187
const reference = parseRepositoryReference(input)
150-
if (!reference) throw new Error("Repository must be a git URL, host/path reference, or GitHub owner/repo shorthand")
151-
if (!isRemoteRepositoryReference(reference)) throw new Error("Local file repositories are not supported")
188+
if (!reference) {
189+
throw new InvalidRepositoryReferenceError({
190+
repository: input,
191+
message: "Repository must be a git URL, host/path reference, or GitHub owner/repo shorthand",
192+
})
193+
}
194+
if (!isRemoteRepositoryReference(reference)) {
195+
throw new UnsupportedLocalRepositoryError({
196+
repository: input,
197+
message: "Local file repositories are not supported",
198+
})
199+
}
152200
return reference
153201
}
154202

155203
export function validateRepositoryBranch(branch: string) {
156204
if (!/^[A-Za-z0-9/_.-]+$/.test(branch) || branch.startsWith("-") || branch.includes("..")) {
157-
throw new Error(
158-
"Branch must contain only alphanumeric characters, /, _, ., and -, and cannot start with - or contain ..",
159-
)
205+
throw new InvalidRepositoryBranchError({
206+
branch,
207+
message: "Branch must contain only alphanumeric characters, /, _, ., and -, and cannot start with - or contain ..",
208+
})
160209
}
161210
}
162211

packages/opencode/test/util/repository.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import path from "path"
33
import { pathToFileURL } from "url"
44
import { Global } from "@opencode-ai/core/global"
55
import {
6+
InvalidRepositoryBranchError,
7+
InvalidRepositoryReferenceError,
8+
UnsupportedLocalRepositoryError,
69
isFileRepositoryReference,
710
isRemoteRepositoryReference,
811
parseRemoteRepositoryReference,
@@ -61,6 +64,14 @@ describe("util.repository", () => {
6164
expect(() => parseRemoteRepositoryReference(pathToFileURL(localPath).href)).toThrow(
6265
"Local file repositories are not supported",
6366
)
67+
expect(() => parseRemoteRepositoryReference(pathToFileURL(localPath).href)).toThrow(UnsupportedLocalRepositoryError)
68+
})
69+
70+
test("rejects invalid remote repository references with typed errors", () => {
71+
expect(() => parseRemoteRepositoryReference("not-a-repo")).toThrow(InvalidRepositoryReferenceError)
72+
expect(() => parseRemoteRepositoryReference("git@github.com:../../../etc/passwd")).toThrow(
73+
InvalidRepositoryReferenceError,
74+
)
6475
})
6576

6677
test("compares cache identity independent of input spelling", () => {
@@ -77,5 +88,6 @@ describe("util.repository", () => {
7788
expect(() => validateRepositoryBranch("-bad")).toThrow("Branch must contain only alphanumeric characters")
7889
expect(() => validateRepositoryBranch("bad..branch")).toThrow("Branch must contain only alphanumeric characters")
7990
expect(() => validateRepositoryBranch("bad branch")).toThrow("Branch must contain only alphanumeric characters")
91+
expect(() => validateRepositoryBranch("bad branch")).toThrow(InvalidRepositoryBranchError)
8092
})
8193
})

0 commit comments

Comments
 (0)