UNPKG

11.5 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.GitHubPublisher = void 0;
4const builder_util_1 = require("builder-util");
5const builder_util_runtime_1 = require("builder-util-runtime");
6const nodeHttpExecutor_1 = require("builder-util/out/nodeHttpExecutor");
7const lazy_val_1 = require("lazy-val");
8const mime = require("mime");
9const url_1 = require("url");
10const publisher_1 = require("./publisher");
11class 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 ((0, builder_util_1.isEmptyOrSpaces)(token)) {
22 token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
23 if ((0, 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 (!(0, 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 ((0, 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 ((0, builder_util_1.isEnvTrue)(process.env.EP_PRE_RELEASE) || (0, builder_util_1.isEnvTrue)(process.env.EP_PRELEASE) /* https://github.com/electron-userland/electron-builder/issues/2878 */) {
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 // noinspection PointlessBooleanExpressionJS
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 // we don't use "Get a release by tag name" because "tag name" means existing git tag, but we draft release and don't create git tag
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 // https://github.com/electron-userland/electron-builder/issues/1197
70 // https://github.com/electron-userland/electron-builder/issues/2072
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 // https://github.com/electron-userland/electron-builder/issues/1133
82 // https://github.com/electron-userland/electron-builder/issues/2074
83 // if release created < 2 hours — allow to upload
84 const publishedAt = release.published_at == null ? null : Date.parse(release.published_at);
85 if (!(0, builder_util_1.isEnvTrue)(process.env.EP_GH_IGNORE_TIME) && publishedAt != null && Date.now() - publishedAt > 2 * 3600 * 1000) {
86 // https://github.com/electron-userland/electron-builder/issues/1183#issuecomment-275867187
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 // https://github.com/electron-userland/electron-builder/issues/1835
98 if (this.options.publish === "always" || (0, 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 // delete old artifact and re-upload
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 = (0, 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((0, 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 timeout: this.info.timeout || undefined,
145 }, this.token), this.context.cancellationToken, requestProcessor)
146 .catch((e) => {
147 if (attemptNumber > 3) {
148 return Promise.reject(e);
149 }
150 else if (this.doesErrorMeanAlreadyExists(e)) {
151 return this.overwriteArtifact(fileName, release).then(() => this.doUploadFile(attemptNumber + 1, parsedUrl, fileName, dataLength, requestProcessor, release));
152 }
153 else {
154 return new Promise((resolve, reject) => {
155 const newAttemptNumber = attemptNumber + 1;
156 setTimeout(() => {
157 this.doUploadFile(newAttemptNumber, parsedUrl, fileName, dataLength, requestProcessor, release).then(resolve).catch(reject);
158 }, newAttemptNumber * 2000);
159 });
160 }
161 });
162 }
163 doesErrorMeanAlreadyExists(e) {
164 if (!e.description) {
165 return false;
166 }
167 const desc = e.description;
168 const descIncludesAlreadyExists = (desc.includes("errors") && desc.includes("already_exists")) || (desc.errors && desc.errors.length >= 1 && desc.errors[0].code === "already_exists");
169 return e.statusCode === 422 && descIncludesAlreadyExists;
170 }
171 createRelease() {
172 return this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases`, this.token, {
173 tag_name: this.tag,
174 name: this.version,
175 draft: this.releaseType === "draft",
176 prerelease: this.releaseType === "prerelease",
177 });
178 }
179 // test only
180 //noinspection JSUnusedGlobalSymbols
181 async getRelease() {
182 return this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases/${(await this._release.value).id}`, this.token);
183 }
184 //noinspection JSUnusedGlobalSymbols
185 async deleteRelease() {
186 if (!this._release.hasValue) {
187 return;
188 }
189 const release = await this._release.value;
190 for (let i = 0; i < 3; i++) {
191 try {
192 return await this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases/${release.id}`, this.token, null, "DELETE");
193 }
194 catch (e) {
195 if (e instanceof builder_util_runtime_1.HttpError) {
196 if (e.statusCode === 404) {
197 builder_util_1.log.warn({ releaseId: release.id, reason: "doesn't exist" }, "cannot delete release");
198 return;
199 }
200 else if (e.statusCode === 405 || e.statusCode === 502) {
201 continue;
202 }
203 }
204 throw e;
205 }
206 }
207 builder_util_1.log.warn({ releaseId: release.id }, "cannot delete release");
208 }
209 githubRequest(path, token, data = null, method) {
210 // host can contains port, but node http doesn't support host as url does
211 const baseUrl = (0, url_1.parse)(`https://${this.info.host || "api.github.com"}`);
212 return (0, builder_util_runtime_1.parseJson)(nodeHttpExecutor_1.httpExecutor.request((0, builder_util_runtime_1.configureRequestOptions)({
213 protocol: baseUrl.protocol,
214 hostname: baseUrl.hostname,
215 port: baseUrl.port,
216 path: this.info.host != null && this.info.host !== "github.com" ? `/api/v3${path.startsWith("/") ? path : `/${path}`}` : path,
217 headers: { accept: "application/vnd.github.v3+json" },
218 timeout: this.info.timeout || undefined,
219 }, token, method), this.context.cancellationToken, data));
220 }
221 toString() {
222 return `Github (owner: ${this.info.owner}, project: ${this.info.repo}, version: ${this.version})`;
223 }
224}
225exports.GitHubPublisher = GitHubPublisher;
226//# sourceMappingURL=gitHubPublisher.js.map
\No newline at end of file