fix(repository): type expected reference failures (#28880)
This commit is contained in:
@@ -7,8 +7,11 @@ import {
|
|||||||
repositoryCachePath,
|
repositoryCachePath,
|
||||||
sameRepositoryReference,
|
sameRepositoryReference,
|
||||||
parseRepositoryReference,
|
parseRepositoryReference,
|
||||||
|
parseRemoteRepositoryReference,
|
||||||
validateRepositoryBranch,
|
validateRepositoryBranch,
|
||||||
isRemoteRepositoryReference,
|
InvalidRepositoryBranchError,
|
||||||
|
InvalidRepositoryReferenceError,
|
||||||
|
UnsupportedLocalRepositoryError,
|
||||||
type RemoteReference,
|
type RemoteReference,
|
||||||
} from "@/util/repository"
|
} from "@/util/repository"
|
||||||
|
|
||||||
@@ -138,23 +141,26 @@ export function isError(error: unknown): error is Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const parseRemoteReference = Effect.fn("RepositoryCache.parseRemoteReference")(function* (repository: string) {
|
export const parseRemoteReference = Effect.fn("RepositoryCache.parseRemoteReference")(function* (repository: string) {
|
||||||
const reference = parseRepositoryReference(repository)
|
try {
|
||||||
if (!reference) {
|
return parseRemoteRepositoryReference(repository)
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof InvalidRepositoryReferenceError || error instanceof UnsupportedLocalRepositoryError) {
|
||||||
|
return yield* new InvalidRepositoryError({ repository: error.repository, message: error.message })
|
||||||
|
}
|
||||||
return yield* new InvalidRepositoryError({
|
return yield* new InvalidRepositoryError({
|
||||||
repository,
|
repository,
|
||||||
message: "Repository must be a git URL, host/path reference, or GitHub owner/repo shorthand",
|
message: errorMessage(error),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (!isRemoteRepositoryReference(reference)) {
|
|
||||||
return yield* new InvalidRepositoryError({ repository, message: "Local file repositories are not supported" })
|
|
||||||
}
|
|
||||||
return reference
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const validateBranch = Effect.fn("RepositoryCache.validateBranch")(function* (branch: string) {
|
export const validateBranch = Effect.fn("RepositoryCache.validateBranch")(function* (branch: string) {
|
||||||
try {
|
try {
|
||||||
validateRepositoryBranch(branch)
|
validateRepositoryBranch(branch)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof InvalidRepositoryBranchError) {
|
||||||
|
return yield* new InvalidBranchError({ branch: error.branch, message: error.message })
|
||||||
|
}
|
||||||
return yield* new InvalidBranchError({ branch, message: errorMessage(error) })
|
return yield* new InvalidBranchError({ branch, message: errorMessage(error) })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import path from "path"
|
import path from "path"
|
||||||
import { fileURLToPath } from "url"
|
import { fileURLToPath } from "url"
|
||||||
|
import { Schema } from "effect"
|
||||||
import { Global } from "@opencode-ai/core/global"
|
import { Global } from "@opencode-ai/core/global"
|
||||||
|
|
||||||
type BaseReference = {
|
type BaseReference = {
|
||||||
@@ -23,6 +24,43 @@ export type FileReference = BaseReference & {
|
|||||||
|
|
||||||
export type Reference = RemoteReference | FileReference
|
export type Reference = RemoteReference | FileReference
|
||||||
|
|
||||||
|
export class InvalidRepositoryReferenceError extends Schema.TaggedErrorClass<InvalidRepositoryReferenceError>()(
|
||||||
|
"RepositoryInvalidReferenceError",
|
||||||
|
{
|
||||||
|
repository: Schema.String,
|
||||||
|
message: Schema.String,
|
||||||
|
},
|
||||||
|
) {}
|
||||||
|
|
||||||
|
export class UnsupportedLocalRepositoryError extends Schema.TaggedErrorClass<UnsupportedLocalRepositoryError>()(
|
||||||
|
"RepositoryUnsupportedLocalRepositoryError",
|
||||||
|
{
|
||||||
|
repository: Schema.String,
|
||||||
|
message: Schema.String,
|
||||||
|
},
|
||||||
|
) {}
|
||||||
|
|
||||||
|
export class InvalidRepositoryBranchError extends Schema.TaggedErrorClass<InvalidRepositoryBranchError>()(
|
||||||
|
"RepositoryInvalidBranchError",
|
||||||
|
{
|
||||||
|
branch: Schema.String,
|
||||||
|
message: Schema.String,
|
||||||
|
},
|
||||||
|
) {}
|
||||||
|
|
||||||
|
export type RepositoryError =
|
||||||
|
| InvalidRepositoryReferenceError
|
||||||
|
| UnsupportedLocalRepositoryError
|
||||||
|
| InvalidRepositoryBranchError
|
||||||
|
|
||||||
|
export function isRepositoryError(error: unknown): error is RepositoryError {
|
||||||
|
return (
|
||||||
|
error instanceof InvalidRepositoryReferenceError ||
|
||||||
|
error instanceof UnsupportedLocalRepositoryError ||
|
||||||
|
error instanceof InvalidRepositoryBranchError
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeRepositoryInput(input: string) {
|
function normalizeRepositoryInput(input: string) {
|
||||||
return input
|
return input
|
||||||
.trim()
|
.trim()
|
||||||
@@ -147,16 +185,27 @@ export function isRemoteRepositoryReference(reference: Reference): reference is
|
|||||||
|
|
||||||
export function parseRemoteRepositoryReference(input: string) {
|
export function parseRemoteRepositoryReference(input: string) {
|
||||||
const reference = parseRepositoryReference(input)
|
const reference = parseRepositoryReference(input)
|
||||||
if (!reference) throw new Error("Repository must be a git URL, host/path reference, or GitHub owner/repo shorthand")
|
if (!reference) {
|
||||||
if (!isRemoteRepositoryReference(reference)) throw new Error("Local file repositories are not supported")
|
throw new InvalidRepositoryReferenceError({
|
||||||
|
repository: input,
|
||||||
|
message: "Repository must be a git URL, host/path reference, or GitHub owner/repo shorthand",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (!isRemoteRepositoryReference(reference)) {
|
||||||
|
throw new UnsupportedLocalRepositoryError({
|
||||||
|
repository: input,
|
||||||
|
message: "Local file repositories are not supported",
|
||||||
|
})
|
||||||
|
}
|
||||||
return reference
|
return reference
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateRepositoryBranch(branch: string) {
|
export function validateRepositoryBranch(branch: string) {
|
||||||
if (!/^[A-Za-z0-9/_.-]+$/.test(branch) || branch.startsWith("-") || branch.includes("..")) {
|
if (!/^[A-Za-z0-9/_.-]+$/.test(branch) || branch.startsWith("-") || branch.includes("..")) {
|
||||||
throw new Error(
|
throw new InvalidRepositoryBranchError({
|
||||||
"Branch must contain only alphanumeric characters, /, _, ., and -, and cannot start with - or contain ..",
|
branch,
|
||||||
)
|
message: "Branch must contain only alphanumeric characters, /, _, ., and -, and cannot start with - or contain ..",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import path from "path"
|
|||||||
import { pathToFileURL } from "url"
|
import { pathToFileURL } from "url"
|
||||||
import { Global } from "@opencode-ai/core/global"
|
import { Global } from "@opencode-ai/core/global"
|
||||||
import {
|
import {
|
||||||
|
InvalidRepositoryBranchError,
|
||||||
|
InvalidRepositoryReferenceError,
|
||||||
|
UnsupportedLocalRepositoryError,
|
||||||
isFileRepositoryReference,
|
isFileRepositoryReference,
|
||||||
isRemoteRepositoryReference,
|
isRemoteRepositoryReference,
|
||||||
parseRemoteRepositoryReference,
|
parseRemoteRepositoryReference,
|
||||||
@@ -61,6 +64,14 @@ describe("util.repository", () => {
|
|||||||
expect(() => parseRemoteRepositoryReference(pathToFileURL(localPath).href)).toThrow(
|
expect(() => parseRemoteRepositoryReference(pathToFileURL(localPath).href)).toThrow(
|
||||||
"Local file repositories are not supported",
|
"Local file repositories are not supported",
|
||||||
)
|
)
|
||||||
|
expect(() => parseRemoteRepositoryReference(pathToFileURL(localPath).href)).toThrow(UnsupportedLocalRepositoryError)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("rejects invalid remote repository references with typed errors", () => {
|
||||||
|
expect(() => parseRemoteRepositoryReference("not-a-repo")).toThrow(InvalidRepositoryReferenceError)
|
||||||
|
expect(() => parseRemoteRepositoryReference("git@github.com:../../../etc/passwd")).toThrow(
|
||||||
|
InvalidRepositoryReferenceError,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("compares cache identity independent of input spelling", () => {
|
test("compares cache identity independent of input spelling", () => {
|
||||||
@@ -77,5 +88,6 @@ describe("util.repository", () => {
|
|||||||
expect(() => validateRepositoryBranch("-bad")).toThrow("Branch must contain only alphanumeric characters")
|
expect(() => validateRepositoryBranch("-bad")).toThrow("Branch must contain only alphanumeric characters")
|
||||||
expect(() => validateRepositoryBranch("bad..branch")).toThrow("Branch must contain only alphanumeric characters")
|
expect(() => validateRepositoryBranch("bad..branch")).toThrow("Branch must contain only alphanumeric characters")
|
||||||
expect(() => validateRepositoryBranch("bad branch")).toThrow("Branch must contain only alphanumeric characters")
|
expect(() => validateRepositoryBranch("bad branch")).toThrow("Branch must contain only alphanumeric characters")
|
||||||
|
expect(() => validateRepositoryBranch("bad branch")).toThrow(InvalidRepositoryBranchError)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user