import { logError } from './logger' export const isError = (value: unknown): value is Error => value instanceof Error // Converts a git clone URL to the codebase name that includes the slash-separated code host, owner, and repository name // This should captures: // - "github:sourcegraph/sourcegraph" a common SSH host alias // - "https://github.com/sourcegraph/deploy-sourcegraph-k8s.git" // - "git@github.com:sourcegraph/sourcegraph.git" // - "https://dev.azure.com/organization/project/_git/repository" export function convertGitCloneURLToCodebaseName(cloneURL: string): string | null { const result = convertGitCloneURLToCodebaseNameOrError(cloneURL) if (isError(result)) { if (result.message) { if (result.cause) { logError( 'convertGitCloneURLToCodebaseName', result.message, result.cause, result.stack?.concat('\n') ) } else { logError('convertGitCloneURLToCodebaseName', result.message, result.stack?.concat('\n')) } } return null } return result } export function convertGitCloneURLToCodebaseNameOrError(cloneURL: string): string | Error { if (!cloneURL) { return new Error( `Unable to determine the git clone URL for this workspace.\ngit output: ${cloneURL}` ) } try { // Handle common Git SSH URL format const match = cloneURL.match(/^[\w-]+@([^:]+):([\w-]+)\/([\w-\.]+)$/) if (match) { const host = match[1] const owner = match[2] const repo = match[3].replace(/\.git$/, '') return `${host}/${owner}/${repo}` } const uri = new URL(cloneURL) // Handle Azure DevOps URLs if (uri.hostname?.includes('dev.azure') && uri.pathname) { return `${uri.hostname}${uri.pathname.replace('/_git', '')}` } // Handle GitHub URLs if (uri.protocol.startsWith('github') || uri.href.startsWith('github')) { return `github.com/${uri.pathname.replace('.git', '')}` } // Handle GitLab URLs if (uri.protocol.startsWith('gitlab') || uri.href.startsWith('gitlab')) { return `gitlab.com/${uri.pathname.replace('.git', '')}` } // Handle HTTPS URLs if (uri.protocol.startsWith('http') && uri.hostname && uri.pathname) { return `${uri.hostname}${uri.pathname.replace('.git', '')}` } // Generic URL if (uri.hostname && uri.pathname) { return `${uri.hostname}${uri.pathname.replace('.git', '')}` } return new Error('') } catch (error) { return new Error(`Cody could not extract repo name from clone URL ${cloneURL}:`, { cause: error, }) } } /** * Creates a simple subscriber that can be used to register callbacks */ type Listener = (value: T) => void interface Subscriber { subscribe(listener: Listener): () => void notify(value: T): void } export function createSubscriber(): Subscriber { const listeners: Set> = new Set() const subscribe = (listener: Listener): (() => void) => { listeners.add(listener) return () => listeners.delete(listener) } const notify = (value: T): void => { for (const listener of listeners) { listener(value) } } return { subscribe, notify, } } export function nextTick() { return new Promise(resolve => process.nextTick(resolve)) } export type SemverString = `${Prefix}${number}.${number}.${number}` export namespace SemverString { const splitPrefixRegex = /^(?.*)(?\d+\.\d+\.\d+)$/ export function forcePrefix

(prefix: P, value: string): SemverString

{ const match = splitPrefixRegex.exec(value) if (!match || !match.groups?.version) { throw new Error(`Invalid semver string: ${value}`) } return `${prefix}${match.groups?.version}` as SemverString

} } type TupleFromUnion = [T] extends [never] ? [] : T extends any ? [T, ...TupleFromUnion>] : [] // Helper type to ensure an array contains all members of T export type ArrayContainsAll = TupleFromUnion