@ankhorage/repository 0.6.16 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/dist/github/index.d.ts +3 -0
- package/dist/github/index.d.ts.map +1 -1
- package/dist/github/index.js +2 -0
- package/dist/github/index.js.map +1 -1
- package/dist/providers/github/adapters/createGitHubHttpClient.d.ts +17 -0
- package/dist/providers/github/adapters/createGitHubHttpClient.d.ts.map +1 -0
- package/dist/providers/github/adapters/createGitHubHttpClient.js +209 -0
- package/dist/providers/github/adapters/createGitHubHttpClient.js.map +1 -0
- package/dist/providers/github/adapters/createGitHubHttpClient.test.d.ts +2 -0
- package/dist/providers/github/adapters/createGitHubHttpClient.test.d.ts.map +1 -0
- package/dist/providers/github/adapters/createGitHubHttpClient.test.js +195 -0
- package/dist/providers/github/adapters/createGitHubHttpClient.test.js.map +1 -0
- package/dist/providers/github/adapters/createGitHubHttpRepositoryDiscoveryGateway.d.ts +9 -0
- package/dist/providers/github/adapters/createGitHubHttpRepositoryDiscoveryGateway.d.ts.map +1 -0
- package/dist/providers/github/adapters/createGitHubHttpRepositoryDiscoveryGateway.js +91 -0
- package/dist/providers/github/adapters/createGitHubHttpRepositoryDiscoveryGateway.js.map +1 -0
- package/dist/providers/github/adapters/createGitHubHttpRepositoryReadGateway.d.ts +3 -1
- package/dist/providers/github/adapters/createGitHubHttpRepositoryReadGateway.d.ts.map +1 -1
- package/dist/providers/github/adapters/createGitHubHttpRepositoryReadGateway.js +27 -58
- package/dist/providers/github/adapters/createGitHubHttpRepositoryReadGateway.js.map +1 -1
- package/dist/providers/github/adapters/createGitHubHttpRepositoryReadGateway.test.js +162 -0
- package/dist/providers/github/adapters/createGitHubHttpRepositoryReadGateway.test.js.map +1 -1
- package/dist/providers/github/adapters/rateLimitResponseAsync.d.ts +9 -0
- package/dist/providers/github/adapters/rateLimitResponseAsync.d.ts.map +1 -0
- package/dist/providers/github/adapters/rateLimitResponseAsync.js +44 -0
- package/dist/providers/github/adapters/rateLimitResponseAsync.js.map +1 -0
- package/dist/providers/github/definitions/GitHubRepositoryDiscoveryOptions.d.ts +10 -0
- package/dist/providers/github/definitions/GitHubRepositoryDiscoveryOptions.d.ts.map +1 -0
- package/dist/providers/github/definitions/GitHubRepositoryDiscoveryOptions.js +2 -0
- package/dist/providers/github/definitions/GitHubRepositoryDiscoveryOptions.js.map +1 -0
- package/dist/providers/github/definitions/GitHubRepositoryDiscoveryResult.d.ts +25 -0
- package/dist/providers/github/definitions/GitHubRepositoryDiscoveryResult.d.ts.map +1 -0
- package/dist/providers/github/definitions/GitHubRepositoryDiscoveryResult.js +12 -0
- package/dist/providers/github/definitions/GitHubRepositoryDiscoveryResult.js.map +1 -0
- package/dist/providers/github/definitions/GitHubRepositoryMaterializationOptions.d.ts +1 -0
- package/dist/providers/github/definitions/GitHubRepositoryMaterializationOptions.d.ts.map +1 -1
- package/dist/providers/github/ports/GitHubRepositoryDiscoveryGateway.d.ts +18 -0
- package/dist/providers/github/ports/GitHubRepositoryDiscoveryGateway.d.ts.map +1 -0
- package/dist/providers/github/ports/GitHubRepositoryDiscoveryGateway.js +2 -0
- package/dist/providers/github/ports/GitHubRepositoryDiscoveryGateway.js.map +1 -0
- package/dist/providers/github/ports/GitHubRepositoryReadGateway.d.ts +4 -4
- package/dist/providers/github/ports/GitHubRepositoryReadGateway.d.ts.map +1 -1
- package/dist/providers/github/services/discoverGitHubRepositoriesAsync.d.ts +5 -0
- package/dist/providers/github/services/discoverGitHubRepositoriesAsync.d.ts.map +1 -0
- package/dist/providers/github/services/discoverGitHubRepositoriesAsync.js +9 -0
- package/dist/providers/github/services/discoverGitHubRepositoriesAsync.js.map +1 -0
- package/dist/providers/github/services/discoverGitHubRepositoriesWithGatewayAsync.d.ts +6 -0
- package/dist/providers/github/services/discoverGitHubRepositoriesWithGatewayAsync.d.ts.map +1 -0
- package/dist/providers/github/services/discoverGitHubRepositoriesWithGatewayAsync.js +91 -0
- package/dist/providers/github/services/discoverGitHubRepositoriesWithGatewayAsync.js.map +1 -0
- package/dist/providers/github/services/discoverGitHubRepositoriesWithGatewayAsync.test.d.ts +2 -0
- package/dist/providers/github/services/discoverGitHubRepositoriesWithGatewayAsync.test.d.ts.map +1 -0
- package/dist/providers/github/services/discoverGitHubRepositoriesWithGatewayAsync.test.js +55 -0
- package/dist/providers/github/services/discoverGitHubRepositoriesWithGatewayAsync.test.js.map +1 -0
- package/dist/providers/github/services/materializeGitHubRepositoryWithGatewayAsync.d.ts.map +1 -1
- package/dist/providers/github/services/materializeGitHubRepositoryWithGatewayAsync.js +21 -13
- package/dist/providers/github/services/materializeGitHubRepositoryWithGatewayAsync.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @ankhorage/repository
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e482ef6: Add a public GitHub owner and repository discovery API with normalized source parsing, complete pagination, rate-limit handling, retries, cancellation, and repository URL preselection.
|
|
8
|
+
|
|
3
9
|
## 0.6.16
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# @ankhorage/repository
|
|
5
5
|
|
|
6
|
-
         
|
|
7
7
|
|
|
8
8
|
Standalone repository capability for connecting and managing an app project's source repository.
|
|
9
9
|
|
package/dist/github/index.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
export type { GitHubRepositoryConnectionOptions } from '../providers/github/definitions/GitHubRepositoryConnectionOptions.js';
|
|
2
2
|
export type { GitHubRepositoryConnectionFailure, GitHubRepositoryConnectionResult, GitHubRepositoryConnectionStatus, } from '../providers/github/definitions/GitHubRepositoryConnectionResult.js';
|
|
3
|
+
export type { GitHubRepositoryAuthentication, GitHubRepositoryDiscoveryOptions, } from '../providers/github/definitions/GitHubRepositoryDiscoveryOptions.js';
|
|
4
|
+
export { type GitHubOwnerType, GitHubRepositoryDiscoveryError, type GitHubRepositoryDiscoveryErrorCode, type GitHubRepositoryDiscoveryRepository, type GitHubRepositoryDiscoveryResult, } from '../providers/github/definitions/GitHubRepositoryDiscoveryResult.js';
|
|
3
5
|
export type { GitHubRepositoryMaterializationOptions } from '../providers/github/definitions/GitHubRepositoryMaterializationOptions.js';
|
|
4
6
|
export type { GitHubRepositoryMaterializationDiagnostic, GitHubRepositoryMaterializationResult, } from '../providers/github/definitions/GitHubRepositoryMaterializationResult.js';
|
|
5
7
|
export type { GitHubRepositoryVisibility } from '../providers/github/definitions/GitHubRepositoryVisibility.js';
|
|
6
8
|
export { connectGitHubRepositoryAsync } from '../providers/github/services/connectGitHubRepositoryAsync.js';
|
|
9
|
+
export { discoverGitHubRepositoriesAsync } from '../providers/github/services/discoverGitHubRepositoriesAsync.js';
|
|
7
10
|
export { materializeGitHubRepositoryAsync } from '../providers/github/services/materializeGitHubRepositoryAsync.js';
|
|
8
11
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/github/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,iCAAiC,EAAE,MAAM,sEAAsE,CAAC;AAC9H,YAAY,EACV,iCAAiC,EACjC,gCAAgC,EAChC,gCAAgC,GACjC,MAAM,qEAAqE,CAAC;AAC7E,YAAY,EAAE,sCAAsC,EAAE,MAAM,2EAA2E,CAAC;AACxI,YAAY,EACV,yCAAyC,EACzC,qCAAqC,GACtC,MAAM,0EAA0E,CAAC;AAClF,YAAY,EAAE,0BAA0B,EAAE,MAAM,+DAA+D,CAAC;AAChH,OAAO,EAAE,4BAA4B,EAAE,MAAM,8DAA8D,CAAC;AAC5G,OAAO,EAAE,gCAAgC,EAAE,MAAM,kEAAkE,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/github/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,iCAAiC,EAAE,MAAM,sEAAsE,CAAC;AAC9H,YAAY,EACV,iCAAiC,EACjC,gCAAgC,EAChC,gCAAgC,GACjC,MAAM,qEAAqE,CAAC;AAC7E,YAAY,EACV,8BAA8B,EAC9B,gCAAgC,GACjC,MAAM,qEAAqE,CAAC;AAC7E,OAAO,EACL,KAAK,eAAe,EACpB,8BAA8B,EAC9B,KAAK,kCAAkC,EACvC,KAAK,mCAAmC,EACxC,KAAK,+BAA+B,GACrC,MAAM,oEAAoE,CAAC;AAC5E,YAAY,EAAE,sCAAsC,EAAE,MAAM,2EAA2E,CAAC;AACxI,YAAY,EACV,yCAAyC,EACzC,qCAAqC,GACtC,MAAM,0EAA0E,CAAC;AAClF,YAAY,EAAE,0BAA0B,EAAE,MAAM,+DAA+D,CAAC;AAChH,OAAO,EAAE,4BAA4B,EAAE,MAAM,8DAA8D,CAAC;AAC5G,OAAO,EAAE,+BAA+B,EAAE,MAAM,iEAAiE,CAAC;AAClH,OAAO,EAAE,gCAAgC,EAAE,MAAM,kEAAkE,CAAC"}
|
package/dist/github/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { GitHubRepositoryDiscoveryError, } from '../providers/github/definitions/GitHubRepositoryDiscoveryResult.js';
|
|
1
2
|
export { connectGitHubRepositoryAsync } from '../providers/github/services/connectGitHubRepositoryAsync.js';
|
|
3
|
+
export { discoverGitHubRepositoriesAsync } from '../providers/github/services/discoverGitHubRepositoriesAsync.js';
|
|
2
4
|
export { materializeGitHubRepositoryAsync } from '../providers/github/services/materializeGitHubRepositoryAsync.js';
|
|
3
5
|
//# sourceMappingURL=index.js.map
|
package/dist/github/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/github/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/github/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAEL,8BAA8B,GAI/B,MAAM,oEAAoE,CAAC;AAO5E,OAAO,EAAE,4BAA4B,EAAE,MAAM,8DAA8D,CAAC;AAC5G,OAAO,EAAE,+BAA+B,EAAE,MAAM,iEAAiE,CAAC;AAClH,OAAO,EAAE,gCAAgC,EAAE,MAAM,kEAAkE,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { GitHubRepositoryAuthentication } from '../definitions/GitHubRepositoryDiscoveryOptions.js';
|
|
2
|
+
export interface GitHubHttpClient {
|
|
3
|
+
requestBytesAsync(url: string, subject: string, signal?: AbortSignal): Promise<Uint8Array>;
|
|
4
|
+
requestJsonResponseAsync(url: string, subject: string, signal?: AbortSignal): Promise<{
|
|
5
|
+
readonly value: unknown;
|
|
6
|
+
readonly link: string | null;
|
|
7
|
+
}>;
|
|
8
|
+
}
|
|
9
|
+
export interface GitHubHttpClientOptions {
|
|
10
|
+
readonly authentication?: GitHubRepositoryAuthentication;
|
|
11
|
+
readonly now?: () => number;
|
|
12
|
+
readonly timeoutMilliseconds?: number;
|
|
13
|
+
readonly waitAsync?: (milliseconds: number, signal?: AbortSignal) => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
/*** Create the canonical credential-safe GitHub HTTP client for repository provider adapters. */
|
|
16
|
+
export declare function createGitHubHttpClient(fetcher?: typeof fetch, options?: GitHubHttpClientOptions): GitHubHttpClient;
|
|
17
|
+
//# sourceMappingURL=createGitHubHttpClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createGitHubHttpClient.d.ts","sourceRoot":"","sources":["../../../../src/providers/github/adapters/createGitHubHttpClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,oDAAoD,CAAC;AASzG,MAAM,WAAW,gBAAgB;IAC/B,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3F,wBAAwB,CACtB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC;QAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;CACvE;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,cAAc,CAAC,EAAE,8BAA8B,CAAC;IACzD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACpF;AAED,iGAAiG;AACjG,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,OAAO,KAAa,EAC7B,OAAO,GAAE,uBAA4B,GACpC,gBAAgB,CAkBlB"}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { GitHubRepositoryDiscoveryError } from '../definitions/GitHubRepositoryDiscoveryResult.js';
|
|
2
|
+
import { rateLimitResponseAsync } from './rateLimitResponseAsync.js';
|
|
3
|
+
const REQUEST_TIMEOUT_MILLISECONDS = 30_000;
|
|
4
|
+
const MAX_REQUEST_ATTEMPTS = 3;
|
|
5
|
+
const MAX_RETRY_WAIT_MILLISECONDS = 60_000;
|
|
6
|
+
const MAX_RETRY_DELAY_MILLISECONDS = 30_000;
|
|
7
|
+
/*** Create the canonical credential-safe GitHub HTTP client for repository provider adapters. */
|
|
8
|
+
export function createGitHubHttpClient(fetcher = fetch, options = {}) {
|
|
9
|
+
const configuration = {
|
|
10
|
+
authentication: options.authentication ?? 'environment',
|
|
11
|
+
now: options.now ?? Date.now,
|
|
12
|
+
timeoutMilliseconds: options.timeoutMilliseconds ?? REQUEST_TIMEOUT_MILLISECONDS,
|
|
13
|
+
waitAsync: options.waitAsync ?? waitForRetryAsync,
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
requestBytesAsync: (url, subject, signal) => requestWithRetriesAsync(fetcher, configuration, url, subject, signal, (response) => response.arrayBuffer().then((value) => new Uint8Array(value))),
|
|
17
|
+
requestJsonResponseAsync: (url, subject, signal) => requestWithRetriesAsync(fetcher, configuration, url, subject, signal, async (response) => ({
|
|
18
|
+
value: await response.json(),
|
|
19
|
+
link: response.headers.get('link'),
|
|
20
|
+
})),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/*** Execute one GitHub request with bounded retry time and cancellation across all request phases. */
|
|
24
|
+
async function requestWithRetriesAsync(fetcher, configuration, url, subject, signal, readResponseAsync) {
|
|
25
|
+
let waitedMilliseconds = 0;
|
|
26
|
+
for (let attempt = 0; attempt < MAX_REQUEST_ATTEMPTS; attempt += 1) {
|
|
27
|
+
throwIfAborted(signal);
|
|
28
|
+
const response = await fetchResponseAsync(fetcher, configuration, url, signal);
|
|
29
|
+
if (response.ok) {
|
|
30
|
+
try {
|
|
31
|
+
return await readResponseWithDeadlineAsync(readResponseAsync, response, configuration, signal);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (isInterruptionError(error))
|
|
35
|
+
throw error;
|
|
36
|
+
throw new GitHubRepositoryDiscoveryError('api-failure', 'GitHub returned malformed data.');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const rateLimit = await rateLimitResponseAsync(response, configuration.now, (errorResponse) => readResponseWithDeadlineAsync(readGitHubErrorMessageAsync, errorResponse, configuration, signal));
|
|
40
|
+
const failure = requestError(response, subject, rateLimit);
|
|
41
|
+
const retryDelay = retryDelayMilliseconds(response, attempt, rateLimit);
|
|
42
|
+
const canRetry = attempt < MAX_REQUEST_ATTEMPTS - 1 &&
|
|
43
|
+
retryDelay !== undefined &&
|
|
44
|
+
waitedMilliseconds + retryDelay <= MAX_RETRY_WAIT_MILLISECONDS;
|
|
45
|
+
if (!canRetry)
|
|
46
|
+
throw failure;
|
|
47
|
+
await configuration.waitAsync(retryDelay, signal);
|
|
48
|
+
throwIfAborted(signal);
|
|
49
|
+
waitedMilliseconds += retryDelay;
|
|
50
|
+
}
|
|
51
|
+
throw new GitHubRepositoryDiscoveryError('api-failure', 'GitHub request retry limit was exceeded.');
|
|
52
|
+
}
|
|
53
|
+
/*** Read a response body under the same bounded cancellation contract as the network request. */
|
|
54
|
+
async function readResponseWithDeadlineAsync(readResponseAsync, response, configuration, signal) {
|
|
55
|
+
const deadline = createDeadline(signal, configuration.timeoutMilliseconds);
|
|
56
|
+
try {
|
|
57
|
+
return await readWithCancellationAsync(readResponseAsync(response), deadline.signal);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (signal?.aborted)
|
|
61
|
+
throw cancelledError();
|
|
62
|
+
if (deadline.timedOut())
|
|
63
|
+
throw timeoutError();
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
deadline.dispose();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/*** Fetch a GitHub response with a bounded deadline that remains distinguishable from caller cancellation. */
|
|
71
|
+
async function fetchResponseAsync(fetcher, configuration, url, signal) {
|
|
72
|
+
const deadline = createDeadline(signal, configuration.timeoutMilliseconds);
|
|
73
|
+
try {
|
|
74
|
+
return await readWithCancellationAsync(fetcher(url, {
|
|
75
|
+
headers: requestHeaders(configuration.authentication),
|
|
76
|
+
signal: deadline.signal,
|
|
77
|
+
}), deadline.signal);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (signal?.aborted)
|
|
81
|
+
throw cancelledError();
|
|
82
|
+
if (deadline.timedOut())
|
|
83
|
+
throw timeoutError();
|
|
84
|
+
if (isInterruptionError(error))
|
|
85
|
+
throw error;
|
|
86
|
+
throw new GitHubRepositoryDiscoveryError('api-failure', 'GitHub request failed.');
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
deadline.dispose();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/*** Create a request-local signal and release its timer and caller listener after each request. */
|
|
93
|
+
function createDeadline(signal, timeoutMilliseconds) {
|
|
94
|
+
const controller = new AbortController();
|
|
95
|
+
let timedOut = false;
|
|
96
|
+
const abortFromCaller = () => controller.abort();
|
|
97
|
+
signal?.addEventListener('abort', abortFromCaller, { once: true });
|
|
98
|
+
if (signal?.aborted)
|
|
99
|
+
controller.abort();
|
|
100
|
+
const timeout = setTimeout(() => {
|
|
101
|
+
timedOut = true;
|
|
102
|
+
controller.abort();
|
|
103
|
+
}, timeoutMilliseconds);
|
|
104
|
+
return {
|
|
105
|
+
dispose: () => {
|
|
106
|
+
clearTimeout(timeout);
|
|
107
|
+
signal?.removeEventListener('abort', abortFromCaller);
|
|
108
|
+
},
|
|
109
|
+
signal: controller.signal,
|
|
110
|
+
timedOut: () => timedOut,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/*** Race a response operation against cancellation and always remove the temporary abort listener. */
|
|
114
|
+
function readWithCancellationAsync(value, signal) {
|
|
115
|
+
if (!signal)
|
|
116
|
+
return value;
|
|
117
|
+
if (signal.aborted)
|
|
118
|
+
return Promise.reject(cancelledError());
|
|
119
|
+
return new Promise((resolve, reject) => {
|
|
120
|
+
const abort = () => reject(cancelledError());
|
|
121
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
122
|
+
value.then(resolve, reject).finally(() => signal.removeEventListener('abort', abort));
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/*** Calculate a retry delay without replacing a provider-required rate-limit wait with backoff. */
|
|
126
|
+
function retryDelayMilliseconds(response, attempt, rateLimit) {
|
|
127
|
+
if (rateLimit.isRateLimited)
|
|
128
|
+
return rateLimit.retryAfterMilliseconds;
|
|
129
|
+
if (response.status === 429 || response.status >= 500)
|
|
130
|
+
return Math.min(250 * 2 ** attempt, MAX_RETRY_DELAY_MILLISECONDS);
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
/*** Read only GitHub's structured error message and treat absent or malformed bodies as unknown. */
|
|
134
|
+
async function readGitHubErrorMessageAsync(response) {
|
|
135
|
+
try {
|
|
136
|
+
const body = await response.json();
|
|
137
|
+
if (typeof body !== 'object' || body === null)
|
|
138
|
+
return undefined;
|
|
139
|
+
const message = Reflect.get(body, 'message');
|
|
140
|
+
return typeof message === 'string' ? message : undefined;
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/*** Map provider failures to credential-free typed errors at the canonical adapter boundary. */
|
|
147
|
+
function requestError(response, subject, rateLimit) {
|
|
148
|
+
if (rateLimit.isRateLimited) {
|
|
149
|
+
return new GitHubRepositoryDiscoveryError('rate-limited', 'GitHub rate limit reached. Retry after the reported delay.', rateLimit.retryAfterMilliseconds);
|
|
150
|
+
}
|
|
151
|
+
if (response.status === 404 && subject === 'owner') {
|
|
152
|
+
return new GitHubRepositoryDiscoveryError('owner-not-found', 'GitHub owner was not found.');
|
|
153
|
+
}
|
|
154
|
+
if (response.status === 401 || response.status === 403) {
|
|
155
|
+
return new GitHubRepositoryDiscoveryError('permission-denied', 'GitHub denied access to this resource.');
|
|
156
|
+
}
|
|
157
|
+
return new GitHubRepositoryDiscoveryError('api-failure', `GitHub ${subject} request failed with HTTP ${response.status}.`);
|
|
158
|
+
}
|
|
159
|
+
/*** Wait for a retry while promptly cancelling the timer and removing its listener in every outcome. */
|
|
160
|
+
function waitForRetryAsync(milliseconds, signal) {
|
|
161
|
+
if (!signal)
|
|
162
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
163
|
+
if (signal.aborted)
|
|
164
|
+
return Promise.reject(cancelledError());
|
|
165
|
+
return new Promise((resolve, reject) => {
|
|
166
|
+
const done = () => signal.removeEventListener('abort', abort);
|
|
167
|
+
const abort = () => {
|
|
168
|
+
clearTimeout(timeout);
|
|
169
|
+
done();
|
|
170
|
+
reject(cancelledError());
|
|
171
|
+
};
|
|
172
|
+
const timeout = setTimeout(() => {
|
|
173
|
+
done();
|
|
174
|
+
resolve();
|
|
175
|
+
}, milliseconds);
|
|
176
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/*** Build GitHub request headers only when the caller explicitly selected environment authentication. */
|
|
180
|
+
function requestHeaders(authentication) {
|
|
181
|
+
const token = authentication === 'environment'
|
|
182
|
+
? (process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN)
|
|
183
|
+
: undefined;
|
|
184
|
+
return {
|
|
185
|
+
Accept: 'application/vnd.github+json',
|
|
186
|
+
'User-Agent': '@ankhorage/repository',
|
|
187
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
188
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/*** Identify a stable cancellation or timeout error created by this client. */
|
|
192
|
+
function isInterruptionError(error) {
|
|
193
|
+
return (error instanceof GitHubRepositoryDiscoveryError &&
|
|
194
|
+
(error.code === 'cancelled' || error.code === 'timeout'));
|
|
195
|
+
}
|
|
196
|
+
/*** Construct the stable cancellation error returned from the GitHub adapter boundary. */
|
|
197
|
+
function cancelledError() {
|
|
198
|
+
return new GitHubRepositoryDiscoveryError('cancelled', 'GitHub repository operation was cancelled.');
|
|
199
|
+
}
|
|
200
|
+
/*** Construct the stable timeout error without conflating it with an explicit caller cancellation. */
|
|
201
|
+
function timeoutError() {
|
|
202
|
+
return new GitHubRepositoryDiscoveryError('timeout', 'GitHub request timed out.');
|
|
203
|
+
}
|
|
204
|
+
/*** Stop before starting an adapter request when cancellation has already been requested. */
|
|
205
|
+
function throwIfAborted(signal) {
|
|
206
|
+
if (signal?.aborted)
|
|
207
|
+
throw cancelledError();
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=createGitHubHttpClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createGitHubHttpClient.js","sourceRoot":"","sources":["../../../../src/providers/github/adapters/createGitHubHttpClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createGitHubHttpClient.test.d.ts","sourceRoot":"","sources":["../../../../src/providers/github/adapters/createGitHubHttpClient.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test';
|
|
2
|
+
import { createGitHubHttpClient } from './createGitHubHttpClient.js';
|
|
3
|
+
test('omits inherited environment credentials for explicit anonymous requests', async () => {
|
|
4
|
+
const originalToken = process.env.GITHUB_TOKEN;
|
|
5
|
+
process.env.GITHUB_TOKEN = 'not-for-public-discovery';
|
|
6
|
+
const headers = [];
|
|
7
|
+
try {
|
|
8
|
+
const client = createGitHubHttpClient(((_input, init) => {
|
|
9
|
+
headers.push(new Headers(init?.headers));
|
|
10
|
+
return Promise.resolve(Response.json({ owner: 'public' }));
|
|
11
|
+
}), { authentication: 'anonymous' });
|
|
12
|
+
await client.requestJsonResponseAsync('https://api.github.com/users/ankhorage', 'owner');
|
|
13
|
+
expect(headers).toHaveLength(1);
|
|
14
|
+
expect(headers[0]?.has('authorization')).toBe(false);
|
|
15
|
+
}
|
|
16
|
+
finally {
|
|
17
|
+
if (originalToken === undefined)
|
|
18
|
+
delete process.env.GITHUB_TOKEN;
|
|
19
|
+
else
|
|
20
|
+
process.env.GITHUB_TOKEN = originalToken;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
test('cancels an in-flight request through its combined fetch signal', () => {
|
|
24
|
+
const controller = new AbortController();
|
|
25
|
+
const client = createGitHubHttpClient(((_input, init) => new Promise((_resolve, reject) => {
|
|
26
|
+
init?.signal?.addEventListener('abort', () => reject(new DOMException('Aborted', 'AbortError')));
|
|
27
|
+
})));
|
|
28
|
+
const request = client.requestJsonResponseAsync('https://api.github.com/users/ankhorage', 'owner', controller.signal);
|
|
29
|
+
controller.abort();
|
|
30
|
+
expect(request).rejects.toMatchObject({ code: 'cancelled' });
|
|
31
|
+
});
|
|
32
|
+
test('keeps a permission denial with available primary quota distinct from rate limiting', async () => {
|
|
33
|
+
let calls = 0;
|
|
34
|
+
const client = createGitHubHttpClient(((_input) => {
|
|
35
|
+
calls += 1;
|
|
36
|
+
return Promise.resolve(Response.json({ message: 'Resource not accessible by integration' }, {
|
|
37
|
+
status: 403,
|
|
38
|
+
headers: { 'x-ratelimit-remaining': '1', 'x-ratelimit-reset': '10' },
|
|
39
|
+
}));
|
|
40
|
+
}), { now: () => 10_000, waitAsync: () => Promise.resolve() });
|
|
41
|
+
expect(await requestFailureAsync(client)).toMatchObject({ code: 'permission-denied' });
|
|
42
|
+
expect(calls).toBe(1);
|
|
43
|
+
});
|
|
44
|
+
test('does not replace a provider-required rate-limit wait beyond the retry budget with backoff', async () => {
|
|
45
|
+
let calls = 0;
|
|
46
|
+
const waits = [];
|
|
47
|
+
const client = createGitHubHttpClient(((_input) => {
|
|
48
|
+
calls += 1;
|
|
49
|
+
return Promise.resolve(new Response('', { status: 429, headers: { 'retry-after': '120' } }));
|
|
50
|
+
}), { waitAsync: (milliseconds) => (waits.push(milliseconds), Promise.resolve()) });
|
|
51
|
+
expect(await requestFailureAsync(client)).toMatchObject({
|
|
52
|
+
code: 'rate-limited',
|
|
53
|
+
retryAfterMilliseconds: 120_000,
|
|
54
|
+
});
|
|
55
|
+
expect(calls).toBe(1);
|
|
56
|
+
expect(waits).toEqual([]);
|
|
57
|
+
});
|
|
58
|
+
test('keeps an exhausted-primary reset deadline for HTTP 429 when it is later than Retry-After', async () => {
|
|
59
|
+
let calls = 0;
|
|
60
|
+
const waits = [];
|
|
61
|
+
const client = createGitHubHttpClient(((_input) => {
|
|
62
|
+
calls += 1;
|
|
63
|
+
return Promise.resolve(new Response('', {
|
|
64
|
+
status: 429,
|
|
65
|
+
headers: {
|
|
66
|
+
'retry-after': '2',
|
|
67
|
+
'x-ratelimit-remaining': '0',
|
|
68
|
+
'x-ratelimit-reset': '121',
|
|
69
|
+
},
|
|
70
|
+
}));
|
|
71
|
+
}), {
|
|
72
|
+
now: () => 1_000,
|
|
73
|
+
waitAsync: (milliseconds) => (waits.push(milliseconds), Promise.resolve()),
|
|
74
|
+
});
|
|
75
|
+
expect(await requestFailureAsync(client)).toMatchObject({
|
|
76
|
+
code: 'rate-limited',
|
|
77
|
+
retryAfterMilliseconds: 120_000,
|
|
78
|
+
});
|
|
79
|
+
expect(calls).toBe(1);
|
|
80
|
+
expect(waits).toEqual([]);
|
|
81
|
+
});
|
|
82
|
+
test('keeps Retry-After for HTTP 429 when it is later than an exhausted-primary reset deadline', async () => {
|
|
83
|
+
let calls = 0;
|
|
84
|
+
const waits = [];
|
|
85
|
+
const client = createGitHubHttpClient(((_input) => {
|
|
86
|
+
calls += 1;
|
|
87
|
+
return Promise.resolve(new Response('', {
|
|
88
|
+
status: 429,
|
|
89
|
+
headers: {
|
|
90
|
+
'retry-after': '120',
|
|
91
|
+
'x-ratelimit-remaining': '0',
|
|
92
|
+
'x-ratelimit-reset': '3',
|
|
93
|
+
},
|
|
94
|
+
}));
|
|
95
|
+
}), {
|
|
96
|
+
now: () => 1_000,
|
|
97
|
+
waitAsync: (milliseconds) => (waits.push(milliseconds), Promise.resolve()),
|
|
98
|
+
});
|
|
99
|
+
expect(await requestFailureAsync(client)).toMatchObject({
|
|
100
|
+
code: 'rate-limited',
|
|
101
|
+
retryAfterMilliseconds: 120_000,
|
|
102
|
+
});
|
|
103
|
+
expect(calls).toBe(1);
|
|
104
|
+
expect(waits).toEqual([]);
|
|
105
|
+
});
|
|
106
|
+
test('retries HTTP 429 after an exhausted-primary reset deadline when Retry-After is absent', async () => {
|
|
107
|
+
let calls = 0;
|
|
108
|
+
const waits = [];
|
|
109
|
+
const client = createGitHubHttpClient(((_input) => {
|
|
110
|
+
calls += 1;
|
|
111
|
+
return Promise.resolve(calls === 1
|
|
112
|
+
? new Response('', {
|
|
113
|
+
status: 429,
|
|
114
|
+
headers: { 'x-ratelimit-remaining': '0', 'x-ratelimit-reset': '3' },
|
|
115
|
+
})
|
|
116
|
+
: Response.json({ owner: 'public' }));
|
|
117
|
+
}), {
|
|
118
|
+
now: () => 1_000,
|
|
119
|
+
waitAsync: (milliseconds) => (waits.push(milliseconds), Promise.resolve()),
|
|
120
|
+
});
|
|
121
|
+
expect(await client.requestJsonResponseAsync('https://api.github.com/users/ankhorage', 'owner')).toEqual({ link: null, value: { owner: 'public' } });
|
|
122
|
+
expect(calls).toBe(2);
|
|
123
|
+
expect(waits).toEqual([2_000]);
|
|
124
|
+
});
|
|
125
|
+
test('identifies a secondary limit despite available primary quota and reset metadata', async () => {
|
|
126
|
+
let calls = 0;
|
|
127
|
+
const client = createGitHubHttpClient(((_input) => {
|
|
128
|
+
calls += 1;
|
|
129
|
+
return Promise.resolve(Response.json({ message: 'You have exceeded a secondary rate limit. Please wait a few minutes.' }, {
|
|
130
|
+
status: 403,
|
|
131
|
+
headers: { 'x-ratelimit-remaining': '1', 'x-ratelimit-reset': '10' },
|
|
132
|
+
}));
|
|
133
|
+
}), { now: () => 10_000 });
|
|
134
|
+
expect(await requestFailureAsync(client)).toMatchObject({
|
|
135
|
+
code: 'rate-limited',
|
|
136
|
+
retryAfterMilliseconds: undefined,
|
|
137
|
+
});
|
|
138
|
+
expect(calls).toBe(1);
|
|
139
|
+
});
|
|
140
|
+
test('keeps a genuine GitHub permission denial distinct from a secondary rate limit', async () => {
|
|
141
|
+
let calls = 0;
|
|
142
|
+
const client = createGitHubHttpClient(((_input) => {
|
|
143
|
+
calls += 1;
|
|
144
|
+
return Promise.resolve(Response.json({ message: 'Resource not accessible by integration' }, { status: 403 }));
|
|
145
|
+
}));
|
|
146
|
+
expect(await requestFailureAsync(client)).toMatchObject({ code: 'permission-denied' });
|
|
147
|
+
expect(calls).toBe(1);
|
|
148
|
+
});
|
|
149
|
+
test('uses a primary reset delay only after primary quota exhaustion', async () => {
|
|
150
|
+
let calls = 0;
|
|
151
|
+
const client = createGitHubHttpClient(((_input) => {
|
|
152
|
+
calls += 1;
|
|
153
|
+
return Promise.resolve(new Response('', {
|
|
154
|
+
status: 403,
|
|
155
|
+
headers: { 'x-ratelimit-remaining': '0', 'x-ratelimit-reset': '121' },
|
|
156
|
+
}));
|
|
157
|
+
}), { now: () => 1_000, waitAsync: () => Promise.resolve() });
|
|
158
|
+
expect(await requestFailureAsync(client)).toMatchObject({
|
|
159
|
+
code: 'rate-limited',
|
|
160
|
+
retryAfterMilliseconds: 120_000,
|
|
161
|
+
});
|
|
162
|
+
expect(calls).toBe(1);
|
|
163
|
+
});
|
|
164
|
+
test('recognizes Retry-After as rate-limit evidence for HTTP 403', async () => {
|
|
165
|
+
let calls = 0;
|
|
166
|
+
const waits = [];
|
|
167
|
+
const client = createGitHubHttpClient(((_input) => {
|
|
168
|
+
calls += 1;
|
|
169
|
+
return Promise.resolve(calls === 1
|
|
170
|
+
? new Response('', { status: 403, headers: { 'retry-after': '2' } })
|
|
171
|
+
: Response.json({ owner: 'public' }));
|
|
172
|
+
}), { waitAsync: (milliseconds) => (waits.push(milliseconds), Promise.resolve()) });
|
|
173
|
+
const response = await client.requestJsonResponseAsync('https://api.github.com/users/ankhorage', 'owner');
|
|
174
|
+
expect(response).toEqual({ link: null, value: { owner: 'public' } });
|
|
175
|
+
expect(calls).toBe(2);
|
|
176
|
+
expect(waits).toEqual([2_000]);
|
|
177
|
+
});
|
|
178
|
+
test('times out while GitHub response processing is still pending', () => {
|
|
179
|
+
const client = createGitHubHttpClient(((_input) => Promise.resolve({
|
|
180
|
+
headers: new Headers(),
|
|
181
|
+
json: () => new Promise(() => undefined),
|
|
182
|
+
ok: true,
|
|
183
|
+
})), { timeoutMilliseconds: 0 });
|
|
184
|
+
expect(client.requestJsonResponseAsync('https://api.github.com/users/ankhorage', 'owner')).rejects.toMatchObject({ code: 'timeout' });
|
|
185
|
+
});
|
|
186
|
+
async function requestFailureAsync(client) {
|
|
187
|
+
try {
|
|
188
|
+
await client.requestJsonResponseAsync('https://api.github.com/users/ankhorage', 'owner');
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
return error;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=createGitHubHttpClient.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createGitHubHttpClient.test.js","sourceRoot":"","sources":["../../../../src/providers/github/adapters/createGitHubHttpClient.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { GitHubRepositoryDiscoveryGateway } from '../ports/GitHubRepositoryDiscoveryGateway.js';
|
|
2
|
+
type RequestJsonResponseAsync = (url: string, subject: string, signal: AbortSignal | undefined) => Promise<{
|
|
3
|
+
readonly value: unknown;
|
|
4
|
+
readonly link: string | null;
|
|
5
|
+
}>;
|
|
6
|
+
/** Create the discovery adapter from the shared GitHub HTTP requester. */
|
|
7
|
+
export declare function createGitHubHttpRepositoryDiscoveryGateway(requestJsonResponseAsync: RequestJsonResponseAsync): GitHubRepositoryDiscoveryGateway;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=createGitHubHttpRepositoryDiscoveryGateway.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createGitHubHttpRepositoryDiscoveryGateway.d.ts","sourceRoot":"","sources":["../../../../src/providers/github/adapters/createGitHubHttpRepositoryDiscoveryGateway.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,gCAAgC,EAEjC,MAAM,8CAA8C,CAAC;AAEtD,KAAK,wBAAwB,GAAG,CAC9B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,WAAW,GAAG,SAAS,KAC5B,OAAO,CAAC;IAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAAC;AAIxE,0EAA0E;AAC1E,wBAAgB,0CAA0C,CACxD,wBAAwB,EAAE,wBAAwB,GACjD,gCAAgC,CAOlC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { GitHubRepositoryDiscoveryError } from '../definitions/GitHubRepositoryDiscoveryResult.js';
|
|
2
|
+
const API_ORIGIN = 'https://api.github.com';
|
|
3
|
+
/** Create the discovery adapter from the shared GitHub HTTP requester. */
|
|
4
|
+
export function createGitHubHttpRepositoryDiscoveryGateway(requestJsonResponseAsync) {
|
|
5
|
+
return {
|
|
6
|
+
resolveOwnerAsync: (owner, signal) => resolveOwnerAsync(requestJsonResponseAsync, owner, signal),
|
|
7
|
+
listOwnerRepositoriesAsync: (owner, signal) => listOwnerRepositoriesAsync(requestJsonResponseAsync, owner, signal),
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/** Resolve a login through GitHub rather than inferring whether it names a user or organization. */
|
|
11
|
+
async function resolveOwnerAsync(requestJsonResponseAsync, owner, signal) {
|
|
12
|
+
const { value } = await requestJsonResponseAsync(`${API_ORIGIN}/users/${encodeURIComponent(owner)}`, 'owner', signal);
|
|
13
|
+
const record = requireRecord(value, 'owner');
|
|
14
|
+
const type = requireString(record.type, 'owner type');
|
|
15
|
+
if (type !== 'User' && type !== 'Organization') {
|
|
16
|
+
throw new GitHubRepositoryDiscoveryError('api-failure', 'GitHub returned an unsupported owner type.');
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
login: requireString(record.login, 'owner login'),
|
|
20
|
+
type: type === 'Organization' ? 'organization' : 'user',
|
|
21
|
+
url: requireString(record.html_url, 'owner URL'),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Read every page of repositories visible through GitHub's owner listing endpoints. */
|
|
25
|
+
async function listOwnerRepositoriesAsync(requestJsonResponseAsync, owner, signal) {
|
|
26
|
+
const route = owner.type === 'organization' ? 'orgs' : 'users';
|
|
27
|
+
let nextUrl = `${API_ORIGIN}/${route}/${encodeURIComponent(owner.login)}/repos?per_page=100&type=all&sort=full_name&direction=asc`;
|
|
28
|
+
const repositories = new Map();
|
|
29
|
+
while (nextUrl !== undefined) {
|
|
30
|
+
const page = await requestJsonResponseAsync(nextUrl, 'repository list', signal);
|
|
31
|
+
if (!Array.isArray(page.value)) {
|
|
32
|
+
throw new GitHubRepositoryDiscoveryError('api-failure', 'GitHub returned an invalid repository list.');
|
|
33
|
+
}
|
|
34
|
+
for (const value of page.value) {
|
|
35
|
+
const repository = parseDiscoveredRepository(value);
|
|
36
|
+
repositories.set(repository.id, repository);
|
|
37
|
+
}
|
|
38
|
+
nextUrl = nextPageUrl(page.link);
|
|
39
|
+
}
|
|
40
|
+
return [...repositories.values()].sort(compareRepositories);
|
|
41
|
+
}
|
|
42
|
+
/** Parse one listed repository without exposing API-only response data to callers. */
|
|
43
|
+
function parseDiscoveredRepository(value) {
|
|
44
|
+
const record = requireRecord(value, 'repository');
|
|
45
|
+
const { id } = record;
|
|
46
|
+
if (typeof id !== 'number' || !Number.isSafeInteger(id) || id < 1) {
|
|
47
|
+
throw new GitHubRepositoryDiscoveryError('api-failure', 'GitHub returned an invalid repository ID.');
|
|
48
|
+
}
|
|
49
|
+
const owner = requireRecord(record.owner, 'repository owner');
|
|
50
|
+
return {
|
|
51
|
+
id: String(id),
|
|
52
|
+
name: requireString(record.name, 'repository name'),
|
|
53
|
+
owner: requireString(owner.login, 'repository owner login'),
|
|
54
|
+
url: requireString(record.html_url, 'repository URL'),
|
|
55
|
+
visibility: record.private === true ? 'private' : 'public',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** Select the next GitHub pagination target while rejecting an unexpected cross-origin link. */
|
|
59
|
+
function nextPageUrl(link) {
|
|
60
|
+
if (!link)
|
|
61
|
+
return undefined;
|
|
62
|
+
const match = /<([^>]+)>;\s*rel="next"/u.exec(link);
|
|
63
|
+
if (!match?.[1])
|
|
64
|
+
return undefined;
|
|
65
|
+
const url = new URL(match[1]);
|
|
66
|
+
if (url.origin !== API_ORIGIN) {
|
|
67
|
+
throw new GitHubRepositoryDiscoveryError('api-failure', 'GitHub returned an invalid pagination URL.');
|
|
68
|
+
}
|
|
69
|
+
return url.href;
|
|
70
|
+
}
|
|
71
|
+
/** Sort repository identities independently of the provider's page ordering. */
|
|
72
|
+
function compareRepositories(left, right) {
|
|
73
|
+
const leftIdentity = `${left.owner.toLowerCase()}/${left.name.toLowerCase()}`;
|
|
74
|
+
const rightIdentity = `${right.owner.toLowerCase()}/${right.name.toLowerCase()}`;
|
|
75
|
+
return leftIdentity < rightIdentity ? -1 : leftIdentity > rightIdentity ? 1 : 0;
|
|
76
|
+
}
|
|
77
|
+
/** Narrow unknown JSON values to object records. */
|
|
78
|
+
function requireRecord(value, subject) {
|
|
79
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
80
|
+
throw new GitHubRepositoryDiscoveryError('api-failure', `GitHub returned invalid ${subject}.`);
|
|
81
|
+
}
|
|
82
|
+
return Object.fromEntries(Object.entries(value));
|
|
83
|
+
}
|
|
84
|
+
/** Require a non-empty provider string field. */
|
|
85
|
+
function requireString(value, subject) {
|
|
86
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
87
|
+
throw new GitHubRepositoryDiscoveryError('api-failure', `GitHub returned invalid ${subject}.`);
|
|
88
|
+
}
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=createGitHubHttpRepositoryDiscoveryGateway.js.map
|