-
Notifications
You must be signed in to change notification settings - Fork 190
Expand file tree
/
Copy pathgitUtils.ts
More file actions
137 lines (124 loc) · 4.7 KB
/
Copy pathgitUtils.ts
File metadata and controls
137 lines (124 loc) · 4.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import os from 'node:os'
import fs from 'node:fs'
import { command } from './command.ts'
import {
getGithubDeployKey,
getGithubReadToken,
getGithubReleaseToken,
getGithubPullRequestToken,
getGithubCommitToken,
type OctoStsToken,
} from './secrets.ts'
import { FetchError, fetchHandlingError, findError } from './executionUtils.ts'
interface GitHubPR {
// eslint-disable-next-line id-denylist
number: number
title: string
body: string
base: {
ref: string
}
}
interface GitHubRelease {
html_url: string
}
interface GitHubReleaseParams {
version: string
body: string
}
export async function fetchPR(localBranch: string): Promise<GitHubPR | null> {
using readToken = getGithubReadToken()
const pr = await callGitHubApi<GitHubPR[]>('GET', `pulls?head=DataDog:${localBranch}`, readToken)
if (pr && pr.length > 1) {
throw new Error('Multiple pull requests found for the branch')
}
return pr ? pr[0] : null
}
/**
* Create a GitHub release.
*
* @param params - The parameters for the GitHub release.
* @param params.version - The version to create a release for.
* @param params.body - The body of the release.
*/
export async function createGitHubRelease({ version, body }: GitHubReleaseParams): Promise<GitHubRelease> {
using readToken = getGithubReadToken()
try {
await callGitHubApi('GET', `releases/tags/${version}`, readToken)
throw new Error(`Release ${version} already exists`)
} catch (error) {
const fetchError = findError(error, FetchError)
if (fetchError?.response.status !== 404) {
throw error
}
}
// content write
using releaseToken = getGithubReleaseToken()
return await callGitHubApi<GitHubRelease>('POST', 'releases', releaseToken, {
tag_name: version,
name: version,
body,
})
}
export function createPullRequest(mainBranch: string, labels?: string[]) {
using token = getGithubPullRequestToken()
command`gh auth login --with-token`.withInput(token.value).run()
const labelArgs = labels?.flatMap((label) => ['--label', label]) ?? []
const pullRequestUrl = command`gh pr create --fill --base ${mainBranch} ${labelArgs}`.run()
return pullRequestUrl.trim()
}
/**
* Push the current HEAD commit to a new remote branch as a signed (Verified) commit, using
* commit-headless to create it through the GitHub API instead of a plain `git push`. The local
* branch is then made to track the newly created remote branch, so callers (e.g. `gh pr create`)
* don't attempt an unsigned push of their own.
*/
export function pushSignedCommit(branch: string): void {
// `--create-branch` has no default branch point, so `--head-sha` must be given explicitly: the
// parent of HEAD, i.e. the commit the local branch was created from.
const headSha = command`git rev-parse HEAD^`.run().trim()
using token = getGithubCommitToken()
command`commit-headless push -T DataDog/browser-sdk --branch ${branch} --create-branch --head-sha ${headSha}`
.withEnvironment({ GITHUB_TOKEN: token.value })
.run()
command`git fetch --no-tags origin ${branch}`.run()
command`git branch --set-upstream-to=origin/${branch} ${branch}`.run()
}
export function getLastCommonCommit(baseBranch: string): string {
try {
command`git fetch --depth=100 origin ${baseBranch}`.run()
const commandOutput = command`git merge-base origin/${baseBranch} HEAD`.run()
return commandOutput.trim()
} catch (error) {
throw new Error('Failed to get last common commit', { cause: error })
}
}
export function initGitConfig(repository: string): void {
const homedir = os.homedir()
// ssh-add expects a new line at the end of the PEM-formatted private key
// https://stackoverflow.com/a/59595773
command`ssh-add -`.withInput(`${getGithubDeployKey()}\n`).run()
command`mkdir -p ${homedir}/.ssh`.run()
command`chmod 700 ${homedir}/.ssh`.run()
const sshHost = command`ssh-keyscan -H github.com`.run()
fs.appendFileSync(`${homedir}/.ssh/known_hosts`, sshHost)
command`git config user.email ci.browser-sdk@datadoghq.com`.run()
command`git config user.name ci.browser-sdk`.run()
command`git remote set-url origin ${repository}`.run()
}
export const LOCAL_BRANCH = process.env.CI_COMMIT_REF_NAME
async function callGitHubApi<T>(method: string, path: string, token: OctoStsToken, body?: any): Promise<T> {
try {
const response = await fetchHandlingError(`https://api.github.com/repos/DataDog/browser-sdk/${path}`, {
method,
headers: {
Authorization: `token ${token.value}`,
'X-GitHub-Api-Version': '2022-11-28',
},
body: body ? JSON.stringify(body) : undefined,
})
return (await response.json()) as Promise<T>
} catch (error) {
throw new Error(`Failed to call GitHub API: ${method} ${path}`, { cause: error })
}
}