-
Notifications
You must be signed in to change notification settings - Fork 190
Expand file tree
/
Copy pathgitUtils.ts
More file actions
138 lines (125 loc) · 4.74 KB
/
Copy pathgitUtils.ts
File metadata and controls
138 lines (125 loc) · 4.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
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`. Since the
* remote commit is re-signed, its SHA differs from the local one: `--reset` resets the local
* branch to it, so callers (e.g. `gh pr create`) see it as fully pushed and 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} --reset`
.withEnvironment({ GITHUB_TOKEN: token.value })
.withLogs()
.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 })
}
}