OverviewFilesCommitsIssuesDocsReleasesPeers

mirall-cli / node_modules / electron-publish / out / gitHubPublisher.js

233 lines · javascript ref:
"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 lazy_val_1 = require("lazy-val");
const mime = require("mime");
const url_1 = require("url");
const httpPublisher_1 = require("./httpPublisher");
const publisher_1 = require("./publisher");
const util_1 = require("./util");
class GitHubPublisher extends httpPublisher_1.HttpPublisher {
    constructor(context, info, version, options = {}, releaseBody, releaseName) {
        super(context, true);
        this.info = info;
        this.version = version;
        this.options = options;
        this.releaseBody = releaseBody;
        this.releaseName = releaseName;
        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) || process.env.GITHUB_RELEASE_TOKEN) {
            token = process.env.GITHUB_RELEASE_TOKEN ? process.env.GITHUB_RELEASE_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 ${(0, builder_util_runtime_1.hashSensitiveValue)(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 = (0, builder_util_runtime_1.githubTagPrefix)(info) + 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 builder_util_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() {
        const data = {
            tag_name: this.tag,
            name: this.releaseName || this.version,
            draft: this.releaseType === "draft",
            prerelease: this.releaseType === "prerelease",
        };
        if (this.releaseBody) {
            data.body = (0, util_1.trimStringWithWarn)(this.releaseBody, 100000, "release body exceeds GitHub API limit, truncating");
        }
        return this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases`, this.token, data);
    }
    // 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)(builder_util_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