1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.GitHubPublisher = void 0;
|
4 | const builder_util_1 = require("builder-util");
|
5 | const builder_util_runtime_1 = require("builder-util-runtime");
|
6 | const nodeHttpExecutor_1 = require("builder-util/out/nodeHttpExecutor");
|
7 | const lazy_val_1 = require("lazy-val");
|
8 | const mime = require("mime");
|
9 | const url_1 = require("url");
|
10 | const publisher_1 = require("./publisher");
|
11 | class GitHubPublisher extends publisher_1.HttpPublisher {
|
12 | constructor(context, info, version, options = {}) {
|
13 | super(context, true);
|
14 | this.info = info;
|
15 | this.version = version;
|
16 | this.options = options;
|
17 | this._release = new lazy_val_1.Lazy(() => (this.token === "__test__" ? Promise.resolve(null) : this.getOrCreateRelease()));
|
18 | this.providerName = "github";
|
19 | this.releaseLogFields = null;
|
20 | let token = info.token;
|
21 | if (builder_util_1.isEmptyOrSpaces(token)) {
|
22 | token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
|
23 | if (builder_util_1.isEmptyOrSpaces(token)) {
|
24 | throw new builder_util_1.InvalidConfigurationError(`GitHub Personal Access Token is not set, neither programmatically, nor using env "GH_TOKEN"`);
|
25 | }
|
26 | token = token.trim();
|
27 | if (!builder_util_1.isTokenCharValid(token)) {
|
28 | throw new builder_util_1.InvalidConfigurationError(`GitHub Personal Access Token (${JSON.stringify(token)}) contains invalid characters, please check env "GH_TOKEN"`);
|
29 | }
|
30 | }
|
31 | this.token = token;
|
32 | if (version.startsWith("v")) {
|
33 | throw new builder_util_1.InvalidConfigurationError(`Version must not start with "v": ${version}`);
|
34 | }
|
35 | this.tag = info.vPrefixedTagName === false ? version : `v${version}`;
|
36 | if (builder_util_1.isEnvTrue(process.env.EP_DRAFT)) {
|
37 | this.releaseType = "draft";
|
38 | builder_util_1.log.info({ reason: "env EP_DRAFT is set to true" }, "GitHub provider release type is set to draft");
|
39 | }
|
40 | else if (builder_util_1.isEnvTrue(process.env.EP_PRE_RELEASE) || builder_util_1.isEnvTrue(process.env.EP_PRELEASE) ) {
|
41 | this.releaseType = "prerelease";
|
42 | builder_util_1.log.info({ reason: "env EP_PRE_RELEASE is set to true" }, "GitHub provider release type is set to prerelease");
|
43 | }
|
44 | else if (info.releaseType != null) {
|
45 | this.releaseType = info.releaseType;
|
46 | }
|
47 | else if (options.prerelease) {
|
48 | this.releaseType = "prerelease";
|
49 | }
|
50 | else {
|
51 |
|
52 | this.releaseType = options.draft === false ? "release" : "draft";
|
53 | }
|
54 | }
|
55 | async getOrCreateRelease() {
|
56 | const logFields = {
|
57 | tag: this.tag,
|
58 | version: this.version,
|
59 | };
|
60 |
|
61 | const releases = await this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases`, this.token);
|
62 | for (const release of releases) {
|
63 | if (!(release.tag_name === this.tag || release.tag_name === this.version)) {
|
64 | continue;
|
65 | }
|
66 | if (release.draft) {
|
67 | return release;
|
68 | }
|
69 |
|
70 |
|
71 | if (this.releaseType === "draft") {
|
72 | this.releaseLogFields = {
|
73 | reason: "existing type not compatible with publishing type",
|
74 | ...logFields,
|
75 | existingType: release.prerelease ? "pre-release" : "release",
|
76 | publishingType: this.releaseType,
|
77 | };
|
78 | builder_util_1.log.warn(this.releaseLogFields, "GitHub release not created");
|
79 | return null;
|
80 | }
|
81 |
|
82 |
|
83 |
|
84 | const publishedAt = release.published_at == null ? null : Date.parse(release.published_at);
|
85 | if (!builder_util_1.isEnvTrue(process.env.EP_GH_IGNORE_TIME) && publishedAt != null && Date.now() - publishedAt > 2 * 3600 * 1000) {
|
86 |
|
87 | this.releaseLogFields = {
|
88 | reason: "existing release published more than 2 hours ago",
|
89 | ...logFields,
|
90 | date: new Date(publishedAt).toString(),
|
91 | };
|
92 | builder_util_1.log.warn(this.releaseLogFields, "GitHub release not created");
|
93 | return null;
|
94 | }
|
95 | return release;
|
96 | }
|
97 |
|
98 | if (this.options.publish === "always" || publisher_1.getCiTag() != null) {
|
99 | builder_util_1.log.info({
|
100 | reason: "release doesn't exist",
|
101 | ...logFields,
|
102 | }, `creating GitHub release`);
|
103 | return this.createRelease();
|
104 | }
|
105 | this.releaseLogFields = {
|
106 | reason: 'release doesn\'t exist and not created because "publish" is not "always" and build is not on tag',
|
107 | ...logFields,
|
108 | };
|
109 | return null;
|
110 | }
|
111 | async overwriteArtifact(fileName, release) {
|
112 |
|
113 | builder_util_1.log.warn({ file: fileName, reason: "already exists on GitHub" }, "overwrite published file");
|
114 | const assets = await this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases/${release.id}/assets`, this.token, null);
|
115 | for (const asset of assets) {
|
116 | if (asset.name === fileName) {
|
117 | await this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases/assets/${asset.id}`, this.token, null, "DELETE");
|
118 | return;
|
119 | }
|
120 | }
|
121 | builder_util_1.log.debug({ file: fileName, reason: "not found on GitHub" }, "trying to upload again");
|
122 | }
|
123 | async doUpload(fileName, arch, dataLength, requestProcessor) {
|
124 | const release = await this._release.value;
|
125 | if (release == null) {
|
126 | builder_util_1.log.warn({ file: fileName, ...this.releaseLogFields }, "skipped publishing");
|
127 | return;
|
128 | }
|
129 | const parsedUrl = url_1.parse(`${release.upload_url.substring(0, release.upload_url.indexOf("{"))}?name=${fileName}`);
|
130 | return await this.doUploadFile(0, parsedUrl, fileName, dataLength, requestProcessor, release);
|
131 | }
|
132 | doUploadFile(attemptNumber, parsedUrl, fileName, dataLength, requestProcessor, release) {
|
133 | return nodeHttpExecutor_1.httpExecutor
|
134 | .doApiRequest(builder_util_runtime_1.configureRequestOptions({
|
135 | protocol: parsedUrl.protocol,
|
136 | hostname: parsedUrl.hostname,
|
137 | path: parsedUrl.path,
|
138 | method: "POST",
|
139 | headers: {
|
140 | accept: "application/vnd.github.v3+json",
|
141 | "Content-Type": mime.getType(fileName) || "application/octet-stream",
|
142 | "Content-Length": dataLength,
|
143 | },
|
144 | }, this.token), this.context.cancellationToken, requestProcessor)
|
145 | .catch(e => {
|
146 | if (e.statusCode === 422 && e.description != null && e.description.errors != null && e.description.errors[0].code === "already_exists") {
|
147 | return this.overwriteArtifact(fileName, release).then(() => this.doUploadFile(attemptNumber, parsedUrl, fileName, dataLength, requestProcessor, release));
|
148 | }
|
149 | if (attemptNumber > 3) {
|
150 | return Promise.reject(e);
|
151 | }
|
152 | else {
|
153 | return new Promise((resolve, reject) => {
|
154 | const newAttemptNumber = attemptNumber + 1;
|
155 | setTimeout(() => {
|
156 | this.doUploadFile(newAttemptNumber, parsedUrl, fileName, dataLength, requestProcessor, release).then(resolve).catch(reject);
|
157 | }, newAttemptNumber * 2000);
|
158 | });
|
159 | }
|
160 | });
|
161 | }
|
162 | createRelease() {
|
163 | return this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases`, this.token, {
|
164 | tag_name: this.tag,
|
165 | draft: this.releaseType === "draft",
|
166 | prerelease: this.releaseType === "prerelease",
|
167 | });
|
168 | }
|
169 |
|
170 |
|
171 | async getRelease() {
|
172 | return this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases/${(await this._release.value).id}`, this.token);
|
173 | }
|
174 |
|
175 | async deleteRelease() {
|
176 | if (!this._release.hasValue) {
|
177 | return;
|
178 | }
|
179 | const release = await this._release.value;
|
180 | for (let i = 0; i < 3; i++) {
|
181 | try {
|
182 | return await this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases/${release.id}`, this.token, null, "DELETE");
|
183 | }
|
184 | catch (e) {
|
185 | if (e instanceof builder_util_runtime_1.HttpError) {
|
186 | if (e.statusCode === 404) {
|
187 | builder_util_1.log.warn({ releaseId: release.id, reason: "doesn't exist" }, "cannot delete release");
|
188 | return;
|
189 | }
|
190 | else if (e.statusCode === 405 || e.statusCode === 502) {
|
191 | continue;
|
192 | }
|
193 | }
|
194 | throw e;
|
195 | }
|
196 | }
|
197 | builder_util_1.log.warn({ releaseId: release.id }, "cannot delete release");
|
198 | }
|
199 | githubRequest(path, token, data = null, method) {
|
200 |
|
201 | const baseUrl = url_1.parse(`https://${this.info.host || "api.github.com"}`);
|
202 | return builder_util_runtime_1.parseJson(nodeHttpExecutor_1.httpExecutor.request(builder_util_runtime_1.configureRequestOptions({
|
203 | protocol: baseUrl.protocol,
|
204 | hostname: baseUrl.hostname,
|
205 | port: baseUrl.port,
|
206 | path: this.info.host != null && this.info.host !== "github.com" ? `/api/v3${path.startsWith("/") ? path : `/${path}`}` : path,
|
207 | headers: { accept: "application/vnd.github.v3+json" },
|
208 | }, token, method), this.context.cancellationToken, data));
|
209 | }
|
210 | toString() {
|
211 | return `Github (owner: ${this.info.owner}, project: ${this.info.repo}, version: ${this.version})`;
|
212 | }
|
213 | }
|
214 | exports.GitHubPublisher = GitHubPublisher;
|
215 |
|
\ | No newline at end of file |