Repository navigation
Expand file tree
/
Copy pathgithub_bugbot_review_navigation_adapter.ts
More file actions
63 lines (58 loc) · 2.14 KB
/
Copy pathgithub_bugbot_review_navigation_adapter.ts
File metadata and controls
63 lines (58 loc) · 2.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import type {
BugbotReviewNavigation,
BugbotReviewNavigationPort,
} from '../../application/ports/bugbot_review_navigation_ports';
/** Builds trusted GitHub/GitHub Enterprise navigation without leaking env access upstream. */
export class GithubBugbotReviewNavigationAdapter implements BugbotReviewNavigationPort {
private readonly serverUrl: string;
constructor(
serverUrl = process.env.GITHUB_SERVER_URL ?? 'https://github.com',
private readonly workflowRepository = process.env.GITHUB_REPOSITORY,
private readonly workflowRunId = process.env.GITHUB_RUN_ID,
) {
this.serverUrl = normalizeHttpsServerUrl(serverUrl);
}
forPullRequest(
owner: string,
repository: string,
pullRequestNumber: number,
headSha: string,
): BugbotReviewNavigation {
if (
!isGithubPathSegment(owner) ||
!isGithubPathSegment(repository) ||
!Number.isSafeInteger(pullRequestNumber) ||
pullRequestNumber <= 0 ||
!/^[a-fA-F0-9]{7,64}$/u.test(headSha)
) {
throw new Error('GitHub navigation target is invalid.');
}
const repositoryPath = `${encodeURIComponent(owner)}/${encodeURIComponent(repository)}`;
const base = `${this.serverUrl}/${repositoryPath}`;
const runUrl = this.workflowRepository === `${owner}/${repository}` && /^\d+$/u.test(this.workflowRunId ?? '')
? `${base}/actions/runs/${this.workflowRunId}`
: undefined;
return {
pullRequestUrl: `${base}/pull/${pullRequestNumber}`,
commitUrl: `${base}/commit/${encodeURIComponent(headSha)}`,
...(runUrl ? { runUrl } : {}),
};
}
}
function isGithubPathSegment(value: string): boolean {
return /^[a-zA-Z0-9_.-]+$/u.test(value);
}
function normalizeHttpsServerUrl(value: string): string {
let url: URL;
try {
url = new URL(value);
} catch {
throw new Error('GitHub server URL must be an absolute HTTPS URL without credentials.');
}
if (url.protocol !== 'https:' || url.username || url.password || !url.hostname) {
throw new Error('GitHub server URL must be an absolute HTTPS URL without credentials.');
}
url.hash = '';
url.search = '';
return url.toString().replace(/\/+$/u, '');
}