226 lines
11 KiB
JavaScript
226 lines
11 KiB
JavaScript
|
"use strict";
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.GitHubPublisher = void 0;
|
||
|
const builder_util_1 = require("builder-util");
|
||
|
const builder_util_runtime_1 = require("builder-util-runtime");
|
||
|
const nodeHttpExecutor_1 = require("builder-util/out/nodeHttpExecutor");
|
||
|
const lazy_val_1 = require("lazy-val");
|
||
|
const mime = require("mime");
|
||
|
const url_1 = require("url");
|
||
|
const publisher_1 = require("./publisher");
|
||
|
class GitHubPublisher extends publisher_1.HttpPublisher {
|
||
|
constructor(context, info, version, options = {}) {
|
||
|
super(context, true);
|
||
|
this.info = info;
|
||
|
this.version = version;
|
||
|
this.options = options;
|
||
|
this._release = new lazy_val_1.Lazy(() => (this.token === "__test__" ? Promise.resolve(null) : this.getOrCreateRelease()));
|
||
|
this.providerName = "github";
|
||
|
this.releaseLogFields = null;
|
||
|
let token = info.token;
|
||
|
if ((0, builder_util_1.isEmptyOrSpaces)(token)) {
|
||
|
token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
|
||
|
if ((0, builder_util_1.isEmptyOrSpaces)(token)) {
|
||
|
throw new builder_util_1.InvalidConfigurationError(`GitHub Personal Access Token is not set, neither programmatically, nor using env "GH_TOKEN"`);
|
||
|
}
|
||
|
token = token.trim();
|
||
|
if (!(0, builder_util_1.isTokenCharValid)(token)) {
|
||
|
throw new builder_util_1.InvalidConfigurationError(`GitHub Personal Access Token (${JSON.stringify(token)}) contains invalid characters, please check env "GH_TOKEN"`);
|
||
|
}
|
||
|
}
|
||
|
this.token = token;
|
||
|
if (version.startsWith("v")) {
|
||
|
throw new builder_util_1.InvalidConfigurationError(`Version must not start with "v": ${version}`);
|
||
|
}
|
||
|
this.tag = info.vPrefixedTagName === false ? version : `v${version}`;
|
||
|
if ((0, builder_util_1.isEnvTrue)(process.env.EP_DRAFT)) {
|
||
|
this.releaseType = "draft";
|
||
|
builder_util_1.log.info({ reason: "env EP_DRAFT is set to true" }, "GitHub provider release type is set to draft");
|
||
|
}
|
||
|
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 */) {
|
||
|
this.releaseType = "prerelease";
|
||
|
builder_util_1.log.info({ reason: "env EP_PRE_RELEASE is set to true" }, "GitHub provider release type is set to prerelease");
|
||
|
}
|
||
|
else if (info.releaseType != null) {
|
||
|
this.releaseType = info.releaseType;
|
||
|
}
|
||
|
else if (options.prerelease) {
|
||
|
this.releaseType = "prerelease";
|
||
|
}
|
||
|
else {
|
||
|
// noinspection PointlessBooleanExpressionJS
|
||
|
this.releaseType = options.draft === false ? "release" : "draft";
|
||
|
}
|
||
|
}
|
||
|
async getOrCreateRelease() {
|
||
|
const logFields = {
|
||
|
tag: this.tag,
|
||
|
version: this.version,
|
||
|
};
|
||
|
// 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
|
||
|
const releases = await this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases`, this.token);
|
||
|
for (const release of releases) {
|
||
|
if (!(release.tag_name === this.tag || release.tag_name === this.version)) {
|
||
|
continue;
|
||
|
}
|
||
|
if (release.draft) {
|
||
|
return release;
|
||
|
}
|
||
|
// https://github.com/electron-userland/electron-builder/issues/1197
|
||
|
// https://github.com/electron-userland/electron-builder/issues/2072
|
||
|
if (this.releaseType === "draft") {
|
||
|
this.releaseLogFields = {
|
||
|
reason: "existing type not compatible with publishing type",
|
||
|
...logFields,
|
||
|
existingType: release.prerelease ? "pre-release" : "release",
|
||
|
publishingType: this.releaseType,
|
||
|
};
|
||
|
builder_util_1.log.warn(this.releaseLogFields, "GitHub release not created");
|
||
|
return null;
|
||
|
}
|
||
|
// https://github.com/electron-userland/electron-builder/issues/1133
|
||
|
// https://github.com/electron-userland/electron-builder/issues/2074
|
||
|
// if release created < 2 hours — allow to upload
|
||
|
const publishedAt = release.published_at == null ? null : Date.parse(release.published_at);
|
||
|
if (!(0, builder_util_1.isEnvTrue)(process.env.EP_GH_IGNORE_TIME) && publishedAt != null && Date.now() - publishedAt > 2 * 3600 * 1000) {
|
||
|
// https://github.com/electron-userland/electron-builder/issues/1183#issuecomment-275867187
|
||
|
this.releaseLogFields = {
|
||
|
reason: "existing release published more than 2 hours ago",
|
||
|
...logFields,
|
||
|
date: new Date(publishedAt).toString(),
|
||
|
};
|
||
|
builder_util_1.log.warn(this.releaseLogFields, "GitHub release not created");
|
||
|
return null;
|
||
|
}
|
||
|
return release;
|
||
|
}
|
||
|
// https://github.com/electron-userland/electron-builder/issues/1835
|
||
|
if (this.options.publish === "always" || (0, publisher_1.getCiTag)() != null) {
|
||
|
builder_util_1.log.info({
|
||
|
reason: "release doesn't exist",
|
||
|
...logFields,
|
||
|
}, `creating GitHub release`);
|
||
|
return this.createRelease();
|
||
|
}
|
||
|
this.releaseLogFields = {
|
||
|
reason: 'release doesn\'t exist and not created because "publish" is not "always" and build is not on tag',
|
||
|
...logFields,
|
||
|
};
|
||
|
return null;
|
||
|
}
|
||
|
async overwriteArtifact(fileName, release) {
|
||
|
// delete old artifact and re-upload
|
||
|
builder_util_1.log.warn({ file: fileName, reason: "already exists on GitHub" }, "overwrite published file");
|
||
|
const assets = await this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases/${release.id}/assets`, this.token, null);
|
||
|
for (const asset of assets) {
|
||
|
if (asset.name === fileName) {
|
||
|
await this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases/assets/${asset.id}`, this.token, null, "DELETE");
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
builder_util_1.log.debug({ file: fileName, reason: "not found on GitHub" }, "trying to upload again");
|
||
|
}
|
||
|
async doUpload(fileName, arch, dataLength, requestProcessor) {
|
||
|
const release = await this._release.value;
|
||
|
if (release == null) {
|
||
|
builder_util_1.log.warn({ file: fileName, ...this.releaseLogFields }, "skipped publishing");
|
||
|
return;
|
||
|
}
|
||
|
const parsedUrl = (0, url_1.parse)(`${release.upload_url.substring(0, release.upload_url.indexOf("{"))}?name=${fileName}`);
|
||
|
return await this.doUploadFile(0, parsedUrl, fileName, dataLength, requestProcessor, release);
|
||
|
}
|
||
|
doUploadFile(attemptNumber, parsedUrl, fileName, dataLength, requestProcessor, release) {
|
||
|
return nodeHttpExecutor_1.httpExecutor
|
||
|
.doApiRequest((0, builder_util_runtime_1.configureRequestOptions)({
|
||
|
protocol: parsedUrl.protocol,
|
||
|
hostname: parsedUrl.hostname,
|
||
|
path: parsedUrl.path,
|
||
|
method: "POST",
|
||
|
headers: {
|
||
|
accept: "application/vnd.github.v3+json",
|
||
|
"Content-Type": mime.getType(fileName) || "application/octet-stream",
|
||
|
"Content-Length": dataLength,
|
||
|
},
|
||
|
timeout: this.info.timeout || undefined,
|
||
|
}, this.token), this.context.cancellationToken, requestProcessor)
|
||
|
.catch((e) => {
|
||
|
if (attemptNumber > 3) {
|
||
|
return Promise.reject(e);
|
||
|
}
|
||
|
else if (this.doesErrorMeanAlreadyExists(e)) {
|
||
|
return this.overwriteArtifact(fileName, release).then(() => this.doUploadFile(attemptNumber + 1, parsedUrl, fileName, dataLength, requestProcessor, release));
|
||
|
}
|
||
|
else {
|
||
|
return new Promise((resolve, reject) => {
|
||
|
const newAttemptNumber = attemptNumber + 1;
|
||
|
setTimeout(() => {
|
||
|
this.doUploadFile(newAttemptNumber, parsedUrl, fileName, dataLength, requestProcessor, release).then(resolve).catch(reject);
|
||
|
}, newAttemptNumber * 2000);
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
doesErrorMeanAlreadyExists(e) {
|
||
|
if (!e.description) {
|
||
|
return false;
|
||
|
}
|
||
|
const desc = e.description;
|
||
|
const descIncludesAlreadyExists = (desc.includes("errors") && desc.includes("already_exists")) || (desc.errors && desc.errors.length >= 1 && desc.errors[0].code === "already_exists");
|
||
|
return e.statusCode === 422 && descIncludesAlreadyExists;
|
||
|
}
|
||
|
createRelease() {
|
||
|
return this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases`, this.token, {
|
||
|
tag_name: this.tag,
|
||
|
name: this.version,
|
||
|
draft: this.releaseType === "draft",
|
||
|
prerelease: this.releaseType === "prerelease",
|
||
|
});
|
||
|
}
|
||
|
// test only
|
||
|
//noinspection JSUnusedGlobalSymbols
|
||
|
async getRelease() {
|
||
|
return this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases/${(await this._release.value).id}`, this.token);
|
||
|
}
|
||
|
//noinspection JSUnusedGlobalSymbols
|
||
|
async deleteRelease() {
|
||
|
if (!this._release.hasValue) {
|
||
|
return;
|
||
|
}
|
||
|
const release = await this._release.value;
|
||
|
for (let i = 0; i < 3; i++) {
|
||
|
try {
|
||
|
return await this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases/${release.id}`, this.token, null, "DELETE");
|
||
|
}
|
||
|
catch (e) {
|
||
|
if (e instanceof builder_util_runtime_1.HttpError) {
|
||
|
if (e.statusCode === 404) {
|
||
|
builder_util_1.log.warn({ releaseId: release.id, reason: "doesn't exist" }, "cannot delete release");
|
||
|
return;
|
||
|
}
|
||
|
else if (e.statusCode === 405 || e.statusCode === 502) {
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
throw e;
|
||
|
}
|
||
|
}
|
||
|
builder_util_1.log.warn({ releaseId: release.id }, "cannot delete release");
|
||
|
}
|
||
|
githubRequest(path, token, data = null, method) {
|
||
|
// host can contains port, but node http doesn't support host as url does
|
||
|
const baseUrl = (0, url_1.parse)(`https://${this.info.host || "api.github.com"}`);
|
||
|
return (0, builder_util_runtime_1.parseJson)(nodeHttpExecutor_1.httpExecutor.request((0, builder_util_runtime_1.configureRequestOptions)({
|
||
|
protocol: baseUrl.protocol,
|
||
|
hostname: baseUrl.hostname,
|
||
|
port: baseUrl.port,
|
||
|
path: this.info.host != null && this.info.host !== "github.com" ? `/api/v3${path.startsWith("/") ? path : `/${path}`}` : path,
|
||
|
headers: { accept: "application/vnd.github.v3+json" },
|
||
|
timeout: this.info.timeout || undefined,
|
||
|
}, token, method), this.context.cancellationToken, data));
|
||
|
}
|
||
|
toString() {
|
||
|
return `Github (owner: ${this.info.owner}, project: ${this.info.repo}, version: ${this.version})`;
|
||
|
}
|
||
|
}
|
||
|
exports.GitHubPublisher = GitHubPublisher;
|
||
|
//# sourceMappingURL=gitHubPublisher.js.map
|