This repository was archived by the owner on Aug 1, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 488
Expand file tree
/
Copy pathutils.ts
More file actions
208 lines (186 loc) · 6.71 KB
/
Copy pathutils.ts
File metadata and controls
208 lines (186 loc) · 6.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
import isEqual from 'lodash/isEqual'
import { logError } from './logger'
type PromiseResolverFn<T, E> = (value?: T, err?: E) => void
/**
* Creates a promise that can be resolved externally
*/
export function promise<T, E = any>(): [PromiseResolverFn<T, E>, Promise<T>] {
let resolverFn: PromiseResolverFn<T, E> = undefined as any
const internalPromise = new Promise<T>((resolve, reject) => {
resolverFn = (value, err) => (err ? reject(err) : resolve(value!))
})
if (!resolverFn) {
throw new Error('Unreachable code')
}
return [resolverFn!, internalPromise]
}
export function isError(value: unknown): value is Error {
return value instanceof Error
}
/**
* This is a little helper function that can be used to assert that all cases of a switch statement are handled.
*
* @example
* const something : 'foo' | 'bar' | 'buz' = 'foo'
* switch (something) {
* case 'foo':
* // ...
* break
* case 'bar':
* // ...
* break
* default:
* // gives a type and runtime error since we're not handling 'buz'
* assertUnreachable(something)
* }
*/
export function assertUnreachable<T>(v: never): never
export function assertUnreachable<T>(v: T) {
throw new Error(`Unreachable Code Path for <${v}>`)
}
// Converts a git clone URL in either ssh or https format to the codebase 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"
// - "git@gitlab-my-company.net:20022/path/with/subfolders/repo.git"
// - "otheruser@gitlab-my-company.net:20022/monorepo.git"
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
}
// This converts a git clone URL to the what is *likely* the repoName on Sourcegraph.
// This is not guaranteed to be correct, and we should add an endpoint to Sourcegraph
// to resolve the repoName from the cloneURL.
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 formats
const match = cloneURL.match(/^[\w-]+@([^:]+):(?:(\d+)\/)?([\w-\/\.]+)$/)
if (match) {
const [, host, port, path] = match
return `${host}${port ? `:${port}` : ''}/${path.replace(/\.git$/, '')}`
}
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<T> = (value: T) => void
interface Subscriber<T> {
subscribe(listener: Listener<T>): () => void
notify(value: T): void
}
export function createSubscriber<T>(): Subscriber<T> {
const listeners: Set<Listener<T>> = new Set()
const subscribe = (listener: Listener<T>): (() => 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 extends string> = `${Prefix}${number}.${number}.${number}`
export namespace SemverString {
const splitPrefixRegex = /^(?<prefix>.*)(?<version>\d+\.\d+\.\d+)$/
export function forcePrefix<P extends string>(prefix: P, value: string): SemverString<P> {
const match = splitPrefixRegex.exec(value)
if (!match || !match.groups?.version) {
throw new Error(`Invalid semver string: ${value}`)
}
return `${prefix}${match.groups?.version}` as SemverString<P>
}
}
type TupleFromUnion<T, U = T> = [T] extends [never]
? []
: T extends any
? [T, ...TupleFromUnion<Exclude<U, T>>]
: []
// Helper type to ensure an array contains all members of T
export type ArrayContainsAll<T extends string> = TupleFromUnion<T>
/** Make T readonly (recursively). */
export type ReadonlyDeep<T> = {
readonly [P in keyof T]: T[P] extends (infer U)[]
? ReadonlyArray<ReadonlyDeep<U>>
: T[P] extends object
? ReadonlyDeep<T[P]>
: T[P]
}
/** Make T partial (recursively). */
export type PartialDeep<T> = {
[P in keyof T]?: T[P] extends (infer U)[]
? Array<PartialDeep<U>>
: T[P] extends object
? PartialDeep<T[P]>
: T[P]
}
export function memoize<T extends (...args: any[]) => any>(
func: T
): (...args: Parameters<T>) => ReturnType<T> {
let lastArguments: any[] | null = null
let lastCalculatedValue: ReturnType<T> | null = null
return (...args: Parameters<T>): ReturnType<T> => {
if (isEqual(lastArguments, args)) {
return lastCalculatedValue as ReturnType<T>
}
lastArguments = args
lastCalculatedValue = func(args)
return lastCalculatedValue as ReturnType<T>
}
}