@shipfox/api-integration-github 12.7.0 → 13.1.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +6 -0
- package/dist/api/client.d.ts +13 -3
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +83 -4
- package/dist/api/client.js.map +1 -1
- package/dist/core/agent-tools.d.ts.map +1 -1
- package/dist/core/agent-tools.js +2 -6
- package/dist/core/agent-tools.js.map +1 -1
- package/dist/core/bot-identity.d.ts +4 -0
- package/dist/core/bot-identity.d.ts.map +1 -0
- package/dist/core/bot-identity.js +15 -0
- package/dist/core/bot-identity.js.map +1 -0
- package/dist/core/source-control.d.ts +2 -1
- package/dist/core/source-control.d.ts.map +1 -1
- package/dist/core/source-control.js +15 -10
- package/dist/core/source-control.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/api/client.test.ts +206 -18
- package/src/api/client.ts +130 -8
- package/src/core/agent-tools.ts +2 -9
- package/src/core/bot-identity.test.ts +19 -0
- package/src/core/bot-identity.ts +19 -0
- package/src/core/source-control.test.ts +87 -1
- package/src/core/source-control.ts +27 -11
- package/tsconfig.build.tsbuildinfo +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
$ shipfox-swc
|
|
2
|
-
Successfully compiled:
|
|
2
|
+
Successfully compiled: 32 files with swc (434.7ms)
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @shipfox/api-integration-github
|
|
2
2
|
|
|
3
|
+
## 13.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 7ae5396: Attributes new runner commits to the GitHub App bot account. Existing runner workspaces keep their first configured author until recreated. Update downstream author filters that match the previous App ID email address.
|
|
8
|
+
|
|
3
9
|
## 12.7.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/dist/api/client.d.ts
CHANGED
|
@@ -42,7 +42,17 @@ export interface GithubUserInstallationPage {
|
|
|
42
42
|
installationIds: number[];
|
|
43
43
|
nextCursor: string | null;
|
|
44
44
|
}
|
|
45
|
-
export interface
|
|
45
|
+
export interface GithubBotUser {
|
|
46
|
+
id: number;
|
|
47
|
+
login: string;
|
|
48
|
+
}
|
|
49
|
+
export interface GithubBotUserClient {
|
|
50
|
+
getBotUser(input: {
|
|
51
|
+
username: string;
|
|
52
|
+
installationAccessToken: string;
|
|
53
|
+
}): Promise<GithubBotUser>;
|
|
54
|
+
}
|
|
55
|
+
export interface GithubApiClient extends Partial<GithubBotUserClient> {
|
|
46
56
|
exchangeOAuthCode(code: string): Promise<string>;
|
|
47
57
|
listUserInstallations(input: {
|
|
48
58
|
userAccessToken: string;
|
|
@@ -85,6 +95,6 @@ export interface GithubInstallationAccessToken {
|
|
|
85
95
|
expiresAt: Date;
|
|
86
96
|
permissions?: Record<string, 'read' | 'write' | 'admin'> | undefined;
|
|
87
97
|
}
|
|
88
|
-
export declare function createGithubApiClient(): GithubApiClient;
|
|
89
|
-
export declare function mapGithubError<T>(operation: () => Promise<T>, notFoundReason?: 'repository-not-found' | 'installation-not-found' | 'file-not-found'): Promise<T>;
|
|
98
|
+
export declare function createGithubApiClient(): GithubApiClient & GithubBotUserClient;
|
|
99
|
+
export declare function mapGithubError<T>(operation: () => Promise<T>, notFoundReason?: 'repository-not-found' | 'installation-not-found' | 'file-not-found' | 'provider-rejected'): Promise<T>;
|
|
90
100
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/api/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
|
package/dist/api/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
|
-
import { MAX_REPOSITORY_FILE_BYTES } from '@shipfox/api-integration-spi';
|
|
2
|
+
import { isRecord, MAX_REPOSITORY_FILE_BYTES } from '@shipfox/api-integration-spi';
|
|
3
|
+
import { logger } from '@shipfox/node-opentelemetry';
|
|
3
4
|
import ky, { HTTPError, TimeoutError } from 'ky';
|
|
4
5
|
import { App, Octokit, RequestError } from 'octokit';
|
|
5
6
|
import { config, normalizedGithubApiBaseUrl, normalizedGithubPrivateKey } from '#config.js';
|
|
@@ -9,6 +10,7 @@ import { getGithubInstallationOctokit, githubInstallationTokenFormatPlugin } fro
|
|
|
9
10
|
const NEXT_PAGE_RE = /[?&]page=(\d+)/;
|
|
10
11
|
const TRAILING_SLASHES_RE = /\/+$/;
|
|
11
12
|
const MAX_TREE_WALK_DEPTH = 10;
|
|
13
|
+
const GITHUB_API_TIMEOUT_MS = 10_000;
|
|
12
14
|
export function createGithubApiClient() {
|
|
13
15
|
return new OctokitGithubApiClient();
|
|
14
16
|
}
|
|
@@ -29,6 +31,35 @@ let OctokitGithubApiClient = class OctokitGithubApiClient {
|
|
|
29
31
|
}
|
|
30
32
|
return body.access_token;
|
|
31
33
|
}
|
|
34
|
+
getBotUser(input) {
|
|
35
|
+
const cacheKey = input.username.trim().toLowerCase();
|
|
36
|
+
const cached = this.botUsers.get(cacheKey);
|
|
37
|
+
if (cached) return Promise.resolve(cached);
|
|
38
|
+
const pending = this.botUserLookups.get(cacheKey);
|
|
39
|
+
if (pending?.installationAccessToken === input.installationAccessToken) {
|
|
40
|
+
return pending.promise;
|
|
41
|
+
}
|
|
42
|
+
const lookup = this.fetchBotUser(input).then((botUser)=>{
|
|
43
|
+
const resolved = this.botUsers.get(cacheKey);
|
|
44
|
+
if (resolved) return resolved;
|
|
45
|
+
this.botUsers.set(cacheKey, botUser);
|
|
46
|
+
logger().info({
|
|
47
|
+
githubAppBotLogin: botUser.login,
|
|
48
|
+
githubAppBotUserId: botUser.id
|
|
49
|
+
}, 'Resolved GitHub App bot identity');
|
|
50
|
+
return botUser;
|
|
51
|
+
});
|
|
52
|
+
const trackedLookup = lookup.finally(()=>{
|
|
53
|
+
if (this.botUserLookups.get(cacheKey)?.promise === trackedLookup) {
|
|
54
|
+
this.botUserLookups.delete(cacheKey);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
this.botUserLookups.set(cacheKey, {
|
|
58
|
+
installationAccessToken: input.installationAccessToken,
|
|
59
|
+
promise: trackedLookup
|
|
60
|
+
});
|
|
61
|
+
return trackedLookup;
|
|
62
|
+
}
|
|
32
63
|
async listUserInstallations(input) {
|
|
33
64
|
const octokit = new Octokit({
|
|
34
65
|
auth: input.userAccessToken,
|
|
@@ -218,6 +249,49 @@ let OctokitGithubApiClient = class OctokitGithubApiClient {
|
|
|
218
249
|
}
|
|
219
250
|
return this.app;
|
|
220
251
|
}
|
|
252
|
+
async fetchBotUser(input) {
|
|
253
|
+
const octokit = new Octokit({
|
|
254
|
+
auth: input.installationAccessToken,
|
|
255
|
+
baseUrl: normalizedGithubApiBaseUrl()
|
|
256
|
+
});
|
|
257
|
+
let response;
|
|
258
|
+
try {
|
|
259
|
+
response = await mapGithubError(()=>octokit.rest.users.getByUsername({
|
|
260
|
+
username: input.username,
|
|
261
|
+
request: {
|
|
262
|
+
signal: AbortSignal.timeout(GITHUB_API_TIMEOUT_MS)
|
|
263
|
+
}
|
|
264
|
+
}), 'provider-rejected');
|
|
265
|
+
} catch (error) {
|
|
266
|
+
if (error instanceof GithubIntegrationProviderError && error.status === 404) {
|
|
267
|
+
throw new GithubIntegrationProviderError('provider-rejected', `Configured GitHub bot user ${input.username} was not found`, undefined, error.status);
|
|
268
|
+
}
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
const data = response.data;
|
|
272
|
+
if (!isRecord(data)) {
|
|
273
|
+
throw new GithubIntegrationProviderError('malformed-provider-response', 'GitHub bot user response is missing required fields');
|
|
274
|
+
}
|
|
275
|
+
const { id, login, type } = data;
|
|
276
|
+
if (typeof id !== 'number' || !Number.isSafeInteger(id) || id <= 0 || typeof login !== 'string' || login.trim().length === 0) {
|
|
277
|
+
throw new GithubIntegrationProviderError('malformed-provider-response', 'GitHub bot user response is missing required fields');
|
|
278
|
+
}
|
|
279
|
+
if (type !== 'Bot') {
|
|
280
|
+
throw new GithubIntegrationProviderError('malformed-provider-response', 'Configured GitHub username is not a bot account');
|
|
281
|
+
}
|
|
282
|
+
const canonicalLogin = login.trim();
|
|
283
|
+
if (canonicalLogin.toLowerCase() !== input.username.trim().toLowerCase()) {
|
|
284
|
+
throw new GithubIntegrationProviderError('malformed-provider-response', 'GitHub bot user response did not match the configured username');
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
id,
|
|
288
|
+
login: canonicalLogin
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
constructor(){
|
|
292
|
+
this.botUsers = new Map();
|
|
293
|
+
this.botUserLookups = new Map();
|
|
294
|
+
}
|
|
221
295
|
};
|
|
222
296
|
async function mapGithubOAuthError(operation) {
|
|
223
297
|
try {
|
|
@@ -243,6 +317,9 @@ export async function mapGithubError(operation, notFoundReason = 'repository-not
|
|
|
243
317
|
return await operation();
|
|
244
318
|
} catch (error) {
|
|
245
319
|
if (error instanceof GithubIntegrationProviderError) throw error;
|
|
320
|
+
if (isGithubTimeoutError(error)) {
|
|
321
|
+
throw new GithubIntegrationProviderError('timeout', 'GitHub request timed out');
|
|
322
|
+
}
|
|
246
323
|
if (error instanceof RequestError) {
|
|
247
324
|
if (error.status === 404) {
|
|
248
325
|
throw new GithubIntegrationProviderError(notFoundReason, error.message, undefined, error.status);
|
|
@@ -260,12 +337,14 @@ export async function mapGithubError(operation, notFoundReason = 'repository-not
|
|
|
260
337
|
throw new GithubIntegrationProviderError('provider-rejected', error.message, undefined, error.status);
|
|
261
338
|
}
|
|
262
339
|
}
|
|
263
|
-
if (error instanceof Error && error.name === 'AbortError') {
|
|
264
|
-
throw new GithubIntegrationProviderError('timeout', 'GitHub request timed out');
|
|
265
|
-
}
|
|
266
340
|
throw error;
|
|
267
341
|
}
|
|
268
342
|
}
|
|
343
|
+
function isGithubTimeoutError(error) {
|
|
344
|
+
if (!(error instanceof Error)) return false;
|
|
345
|
+
if (error.name === 'AbortError' || error.name === 'TimeoutError') return true;
|
|
346
|
+
return error.cause instanceof Error && isGithubTimeoutError(error.cause);
|
|
347
|
+
}
|
|
269
348
|
function isGithubRateLimitError(error) {
|
|
270
349
|
return error.status === 403 && error.response?.headers['x-ratelimit-remaining'] === '0';
|
|
271
350
|
}
|
package/dist/api/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/api/client.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport {MAX_REPOSITORY_FILE_BYTES} from '@shipfox/api-integration-spi';\nimport ky, {HTTPError, TimeoutError} from 'ky';\nimport {App, Octokit, RequestError} from 'octokit';\nimport {config, normalizedGithubApiBaseUrl, normalizedGithubPrivateKey} from '#config.js';\nimport {GithubIntegrationProviderError} from '#core/errors.js';\nimport {recordInstallationTokenFormat} from '#metrics/index.js';\nimport {\n getGithubInstallationOctokit,\n githubInstallationTokenFormatPlugin,\n} from './github-octokit.js';\n\nconst NEXT_PAGE_RE = /[?&]page=(\\d+)/;\nconst TRAILING_SLASHES_RE = /\\/+$/;\nconst MAX_TREE_WALK_DEPTH = 10;\n\nexport interface GithubAccount {\n login: string;\n type: string;\n}\n\nexport interface GithubInstallationDetails {\n id: number;\n account: GithubAccount;\n repositorySelection: string;\n suspendedAt: Date | null;\n htmlUrl: string;\n raw: Record<string, unknown>;\n}\n\nexport interface GithubRepository {\n id: number;\n ownerLogin: string;\n name: string;\n fullName: string;\n defaultBranch: string;\n private: boolean;\n visibility?: string | undefined;\n cloneUrl: string;\n htmlUrl: string;\n}\n\nexport interface GithubRepositoryPage {\n repositories: GithubRepository[];\n nextCursor: string | null;\n}\n\nexport interface GithubFileEntry {\n path: string;\n size: number | null;\n}\n\nexport interface GithubFilePage {\n files: GithubFileEntry[];\n nextCursor: string | null;\n}\n\nexport interface GithubFileContent {\n path: string;\n content: string;\n size: number;\n}\n\nexport interface GithubUserInstallationPage {\n installationIds: number[];\n nextCursor: string | null;\n}\n\nexport interface GithubApiClient {\n exchangeOAuthCode(code: string): Promise<string>;\n listUserInstallations(input: {\n userAccessToken: string;\n cursor?: string | undefined;\n }): Promise<GithubUserInstallationPage>;\n getInstallation(installationId: number): Promise<GithubInstallationDetails>;\n listInstallationRepositories(input: {\n installationId: number;\n limit: number;\n cursor?: string | undefined;\n }): Promise<GithubRepositoryPage>;\n getRepository(input: {installationId: number; repositoryId: number}): Promise<GithubRepository>;\n listRepositoryFiles(input: {\n installationId: number;\n repositoryId: number;\n ref: string;\n prefix: string;\n limit: number;\n cursor?: string | undefined;\n }): Promise<GithubFilePage>;\n fetchRepositoryFile(input: {\n installationId: number;\n repositoryId: number;\n ref: string;\n path: string;\n }): Promise<GithubFileContent>;\n createInstallationAccessToken(input: {\n installationId: number;\n repositoryId: number;\n permissions?: {contents: 'read' | 'write'} | undefined;\n }): Promise<GithubInstallationAccessToken>;\n}\n\nexport interface GithubInstallationAccessToken {\n token: string;\n expiresAt: Date;\n permissions?: Record<string, 'read' | 'write' | 'admin'> | undefined;\n}\n\nexport function createGithubApiClient(): GithubApiClient {\n return new OctokitGithubApiClient();\n}\n\nclass OctokitGithubApiClient implements GithubApiClient {\n private app: App | undefined;\n\n async exchangeOAuthCode(code: string): Promise<string> {\n const body = await mapGithubOAuthError(() =>\n ky\n .post('https://github.com/login/oauth/access_token', {\n headers: {accept: 'application/json'},\n json: {\n client_id: config.GITHUB_APP_CLIENT_ID,\n client_secret: config.GITHUB_APP_CLIENT_SECRET,\n code,\n },\n })\n .json<{access_token?: unknown}>(),\n );\n\n if (typeof body.access_token !== 'string') {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub OAuth response did not include an access token',\n );\n }\n return body.access_token;\n }\n\n async listUserInstallations(input: {\n userAccessToken: string;\n cursor?: string | undefined;\n }): Promise<GithubUserInstallationPage> {\n const octokit = new Octokit({\n auth: input.userAccessToken,\n baseUrl: normalizedGithubApiBaseUrl(),\n });\n const page = cursorToPage(input.cursor);\n const response = await mapGithubError(() =>\n octokit.request('GET /user/installations', {\n per_page: 100,\n page,\n }),\n );\n\n const installations = response.data.installations ?? [];\n return {\n installationIds: installations.map((installation) => installation.id),\n nextCursor: nextCursorFromLink(response.headers.link),\n };\n }\n\n async getInstallation(installationId: number): Promise<GithubInstallationDetails> {\n const response = await mapGithubError(() =>\n this.getApp().octokit.rest.apps.getInstallation({installation_id: installationId}),\n );\n const data = response.data;\n const account = data.account;\n if (!account) {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub installation response did not include an account',\n );\n }\n\n const login = 'login' in account ? account.login : account.slug;\n return {\n id: data.id,\n account: {\n login,\n type: 'type' in account ? account.type : 'Enterprise',\n },\n repositorySelection: data.repository_selection,\n suspendedAt: data.suspended_at ? new Date(data.suspended_at) : null,\n htmlUrl: data.html_url,\n raw: data as unknown as Record<string, unknown>,\n };\n }\n\n async listInstallationRepositories(input: {\n installationId: number;\n limit: number;\n cursor?: string | undefined;\n }): Promise<GithubRepositoryPage> {\n const octokit = await mapGithubError(() =>\n getGithubInstallationOctokit(this.getApp(), input.installationId),\n );\n const page = cursorToPage(input.cursor);\n const response = await mapGithubError(() =>\n octokit.rest.apps.listReposAccessibleToInstallation({\n per_page: input.limit,\n page,\n }),\n );\n\n return {\n repositories: response.data.repositories.map(toGithubRepository),\n nextCursor: nextCursorFromLink(response.headers.link),\n };\n }\n\n async getRepository(input: {\n installationId: number;\n repositoryId: number;\n }): Promise<GithubRepository> {\n const octokit = await mapGithubError(() =>\n getGithubInstallationOctokit(this.getApp(), input.installationId),\n );\n const response = await mapGithubError(() =>\n octokit.request('GET /repositories/{repository_id}', {\n repository_id: input.repositoryId,\n }),\n );\n\n return toGithubRepository(response.data);\n }\n\n async listRepositoryFiles(input: {\n installationId: number;\n repositoryId: number;\n ref: string;\n prefix: string;\n limit: number;\n cursor?: string | undefined;\n }): Promise<GithubFilePage> {\n const octokit = await mapGithubError(() =>\n getGithubInstallationOctokit(this.getApp(), input.installationId),\n );\n const repository = await this.getRepository({\n installationId: input.installationId,\n repositoryId: input.repositoryId,\n });\n const owner = repository.ownerLogin;\n const repo = repository.name;\n const startPath = input.prefix.replace(TRAILING_SLASHES_RE, '');\n const offset = input.cursor ? Number.parseInt(input.cursor, 10) : 0;\n const start = Number.isNaN(offset) || offset < 0 ? 0 : offset;\n const collected: GithubFileEntry[] = [];\n const overflowLimit = start + input.limit + 1;\n\n type GetContentData = Awaited<ReturnType<typeof octokit.rest.repos.getContent>>['data'];\n\n const walk = async (path: string, depth: number): Promise<void> => {\n if (collected.length >= overflowLimit) return;\n if (depth > MAX_TREE_WALK_DEPTH) return;\n\n let data: GetContentData;\n try {\n const response = await mapGithubError(\n () =>\n octokit.rest.repos.getContent({\n owner,\n repo,\n path,\n ref: input.ref,\n }),\n 'file-not-found',\n );\n data = response.data;\n } catch (error) {\n if (error instanceof GithubIntegrationProviderError && error.reason === 'file-not-found') {\n return;\n }\n throw error;\n }\n\n if (!Array.isArray(data)) {\n if (data.type === 'file' && data.path) {\n collected.push({path: data.path, size: typeof data.size === 'number' ? data.size : null});\n }\n return;\n }\n\n const entries = [...data].sort((a, b) => (a.path ?? '').localeCompare(b.path ?? ''));\n for (const entry of entries) {\n if (collected.length >= overflowLimit) return;\n if (!entry.path) continue;\n if (entry.type === 'file') {\n collected.push({\n path: entry.path,\n size: typeof entry.size === 'number' ? entry.size : null,\n });\n } else if (entry.type === 'dir') {\n await walk(entry.path, depth + 1);\n }\n }\n };\n\n await walk(startPath, 0);\n\n const sorted = collected.sort((a, b) => a.path.localeCompare(b.path));\n const page = sorted.slice(start, start + input.limit);\n const consumed = start + page.length;\n const hasMore = consumed < sorted.length;\n\n return {\n files: page,\n nextCursor: hasMore ? String(consumed) : null,\n };\n }\n\n async fetchRepositoryFile(input: {\n installationId: number;\n repositoryId: number;\n ref: string;\n path: string;\n }): Promise<GithubFileContent> {\n const octokit = await mapGithubError(() =>\n getGithubInstallationOctokit(this.getApp(), input.installationId),\n );\n const repository = await this.getRepository({\n installationId: input.installationId,\n repositoryId: input.repositoryId,\n });\n const response = await mapGithubError(\n () =>\n octokit.rest.repos.getContent({\n owner: repository.ownerLogin,\n repo: repository.name,\n path: input.path,\n ref: input.ref,\n }),\n 'file-not-found',\n );\n const data = response.data;\n\n if (Array.isArray(data) || data.type !== 'file') {\n throw new GithubIntegrationProviderError('file-not-found', 'GitHub path is not a file');\n }\n if (data.size > MAX_REPOSITORY_FILE_BYTES) {\n throw new GithubIntegrationProviderError(\n 'content-too-large',\n 'GitHub file content is larger than the supported limit',\n );\n }\n if (typeof data.content !== 'string' || data.encoding !== 'base64') {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub file response did not include base64 content',\n );\n }\n\n return {\n path: data.path,\n size: data.size,\n content: Buffer.from(data.content, 'base64').toString('utf8'),\n };\n }\n\n async createInstallationAccessToken(input: {\n installationId: number;\n repositoryId: number;\n permissions?: {contents: 'read' | 'write'} | undefined;\n }): Promise<GithubInstallationAccessToken> {\n const response = await mapGithubError(() =>\n this.getApp().octokit.rest.apps.createInstallationAccessToken({\n installation_id: input.installationId,\n repository_ids: [input.repositoryId],\n permissions: input.permissions ?? {contents: 'read'},\n }),\n );\n\n if (typeof response.data.token !== 'string') {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub installation access token response did not include a token',\n );\n }\n\n recordInstallationTokenFormat(response.data.token);\n\n const expiresAt = new Date(response.data.expires_at);\n if (Number.isNaN(expiresAt.getTime())) {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub installation access token response did not include a valid expiry',\n );\n }\n\n return {\n token: response.data.token,\n expiresAt,\n ...(response.data.permissions === undefined ? {} : {permissions: response.data.permissions}),\n };\n }\n\n private getApp(): App {\n if (!this.app) {\n this.app = new App({\n appId: config.GITHUB_APP_ID,\n privateKey: normalizedGithubPrivateKey(),\n Octokit: Octokit.plugin(githubInstallationTokenFormatPlugin).defaults({\n baseUrl: normalizedGithubApiBaseUrl(),\n }),\n });\n }\n return this.app;\n }\n}\n\nasync function mapGithubOAuthError<T>(operation: () => Promise<T>): Promise<T> {\n try {\n return await operation();\n } catch (error) {\n if (error instanceof HTTPError) {\n if (error.response.status === 429) {\n throw new GithubIntegrationProviderError(\n 'rate-limited',\n 'GitHub OAuth code exchange failed',\n retryAfterSecondsFromHeaders(error.response.headers),\n );\n }\n if (error.response.status >= 500) {\n throw new GithubIntegrationProviderError(\n 'provider-unavailable',\n 'GitHub OAuth code exchange failed',\n );\n }\n throw new GithubIntegrationProviderError(\n 'access-denied',\n 'GitHub OAuth code exchange failed',\n );\n }\n if (error instanceof TimeoutError) {\n throw new GithubIntegrationProviderError('timeout', 'GitHub OAuth request timed out');\n }\n throw error;\n }\n}\n\nexport async function mapGithubError<T>(\n operation: () => Promise<T>,\n notFoundReason:\n | 'repository-not-found'\n | 'installation-not-found'\n | 'file-not-found' = 'repository-not-found',\n): Promise<T> {\n try {\n return await operation();\n } catch (error) {\n if (error instanceof GithubIntegrationProviderError) throw error;\n if (error instanceof RequestError) {\n if (error.status === 404) {\n throw new GithubIntegrationProviderError(\n notFoundReason,\n error.message,\n undefined,\n error.status,\n );\n }\n if (error.status === 429 || isGithubRateLimitError(error)) {\n throw new GithubIntegrationProviderError(\n 'rate-limited',\n error.message,\n retryAfterSeconds(error),\n error.status,\n );\n }\n if (error.status === 401 || error.status === 403) {\n throw new GithubIntegrationProviderError(\n 'access-denied',\n error.message,\n retryAfterSeconds(error),\n error.status,\n );\n }\n if (error.status >= 500) {\n throw new GithubIntegrationProviderError(\n 'provider-unavailable',\n error.message,\n undefined,\n error.status,\n );\n }\n if (error.status >= 400) {\n throw new GithubIntegrationProviderError(\n 'provider-rejected',\n error.message,\n undefined,\n error.status,\n );\n }\n }\n if (error instanceof Error && error.name === 'AbortError') {\n throw new GithubIntegrationProviderError('timeout', 'GitHub request timed out');\n }\n throw error;\n }\n}\n\nfunction isGithubRateLimitError(error: RequestError): boolean {\n return error.status === 403 && error.response?.headers['x-ratelimit-remaining'] === '0';\n}\n\nfunction retryAfterSeconds(error: RequestError): number | undefined {\n return parseRetryAfterSeconds(error.response?.headers['retry-after']);\n}\n\nfunction retryAfterSecondsFromHeaders(headers: Headers): number | undefined {\n return parseRetryAfterSeconds(headers.get('retry-after'));\n}\n\nfunction parseRetryAfterSeconds(\n retryAfter: string | number | null | undefined,\n): number | undefined {\n if (!retryAfter) return undefined;\n const parsed = Number.parseInt(String(retryAfter), 10);\n return Number.isNaN(parsed) ? undefined : parsed;\n}\n\nfunction cursorToPage(cursor: string | undefined): number {\n if (!cursor) return 1;\n const page = Number.parseInt(cursor, 10);\n return Number.isNaN(page) || page < 1 ? 1 : page;\n}\n\nfunction nextCursorFromLink(link: string | undefined): string | null {\n if (!link) return null;\n const next = link.split(',').find((part) => part.includes('rel=\"next\"'));\n if (!next) return null;\n const match = next.match(NEXT_PAGE_RE);\n return match?.[1] ?? null;\n}\n\nfunction toGithubRepository(repository: {\n id: number;\n owner: {login: string};\n name: string;\n full_name: string;\n default_branch?: string | null | undefined;\n private: boolean;\n visibility?: string | undefined;\n clone_url?: string | null | undefined;\n html_url?: string | null | undefined;\n}): GithubRepository {\n if (!repository.default_branch || !repository.clone_url || !repository.html_url) {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub repository response is missing required fields',\n );\n }\n return {\n id: repository.id,\n ownerLogin: repository.owner.login,\n name: repository.name,\n fullName: repository.full_name,\n defaultBranch: repository.default_branch,\n private: repository.private,\n visibility: repository.visibility,\n cloneUrl: repository.clone_url,\n htmlUrl: repository.html_url,\n };\n}\n"],"names":["Buffer","MAX_REPOSITORY_FILE_BYTES","ky","HTTPError","TimeoutError","App","Octokit","RequestError","config","normalizedGithubApiBaseUrl","normalizedGithubPrivateKey","GithubIntegrationProviderError","recordInstallationTokenFormat","getGithubInstallationOctokit","githubInstallationTokenFormatPlugin","NEXT_PAGE_RE","TRAILING_SLASHES_RE","MAX_TREE_WALK_DEPTH","createGithubApiClient","OctokitGithubApiClient","exchangeOAuthCode","code","body","mapGithubOAuthError","post","headers","accept","json","client_id","GITHUB_APP_CLIENT_ID","client_secret","GITHUB_APP_CLIENT_SECRET","access_token","listUserInstallations","input","octokit","auth","userAccessToken","baseUrl","page","cursorToPage","cursor","response","mapGithubError","request","per_page","installations","data","installationIds","map","installation","id","nextCursor","nextCursorFromLink","link","getInstallation","installationId","getApp","rest","apps","installation_id","account","login","slug","type","repositorySelection","repository_selection","suspendedAt","suspended_at","Date","htmlUrl","html_url","raw","listInstallationRepositories","listReposAccessibleToInstallation","limit","repositories","toGithubRepository","getRepository","repository_id","repositoryId","listRepositoryFiles","repository","owner","ownerLogin","repo","name","startPath","prefix","replace","offset","Number","parseInt","start","isNaN","collected","overflowLimit","walk","path","depth","length","repos","getContent","ref","error","reason","Array","isArray","push","size","entries","sort","a","b","localeCompare","entry","sorted","slice","consumed","hasMore","files","String","fetchRepositoryFile","content","encoding","from","toString","createInstallationAccessToken","repository_ids","permissions","contents","token","expiresAt","expires_at","getTime","undefined","app","appId","GITHUB_APP_ID","privateKey","plugin","defaults","operation","status","retryAfterSecondsFromHeaders","notFoundReason","message","isGithubRateLimitError","retryAfterSeconds","Error","parseRetryAfterSeconds","get","retryAfter","parsed","next","split","find","part","includes","match","default_branch","clone_url","fullName","full_name","defaultBranch","private","visibility","cloneUrl"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/api/client.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport {isRecord, MAX_REPOSITORY_FILE_BYTES} from '@shipfox/api-integration-spi';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport ky, {HTTPError, TimeoutError} from 'ky';\nimport {App, Octokit, RequestError} from 'octokit';\nimport {config, normalizedGithubApiBaseUrl, normalizedGithubPrivateKey} from '#config.js';\nimport {GithubIntegrationProviderError} from '#core/errors.js';\nimport {recordInstallationTokenFormat} from '#metrics/index.js';\nimport {\n getGithubInstallationOctokit,\n githubInstallationTokenFormatPlugin,\n} from './github-octokit.js';\n\nconst NEXT_PAGE_RE = /[?&]page=(\\d+)/;\nconst TRAILING_SLASHES_RE = /\\/+$/;\nconst MAX_TREE_WALK_DEPTH = 10;\nconst GITHUB_API_TIMEOUT_MS = 10_000;\n\nexport interface GithubAccount {\n login: string;\n type: string;\n}\n\nexport interface GithubInstallationDetails {\n id: number;\n account: GithubAccount;\n repositorySelection: string;\n suspendedAt: Date | null;\n htmlUrl: string;\n raw: Record<string, unknown>;\n}\n\nexport interface GithubRepository {\n id: number;\n ownerLogin: string;\n name: string;\n fullName: string;\n defaultBranch: string;\n private: boolean;\n visibility?: string | undefined;\n cloneUrl: string;\n htmlUrl: string;\n}\n\nexport interface GithubRepositoryPage {\n repositories: GithubRepository[];\n nextCursor: string | null;\n}\n\nexport interface GithubFileEntry {\n path: string;\n size: number | null;\n}\n\nexport interface GithubFilePage {\n files: GithubFileEntry[];\n nextCursor: string | null;\n}\n\nexport interface GithubFileContent {\n path: string;\n content: string;\n size: number;\n}\n\nexport interface GithubUserInstallationPage {\n installationIds: number[];\n nextCursor: string | null;\n}\n\nexport interface GithubBotUser {\n id: number;\n login: string;\n}\n\nexport interface GithubBotUserClient {\n getBotUser(input: {username: string; installationAccessToken: string}): Promise<GithubBotUser>;\n}\n\nexport interface GithubApiClient extends Partial<GithubBotUserClient> {\n exchangeOAuthCode(code: string): Promise<string>;\n listUserInstallations(input: {\n userAccessToken: string;\n cursor?: string | undefined;\n }): Promise<GithubUserInstallationPage>;\n getInstallation(installationId: number): Promise<GithubInstallationDetails>;\n listInstallationRepositories(input: {\n installationId: number;\n limit: number;\n cursor?: string | undefined;\n }): Promise<GithubRepositoryPage>;\n getRepository(input: {installationId: number; repositoryId: number}): Promise<GithubRepository>;\n listRepositoryFiles(input: {\n installationId: number;\n repositoryId: number;\n ref: string;\n prefix: string;\n limit: number;\n cursor?: string | undefined;\n }): Promise<GithubFilePage>;\n fetchRepositoryFile(input: {\n installationId: number;\n repositoryId: number;\n ref: string;\n path: string;\n }): Promise<GithubFileContent>;\n createInstallationAccessToken(input: {\n installationId: number;\n repositoryId: number;\n permissions?: {contents: 'read' | 'write'} | undefined;\n }): Promise<GithubInstallationAccessToken>;\n}\n\nexport interface GithubInstallationAccessToken {\n token: string;\n expiresAt: Date;\n permissions?: Record<string, 'read' | 'write' | 'admin'> | undefined;\n}\n\nexport function createGithubApiClient(): GithubApiClient & GithubBotUserClient {\n return new OctokitGithubApiClient();\n}\n\nclass OctokitGithubApiClient implements GithubApiClient, GithubBotUserClient {\n private app: App | undefined;\n private readonly botUsers = new Map<string, GithubBotUser>();\n private readonly botUserLookups = new Map<\n string,\n {installationAccessToken: string; promise: Promise<GithubBotUser>}\n >();\n\n async exchangeOAuthCode(code: string): Promise<string> {\n const body = await mapGithubOAuthError(() =>\n ky\n .post('https://github.com/login/oauth/access_token', {\n headers: {accept: 'application/json'},\n json: {\n client_id: config.GITHUB_APP_CLIENT_ID,\n client_secret: config.GITHUB_APP_CLIENT_SECRET,\n code,\n },\n })\n .json<{access_token?: unknown}>(),\n );\n\n if (typeof body.access_token !== 'string') {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub OAuth response did not include an access token',\n );\n }\n return body.access_token;\n }\n\n getBotUser(input: {username: string; installationAccessToken: string}): Promise<GithubBotUser> {\n const cacheKey = input.username.trim().toLowerCase();\n const cached = this.botUsers.get(cacheKey);\n if (cached) return Promise.resolve(cached);\n\n const pending = this.botUserLookups.get(cacheKey);\n if (pending?.installationAccessToken === input.installationAccessToken) {\n return pending.promise;\n }\n\n const lookup = this.fetchBotUser(input).then((botUser) => {\n const resolved = this.botUsers.get(cacheKey);\n if (resolved) return resolved;\n\n this.botUsers.set(cacheKey, botUser);\n logger().info(\n {githubAppBotLogin: botUser.login, githubAppBotUserId: botUser.id},\n 'Resolved GitHub App bot identity',\n );\n return botUser;\n });\n const trackedLookup = lookup.finally(() => {\n if (this.botUserLookups.get(cacheKey)?.promise === trackedLookup) {\n this.botUserLookups.delete(cacheKey);\n }\n });\n this.botUserLookups.set(cacheKey, {\n installationAccessToken: input.installationAccessToken,\n promise: trackedLookup,\n });\n return trackedLookup;\n }\n\n async listUserInstallations(input: {\n userAccessToken: string;\n cursor?: string | undefined;\n }): Promise<GithubUserInstallationPage> {\n const octokit = new Octokit({\n auth: input.userAccessToken,\n baseUrl: normalizedGithubApiBaseUrl(),\n });\n const page = cursorToPage(input.cursor);\n const response = await mapGithubError(() =>\n octokit.request('GET /user/installations', {\n per_page: 100,\n page,\n }),\n );\n\n const installations = response.data.installations ?? [];\n return {\n installationIds: installations.map((installation) => installation.id),\n nextCursor: nextCursorFromLink(response.headers.link),\n };\n }\n\n async getInstallation(installationId: number): Promise<GithubInstallationDetails> {\n const response = await mapGithubError(() =>\n this.getApp().octokit.rest.apps.getInstallation({installation_id: installationId}),\n );\n const data = response.data;\n const account = data.account;\n if (!account) {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub installation response did not include an account',\n );\n }\n\n const login = 'login' in account ? account.login : account.slug;\n return {\n id: data.id,\n account: {\n login,\n type: 'type' in account ? account.type : 'Enterprise',\n },\n repositorySelection: data.repository_selection,\n suspendedAt: data.suspended_at ? new Date(data.suspended_at) : null,\n htmlUrl: data.html_url,\n raw: data as unknown as Record<string, unknown>,\n };\n }\n\n async listInstallationRepositories(input: {\n installationId: number;\n limit: number;\n cursor?: string | undefined;\n }): Promise<GithubRepositoryPage> {\n const octokit = await mapGithubError(() =>\n getGithubInstallationOctokit(this.getApp(), input.installationId),\n );\n const page = cursorToPage(input.cursor);\n const response = await mapGithubError(() =>\n octokit.rest.apps.listReposAccessibleToInstallation({\n per_page: input.limit,\n page,\n }),\n );\n\n return {\n repositories: response.data.repositories.map(toGithubRepository),\n nextCursor: nextCursorFromLink(response.headers.link),\n };\n }\n\n async getRepository(input: {\n installationId: number;\n repositoryId: number;\n }): Promise<GithubRepository> {\n const octokit = await mapGithubError(() =>\n getGithubInstallationOctokit(this.getApp(), input.installationId),\n );\n const response = await mapGithubError(() =>\n octokit.request('GET /repositories/{repository_id}', {\n repository_id: input.repositoryId,\n }),\n );\n\n return toGithubRepository(response.data);\n }\n\n async listRepositoryFiles(input: {\n installationId: number;\n repositoryId: number;\n ref: string;\n prefix: string;\n limit: number;\n cursor?: string | undefined;\n }): Promise<GithubFilePage> {\n const octokit = await mapGithubError(() =>\n getGithubInstallationOctokit(this.getApp(), input.installationId),\n );\n const repository = await this.getRepository({\n installationId: input.installationId,\n repositoryId: input.repositoryId,\n });\n const owner = repository.ownerLogin;\n const repo = repository.name;\n const startPath = input.prefix.replace(TRAILING_SLASHES_RE, '');\n const offset = input.cursor ? Number.parseInt(input.cursor, 10) : 0;\n const start = Number.isNaN(offset) || offset < 0 ? 0 : offset;\n const collected: GithubFileEntry[] = [];\n const overflowLimit = start + input.limit + 1;\n\n type GetContentData = Awaited<ReturnType<typeof octokit.rest.repos.getContent>>['data'];\n\n const walk = async (path: string, depth: number): Promise<void> => {\n if (collected.length >= overflowLimit) return;\n if (depth > MAX_TREE_WALK_DEPTH) return;\n\n let data: GetContentData;\n try {\n const response = await mapGithubError(\n () =>\n octokit.rest.repos.getContent({\n owner,\n repo,\n path,\n ref: input.ref,\n }),\n 'file-not-found',\n );\n data = response.data;\n } catch (error) {\n if (error instanceof GithubIntegrationProviderError && error.reason === 'file-not-found') {\n return;\n }\n throw error;\n }\n\n if (!Array.isArray(data)) {\n if (data.type === 'file' && data.path) {\n collected.push({path: data.path, size: typeof data.size === 'number' ? data.size : null});\n }\n return;\n }\n\n const entries = [...data].sort((a, b) => (a.path ?? '').localeCompare(b.path ?? ''));\n for (const entry of entries) {\n if (collected.length >= overflowLimit) return;\n if (!entry.path) continue;\n if (entry.type === 'file') {\n collected.push({\n path: entry.path,\n size: typeof entry.size === 'number' ? entry.size : null,\n });\n } else if (entry.type === 'dir') {\n await walk(entry.path, depth + 1);\n }\n }\n };\n\n await walk(startPath, 0);\n\n const sorted = collected.sort((a, b) => a.path.localeCompare(b.path));\n const page = sorted.slice(start, start + input.limit);\n const consumed = start + page.length;\n const hasMore = consumed < sorted.length;\n\n return {\n files: page,\n nextCursor: hasMore ? String(consumed) : null,\n };\n }\n\n async fetchRepositoryFile(input: {\n installationId: number;\n repositoryId: number;\n ref: string;\n path: string;\n }): Promise<GithubFileContent> {\n const octokit = await mapGithubError(() =>\n getGithubInstallationOctokit(this.getApp(), input.installationId),\n );\n const repository = await this.getRepository({\n installationId: input.installationId,\n repositoryId: input.repositoryId,\n });\n const response = await mapGithubError(\n () =>\n octokit.rest.repos.getContent({\n owner: repository.ownerLogin,\n repo: repository.name,\n path: input.path,\n ref: input.ref,\n }),\n 'file-not-found',\n );\n const data = response.data;\n\n if (Array.isArray(data) || data.type !== 'file') {\n throw new GithubIntegrationProviderError('file-not-found', 'GitHub path is not a file');\n }\n if (data.size > MAX_REPOSITORY_FILE_BYTES) {\n throw new GithubIntegrationProviderError(\n 'content-too-large',\n 'GitHub file content is larger than the supported limit',\n );\n }\n if (typeof data.content !== 'string' || data.encoding !== 'base64') {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub file response did not include base64 content',\n );\n }\n\n return {\n path: data.path,\n size: data.size,\n content: Buffer.from(data.content, 'base64').toString('utf8'),\n };\n }\n\n async createInstallationAccessToken(input: {\n installationId: number;\n repositoryId: number;\n permissions?: {contents: 'read' | 'write'} | undefined;\n }): Promise<GithubInstallationAccessToken> {\n const response = await mapGithubError(() =>\n this.getApp().octokit.rest.apps.createInstallationAccessToken({\n installation_id: input.installationId,\n repository_ids: [input.repositoryId],\n permissions: input.permissions ?? {contents: 'read'},\n }),\n );\n\n if (typeof response.data.token !== 'string') {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub installation access token response did not include a token',\n );\n }\n\n recordInstallationTokenFormat(response.data.token);\n\n const expiresAt = new Date(response.data.expires_at);\n if (Number.isNaN(expiresAt.getTime())) {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub installation access token response did not include a valid expiry',\n );\n }\n\n return {\n token: response.data.token,\n expiresAt,\n ...(response.data.permissions === undefined ? {} : {permissions: response.data.permissions}),\n };\n }\n\n private getApp(): App {\n if (!this.app) {\n this.app = new App({\n appId: config.GITHUB_APP_ID,\n privateKey: normalizedGithubPrivateKey(),\n Octokit: Octokit.plugin(githubInstallationTokenFormatPlugin).defaults({\n baseUrl: normalizedGithubApiBaseUrl(),\n }),\n });\n }\n return this.app;\n }\n\n private async fetchBotUser(input: {\n username: string;\n installationAccessToken: string;\n }): Promise<GithubBotUser> {\n const octokit = new Octokit({\n auth: input.installationAccessToken,\n baseUrl: normalizedGithubApiBaseUrl(),\n });\n let response: Awaited<ReturnType<typeof octokit.rest.users.getByUsername>>;\n try {\n response = await mapGithubError(\n () =>\n octokit.rest.users.getByUsername({\n username: input.username,\n request: {signal: AbortSignal.timeout(GITHUB_API_TIMEOUT_MS)},\n }),\n 'provider-rejected',\n );\n } catch (error) {\n if (error instanceof GithubIntegrationProviderError && error.status === 404) {\n throw new GithubIntegrationProviderError(\n 'provider-rejected',\n `Configured GitHub bot user ${input.username} was not found`,\n undefined,\n error.status,\n );\n }\n throw error;\n }\n\n const data: unknown = response.data;\n if (!isRecord(data)) {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub bot user response is missing required fields',\n );\n }\n const {id, login, type} = data;\n if (\n typeof id !== 'number' ||\n !Number.isSafeInteger(id) ||\n id <= 0 ||\n typeof login !== 'string' ||\n login.trim().length === 0\n ) {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub bot user response is missing required fields',\n );\n }\n if (type !== 'Bot') {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'Configured GitHub username is not a bot account',\n );\n }\n const canonicalLogin = login.trim();\n if (canonicalLogin.toLowerCase() !== input.username.trim().toLowerCase()) {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub bot user response did not match the configured username',\n );\n }\n return {id, login: canonicalLogin};\n }\n}\n\nasync function mapGithubOAuthError<T>(operation: () => Promise<T>): Promise<T> {\n try {\n return await operation();\n } catch (error) {\n if (error instanceof HTTPError) {\n if (error.response.status === 429) {\n throw new GithubIntegrationProviderError(\n 'rate-limited',\n 'GitHub OAuth code exchange failed',\n retryAfterSecondsFromHeaders(error.response.headers),\n );\n }\n if (error.response.status >= 500) {\n throw new GithubIntegrationProviderError(\n 'provider-unavailable',\n 'GitHub OAuth code exchange failed',\n );\n }\n throw new GithubIntegrationProviderError(\n 'access-denied',\n 'GitHub OAuth code exchange failed',\n );\n }\n if (error instanceof TimeoutError) {\n throw new GithubIntegrationProviderError('timeout', 'GitHub OAuth request timed out');\n }\n throw error;\n }\n}\n\nexport async function mapGithubError<T>(\n operation: () => Promise<T>,\n notFoundReason:\n | 'repository-not-found'\n | 'installation-not-found'\n | 'file-not-found'\n | 'provider-rejected' = 'repository-not-found',\n): Promise<T> {\n try {\n return await operation();\n } catch (error) {\n if (error instanceof GithubIntegrationProviderError) throw error;\n if (isGithubTimeoutError(error)) {\n throw new GithubIntegrationProviderError('timeout', 'GitHub request timed out');\n }\n if (error instanceof RequestError) {\n if (error.status === 404) {\n throw new GithubIntegrationProviderError(\n notFoundReason,\n error.message,\n undefined,\n error.status,\n );\n }\n if (error.status === 429 || isGithubRateLimitError(error)) {\n throw new GithubIntegrationProviderError(\n 'rate-limited',\n error.message,\n retryAfterSeconds(error),\n error.status,\n );\n }\n if (error.status === 401 || error.status === 403) {\n throw new GithubIntegrationProviderError(\n 'access-denied',\n error.message,\n retryAfterSeconds(error),\n error.status,\n );\n }\n if (error.status >= 500) {\n throw new GithubIntegrationProviderError(\n 'provider-unavailable',\n error.message,\n undefined,\n error.status,\n );\n }\n if (error.status >= 400) {\n throw new GithubIntegrationProviderError(\n 'provider-rejected',\n error.message,\n undefined,\n error.status,\n );\n }\n }\n throw error;\n }\n}\n\nfunction isGithubTimeoutError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n if (error.name === 'AbortError' || error.name === 'TimeoutError') return true;\n return error.cause instanceof Error && isGithubTimeoutError(error.cause);\n}\n\nfunction isGithubRateLimitError(error: RequestError): boolean {\n return error.status === 403 && error.response?.headers['x-ratelimit-remaining'] === '0';\n}\n\nfunction retryAfterSeconds(error: RequestError): number | undefined {\n return parseRetryAfterSeconds(error.response?.headers['retry-after']);\n}\n\nfunction retryAfterSecondsFromHeaders(headers: Headers): number | undefined {\n return parseRetryAfterSeconds(headers.get('retry-after'));\n}\n\nfunction parseRetryAfterSeconds(\n retryAfter: string | number | null | undefined,\n): number | undefined {\n if (!retryAfter) return undefined;\n const parsed = Number.parseInt(String(retryAfter), 10);\n return Number.isNaN(parsed) ? undefined : parsed;\n}\n\nfunction cursorToPage(cursor: string | undefined): number {\n if (!cursor) return 1;\n const page = Number.parseInt(cursor, 10);\n return Number.isNaN(page) || page < 1 ? 1 : page;\n}\n\nfunction nextCursorFromLink(link: string | undefined): string | null {\n if (!link) return null;\n const next = link.split(',').find((part) => part.includes('rel=\"next\"'));\n if (!next) return null;\n const match = next.match(NEXT_PAGE_RE);\n return match?.[1] ?? null;\n}\n\nfunction toGithubRepository(repository: {\n id: number;\n owner: {login: string};\n name: string;\n full_name: string;\n default_branch?: string | null | undefined;\n private: boolean;\n visibility?: string | undefined;\n clone_url?: string | null | undefined;\n html_url?: string | null | undefined;\n}): GithubRepository {\n if (!repository.default_branch || !repository.clone_url || !repository.html_url) {\n throw new GithubIntegrationProviderError(\n 'malformed-provider-response',\n 'GitHub repository response is missing required fields',\n );\n }\n return {\n id: repository.id,\n ownerLogin: repository.owner.login,\n name: repository.name,\n fullName: repository.full_name,\n defaultBranch: repository.default_branch,\n private: repository.private,\n visibility: repository.visibility,\n cloneUrl: repository.clone_url,\n htmlUrl: repository.html_url,\n };\n}\n"],"names":["Buffer","isRecord","MAX_REPOSITORY_FILE_BYTES","logger","ky","HTTPError","TimeoutError","App","Octokit","RequestError","config","normalizedGithubApiBaseUrl","normalizedGithubPrivateKey","GithubIntegrationProviderError","recordInstallationTokenFormat","getGithubInstallationOctokit","githubInstallationTokenFormatPlugin","NEXT_PAGE_RE","TRAILING_SLASHES_RE","MAX_TREE_WALK_DEPTH","GITHUB_API_TIMEOUT_MS","createGithubApiClient","OctokitGithubApiClient","exchangeOAuthCode","code","body","mapGithubOAuthError","post","headers","accept","json","client_id","GITHUB_APP_CLIENT_ID","client_secret","GITHUB_APP_CLIENT_SECRET","access_token","getBotUser","input","cacheKey","username","trim","toLowerCase","cached","botUsers","get","Promise","resolve","pending","botUserLookups","installationAccessToken","promise","lookup","fetchBotUser","then","botUser","resolved","set","info","githubAppBotLogin","login","githubAppBotUserId","id","trackedLookup","finally","delete","listUserInstallations","octokit","auth","userAccessToken","baseUrl","page","cursorToPage","cursor","response","mapGithubError","request","per_page","installations","data","installationIds","map","installation","nextCursor","nextCursorFromLink","link","getInstallation","installationId","getApp","rest","apps","installation_id","account","slug","type","repositorySelection","repository_selection","suspendedAt","suspended_at","Date","htmlUrl","html_url","raw","listInstallationRepositories","listReposAccessibleToInstallation","limit","repositories","toGithubRepository","getRepository","repository_id","repositoryId","listRepositoryFiles","repository","owner","ownerLogin","repo","name","startPath","prefix","replace","offset","Number","parseInt","start","isNaN","collected","overflowLimit","walk","path","depth","length","repos","getContent","ref","error","reason","Array","isArray","push","size","entries","sort","a","b","localeCompare","entry","sorted","slice","consumed","hasMore","files","String","fetchRepositoryFile","content","encoding","from","toString","createInstallationAccessToken","repository_ids","permissions","contents","token","expiresAt","expires_at","getTime","undefined","app","appId","GITHUB_APP_ID","privateKey","plugin","defaults","users","getByUsername","signal","AbortSignal","timeout","status","isSafeInteger","canonicalLogin","Map","operation","retryAfterSecondsFromHeaders","notFoundReason","isGithubTimeoutError","message","isGithubRateLimitError","retryAfterSeconds","Error","cause","parseRetryAfterSeconds","retryAfter","parsed","next","split","find","part","includes","match","default_branch","clone_url","fullName","full_name","defaultBranch","private","visibility","cloneUrl"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-tools.d.ts","sourceRoot":"","sources":["../../src/core/agent-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,yBAAyB,EACzB,gBAAgB,EAChB,kBAAkB,EAClB,qBAAqB,EACrB,0BAA0B,EAC3B,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAEL,KAAK,+BAA+B,EACrC,MAAM,qCAAqC,CAAC;AAE7C,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-tools.d.ts","sourceRoot":"","sources":["../../src/core/agent-tools.ts"],"names":[],"mappings":"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"}
|
package/dist/core/agent-tools.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Octokit } from 'octokit';
|
|
2
2
|
import { mapGithubError } from '#api/client.js';
|
|
3
3
|
import { createGithubInstallationTokenProvider } from '#api/installation-token-provider.js';
|
|
4
|
-
import {
|
|
4
|
+
import { normalizedGithubApiBaseUrl } from '#config.js';
|
|
5
|
+
import { githubAppBotLogin } from './bot-identity.js';
|
|
5
6
|
import { GithubIntegrationProviderError } from './errors.js';
|
|
6
7
|
import { githubAgentToolCatalog, githubAgentToolSelectionCatalog } from './github-agent-tool-catalog.js';
|
|
7
8
|
export { buildGithubAgentToolSelectionCatalog, DEFAULT_JOB_LOG_TAIL_LINES, githubAgentToolCatalog, githubAgentToolSelectionCatalog } from './github-agent-tool-catalog.js';
|
|
@@ -9,7 +10,6 @@ const GITHUB_GRAPHQL_ROUTE = 'POST /graphql';
|
|
|
9
10
|
const GITHUB_ARTIFACT_ARCHIVE_FORMAT = 'zip';
|
|
10
11
|
const GITHUB_ARTIFACT_DOWNLOAD_ROUTE = `GET /repos/{owner}/{repo}/actions/artifacts/{resource_id}/${GITHUB_ARTIFACT_ARCHIVE_FORMAT}`;
|
|
11
12
|
const GITHUB_ARTIFACT_DOWNLOAD_TIMEOUT_MS = 30_000;
|
|
12
|
-
const GITHUB_APP_BOT_SUFFIX = '[bot]';
|
|
13
13
|
const PENDING_REVIEW_PAGE_SIZE = 100;
|
|
14
14
|
const PENDING_REVIEW_MAX_PAGE_REQUESTS = 5;
|
|
15
15
|
const PENDING_REVIEW_LOOKUP_TIMEOUT_MS = 15_000;
|
|
@@ -478,10 +478,6 @@ function latestPendingReviewOnPage(data, requiredIdentifier) {
|
|
|
478
478
|
malformed
|
|
479
479
|
};
|
|
480
480
|
}
|
|
481
|
-
function githubAppBotLogin() {
|
|
482
|
-
const configuredUsername = config.GITHUB_APP_USERNAME?.trim() || config.GITHUB_APP_SLUG.trim();
|
|
483
|
-
return configuredUsername.toLowerCase().endsWith(GITHUB_APP_BOT_SUFFIX) ? configuredUsername : `${configuredUsername}${GITHUB_APP_BOT_SUFFIX}`;
|
|
484
|
-
}
|
|
485
481
|
function githubToolResult(toolId, data, response, parameters, route) {
|
|
486
482
|
const structuredContent = projectGithubToolOutput(toolId, data, response, parameters, route);
|
|
487
483
|
if (structuredContent === undefined) {
|