1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | import {
|
18 | doWithRetry,
|
19 | GitHubRepoRef,
|
20 | Issue,
|
21 | logger,
|
22 | ProjectOperationCredentials,
|
23 | RemoteRepoRef,
|
24 | } from "@atomist/automation-client";
|
25 | import { isGitHubRepoRef } from "@atomist/automation-client/lib/operations/common/GitHubRepoRef";
|
26 | import { toToken } from "@atomist/sdm";
|
27 |
|
28 | import axios, {
|
29 | AxiosPromise,
|
30 | AxiosRequestConfig,
|
31 | } from "axios";
|
32 |
|
33 |
|
34 | export type State = "error" | "failure" | "pending" | "success";
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | export interface Status {
|
40 | state: State;
|
41 | target_url?: string;
|
42 | description?: string;
|
43 | context?: string;
|
44 | }
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 | export function createStatus(creds: string | ProjectOperationCredentials, rr: GitHubRepoRef, inputStatus: Status): AxiosPromise {
|
54 | const config = authHeaders(toToken(creds));
|
55 | const saferStatus = ensureValidUrl(inputStatus);
|
56 | const url = `${rr.scheme}${rr.apiBase}/repos/${rr.owner}/${rr.repo}/statuses/${rr.sha}`;
|
57 | logger.debug("Updating github status: %s to %j", url, saferStatus);
|
58 | return doWithRetry(() =>
|
59 | axios.post(url, saferStatus, config).catch(err =>
|
60 | Promise.reject(new Error(`Error hitting ${url} to set status ${JSON.stringify(saferStatus)}: ${err.message}`)),
|
61 | ), `Updating github status: ${url} to ${JSON.stringify(saferStatus)}`, {});
|
62 | }
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 | function ensureValidUrl(inputStatus: Status): Status {
|
76 |
|
77 | if (!inputStatus.target_url) {
|
78 | return inputStatus;
|
79 | }
|
80 | if (inputStatus.target_url.startsWith("http")) {
|
81 | return inputStatus;
|
82 | }
|
83 | return {
|
84 | target_url: undefined,
|
85 | description: inputStatus.description + " at " + inputStatus.target_url,
|
86 | state: inputStatus.state,
|
87 | context: inputStatus.context,
|
88 | };
|
89 | }
|
90 |
|
91 | export interface Tag {
|
92 | tag: string;
|
93 | message: string;
|
94 |
|
95 |
|
96 | object: string;
|
97 | type: string;
|
98 | tagger: {
|
99 | name: string;
|
100 | email: string;
|
101 | date: string;
|
102 | };
|
103 | }
|
104 |
|
105 | export function createTag(creds: string | ProjectOperationCredentials, rr: GitHubRepoRef, tag: Tag): AxiosPromise {
|
106 | const config = authHeaders(toToken(creds));
|
107 | const url = `${rr.scheme}${rr.apiBase}/repos/${rr.owner}/${rr.repo}/git/tags`;
|
108 | logger.debug("Updating github tag: %s to %j", url, tag);
|
109 | return doWithRetry(() => axios.post(url, tag, config)
|
110 | .catch(err =>
|
111 | Promise.reject(new Error(`Error hitting ${url} to set tag ${JSON.stringify(tag)}: ${err.message}`)),
|
112 | ), `Updating github tag: ${url} to ${JSON.stringify(tag)}`, {});
|
113 | }
|
114 |
|
115 | export function createTagReference(creds: string | ProjectOperationCredentials, rr: GitHubRepoRef, tag: Tag): AxiosPromise {
|
116 | const config = authHeaders(toToken(creds));
|
117 | const url = `${rr.scheme}${rr.apiBase}/repos/${rr.owner}/${rr.repo}/git/refs`;
|
118 | logger.debug("Creating github reference: %s to %j", url, tag);
|
119 | return doWithRetry(() => axios.post(url, { ref: `refs/tags/${tag.tag}`, sha: tag.object }, config)
|
120 | .catch(err =>
|
121 | Promise.reject(new Error(`Error hitting ${url} to set tag ${JSON.stringify(tag)}: ${err.message}`)),
|
122 | ), `Updating github tag: ${url} to ${JSON.stringify(tag)}`, {});
|
123 | }
|
124 |
|
125 | export function deleteRepository(creds: string | ProjectOperationCredentials, rr: GitHubRepoRef): AxiosPromise {
|
126 | const config = authHeaders(toToken(creds));
|
127 | const url = `${rr.scheme}${rr.apiBase}/repos/${rr.owner}/${rr.repo}`;
|
128 | logger.debug("Deleting repository: %s", url);
|
129 | return axios.delete(url, config)
|
130 | .catch(err => {
|
131 | logger.error(err.message);
|
132 | logger.error(err.response.body);
|
133 | return Promise.reject(new Error(`Error hitting ${url} to delete repo`));
|
134 | },
|
135 | );
|
136 | }
|
137 |
|
138 | export interface Release {
|
139 | tag_name: string;
|
140 | target_commitish?: string;
|
141 | name?: string;
|
142 | body?: string;
|
143 | draft?: boolean;
|
144 | prerelease?: boolean;
|
145 | }
|
146 |
|
147 | export function createRelease(creds: string | ProjectOperationCredentials, rr: GitHubRepoRef, release: Release): AxiosPromise {
|
148 | const config = authHeaders(toToken(creds));
|
149 | const url = `${rr.scheme}${rr.apiBase}/repos/${rr.owner}/${rr.repo}/releases`;
|
150 | logger.debug("Updating github release: %s to %j", url, release);
|
151 | return doWithRetry(() => axios.post(url, release, config)
|
152 | .catch(err =>
|
153 | Promise.reject(new Error(`Error hitting ${url} to set release ${JSON.stringify(release)}: ${err.message}`)),
|
154 | ), `Updating github release: ${url} to ${JSON.stringify(release)}`, {});
|
155 | }
|
156 |
|
157 | export interface GitHubCommitsBetween {
|
158 | commits: Array<{
|
159 | sha: string;
|
160 | author: { login: string };
|
161 | commit: { message: string };
|
162 | }>;
|
163 | }
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 | export function listCommitsBetween(creds: string | ProjectOperationCredentials,
|
174 | rr: GitHubRepoRef,
|
175 | startSha: string,
|
176 | endSha: string): Promise<GitHubCommitsBetween> {
|
177 | const config = authHeaders(toToken(creds));
|
178 | const url = `${rr.scheme}${rr.apiBase}/repos/${rr.owner}/${rr.repo}/compare/${startSha}...${endSha}`;
|
179 | return axios.get(url, config)
|
180 | .then(ap => ap.data);
|
181 | }
|
182 |
|
183 | export function authHeaders(token: string): AxiosRequestConfig {
|
184 | return token ? {
|
185 | headers: {
|
186 | Authorization: `token ${token}`,
|
187 | },
|
188 | }
|
189 | : {};
|
190 | }
|
191 |
|
192 | export function tipOfDefaultBranch(creds: string | ProjectOperationCredentials, rr: GitHubRepoRef): Promise<string> {
|
193 |
|
194 | const config = authHeaders(toToken(creds));
|
195 | const url = `${rr.scheme}${rr.apiBase}/repos/${rr.owner}/${rr.repo}/branches/master`;
|
196 | return axios.get(url, config)
|
197 | .then(ap => ap.data.commit.sha);
|
198 | }
|
199 |
|
200 | export function isPublicRepo(creds: string | ProjectOperationCredentials, rr: GitHubRepoRef): Promise<boolean> {
|
201 | const config = authHeaders(toToken(creds));
|
202 | const url = `${rr.scheme}${rr.apiBase}/repos/${rr.owner}/${rr.repo}`;
|
203 | return axios.get(url, config)
|
204 | .then(ap => {
|
205 | const privateness = ap.data.private;
|
206 | logger.debug(`Retrieved ${url}. Private is '${privateness}'`);
|
207 | return !privateness;
|
208 | })
|
209 | .catch(err => {
|
210 | logger.warn(`Could not access ${url} to determine repo visibility: ${err.message}`);
|
211 | return false;
|
212 | });
|
213 | }
|
214 |
|
215 |
|
216 | export function updateIssue(creds: string | ProjectOperationCredentials,
|
217 | rr: RemoteRepoRef,
|
218 | issueNumber: number,
|
219 | issue: Issue): AxiosPromise {
|
220 | const grr = isGitHubRepoRef(rr) ? rr : new GitHubRepoRef(rr.owner, rr.repo, rr.sha);
|
221 | const url = `${grr.scheme}${grr.apiBase}/repos/${grr.owner}/${grr.repo}/issues/${issueNumber}`;
|
222 | logger.debug(`Request to '${url}' to update issue`);
|
223 | return axios.patch(url, issue, authHeaders(toToken(creds)));
|
224 | }
|
225 |
|
226 | export async function listTopics(creds: string | ProjectOperationCredentials, rr: RemoteRepoRef): Promise<string[]> {
|
227 | const headers = {
|
228 | headers: {
|
229 | ...authHeaders(toToken(creds)).headers,
|
230 | Accept: "application/vnd.github.mercy-preview+json",
|
231 | },
|
232 | };
|
233 | const grr = isGitHubRepoRef(rr) ? rr : new GitHubRepoRef(rr.owner, rr.repo, rr.sha);
|
234 | const url = `${grr.scheme}${grr.apiBase}/repos/${grr.owner}/${grr.repo}/topics`;
|
235 | const topics = await axios.get(url, headers);
|
236 | return topics.data.names;
|
237 | }
|