electron-updater
Version:
Cross platform updater for electron applications
198 lines • 9.89 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.GitHubProvider = exports.BaseGitHubProvider = void 0;
exports.computeReleaseNotes = computeReleaseNotes;
const builder_util_runtime_1 = require("builder-util-runtime");
const semver = require("semver");
const url_1 = require("url");
const util_1 = require("../util");
const Provider_1 = require("./Provider");
const hrefRegExp = /\/tag\/([^/]+)$/;
class BaseGitHubProvider extends Provider_1.Provider {
constructor(options, defaultHost, runtimeOptions) {
super({
...runtimeOptions,
/* because GitHib uses S3 */
isUseMultipleRangeRequest: false,
});
this.options = options;
this.baseUrl = (0, util_1.newBaseUrl)((0, builder_util_runtime_1.githubUrl)(options, defaultHost));
const apiHost = defaultHost === "github.com" ? "api.github.com" : defaultHost;
this.baseApiUrl = (0, util_1.newBaseUrl)((0, builder_util_runtime_1.githubUrl)(options, apiHost));
}
computeGithubBasePath(result) {
// https://github.com/electron-userland/electron-builder/issues/1903#issuecomment-320881211
const host = this.options.host;
return host && !["github.com", "api.github.com"].includes(host) ? `/api/v3${result}` : result;
}
}
exports.BaseGitHubProvider = BaseGitHubProvider;
class GitHubProvider extends BaseGitHubProvider {
constructor(options, updater, runtimeOptions) {
super(options, "github.com", runtimeOptions);
this.options = options;
this.updater = updater;
}
get channel() {
const result = this.updater.channel || this.options.channel;
return result == null ? this.getDefaultChannelName() : this.getCustomChannelName(result);
}
async getLatestVersion() {
var _a, _b, _c, _d, _e;
const cancellationToken = new builder_util_runtime_1.CancellationToken();
const feedXml = (await this.httpRequest((0, util_1.newUrlFromBase)(`${this.basePath}.atom`, this.baseUrl), {
accept: "application/xml, application/atom+xml, text/xml, */*",
}, cancellationToken));
const feed = (0, builder_util_runtime_1.parseXml)(feedXml);
// noinspection TypeScriptValidateJSTypes
let latestRelease = feed.element("entry", false, `No published versions on GitHub`);
let tag = null;
try {
if (this.updater.allowPrerelease) {
const currentChannel = ((_a = this.updater) === null || _a === void 0 ? void 0 : _a.channel) || ((_b = semver.prerelease(this.updater.currentVersion)) === null || _b === void 0 ? void 0 : _b[0]) || null;
if (currentChannel === null) {
// noinspection TypeScriptValidateJSTypes
tag = hrefRegExp.exec(latestRelease.element("link").attribute("href"))[1];
}
else {
for (const element of feed.getElements("entry")) {
// noinspection TypeScriptValidateJSTypes
const hrefElement = hrefRegExp.exec(element.element("link").attribute("href"));
// If this is null then something is wrong and skip this release
if (hrefElement === null)
continue;
// This Release's Tag
const hrefTag = hrefElement[1];
//Get Channel from this release's tag
const hrefChannel = ((_c = semver.prerelease(hrefTag)) === null || _c === void 0 ? void 0 : _c[0]) || null;
const shouldFetchVersion = !currentChannel || ["alpha", "beta"].includes(currentChannel);
const isCustomChannel = hrefChannel !== null && !["alpha", "beta"].includes(String(hrefChannel));
// Allow moving from alpha to beta but not down
const channelMismatch = currentChannel === "beta" && hrefChannel === "alpha";
if (shouldFetchVersion && !isCustomChannel && !channelMismatch) {
tag = hrefTag;
break;
}
const isNextPreRelease = hrefChannel && hrefChannel === currentChannel;
if (isNextPreRelease) {
tag = hrefTag;
break;
}
}
}
}
else {
tag = await this.getLatestTagName(cancellationToken);
for (const element of feed.getElements("entry")) {
// noinspection TypeScriptValidateJSTypes
if (hrefRegExp.exec(element.element("link").attribute("href"))[1] === tag) {
latestRelease = element;
break;
}
}
}
}
catch (e) {
throw (0, builder_util_runtime_1.newError)(`Cannot parse releases feed: ${e.stack || e.message},\nXML:\n${feedXml}`, "ERR_UPDATER_INVALID_RELEASE_FEED");
}
if (tag == null) {
throw (0, builder_util_runtime_1.newError)(`No published versions on GitHub`, "ERR_UPDATER_NO_PUBLISHED_VERSIONS");
}
let rawData;
let channelFile = "";
let channelFileUrl = "";
const fetchData = async (channelName) => {
channelFile = (0, util_1.getChannelFilename)(channelName);
channelFileUrl = (0, util_1.newUrlFromBase)(this.getBaseDownloadPath(String(tag), channelFile), this.baseUrl);
const requestOptions = this.createRequestOptions(channelFileUrl);
try {
return (await this.executor.request(requestOptions, cancellationToken));
}
catch (e) {
if (e instanceof builder_util_runtime_1.HttpError && e.statusCode === 404) {
throw (0, builder_util_runtime_1.newError)(`Cannot find ${channelFile} in the latest release artifacts (${channelFileUrl}): ${e.stack || e.message}`, "ERR_UPDATER_CHANNEL_FILE_NOT_FOUND");
}
throw e;
}
};
try {
let channel = this.channel;
if (this.updater.allowPrerelease && ((_d = semver.prerelease(tag)) === null || _d === void 0 ? void 0 : _d[0])) {
channel = this.getCustomChannelName(String((_e = semver.prerelease(tag)) === null || _e === void 0 ? void 0 : _e[0]));
}
rawData = await fetchData(channel);
}
catch (e) {
if (this.updater.allowPrerelease) {
// Allow fallback to `latest.yml`
rawData = await fetchData(this.getDefaultChannelName());
}
else {
throw e;
}
}
const result = (0, Provider_1.parseUpdateInfo)(rawData, channelFile, channelFileUrl);
if (result.releaseName == null) {
result.releaseName = latestRelease.elementValueOrEmpty("title");
}
if (result.releaseNotes == null) {
result.releaseNotes = computeReleaseNotes(this.updater.currentVersion, this.updater.fullChangelog, feed, latestRelease);
}
return {
tag: tag,
...result,
};
}
async getLatestTagName(cancellationToken) {
const options = this.options;
// do not use API for GitHub to avoid limit, only for custom host or GitHub Enterprise
const url = options.host == null || options.host === "github.com"
? (0, util_1.newUrlFromBase)(`${this.basePath}/latest`, this.baseUrl)
: new url_1.URL(`${this.computeGithubBasePath(`/repos/${options.owner}/${options.repo}/releases`)}/latest`, this.baseApiUrl);
try {
const rawData = await this.httpRequest(url, { Accept: "application/json" }, cancellationToken);
if (rawData == null) {
return null;
}
const releaseInfo = JSON.parse(rawData);
return releaseInfo.tag_name;
}
catch (e) {
throw (0, builder_util_runtime_1.newError)(`Unable to find latest version on GitHub (${url}), please ensure a production release exists: ${e.stack || e.message}`, "ERR_UPDATER_LATEST_VERSION_NOT_FOUND");
}
}
get basePath() {
return `/${this.options.owner}/${this.options.repo}/releases`;
}
resolveFiles(updateInfo) {
// still replace space to - due to backward compatibility
return (0, Provider_1.resolveFiles)(updateInfo, this.baseUrl, p => this.getBaseDownloadPath(updateInfo.tag, p.replace(/ /g, "-")));
}
getBaseDownloadPath(tag, fileName) {
return `${this.basePath}/download/${tag}/${fileName}`;
}
}
exports.GitHubProvider = GitHubProvider;
function getNoteValue(parent) {
const result = parent.elementValueOrEmpty("content");
// GitHub reports empty notes as <content>No content.</content>
return result === "No content." ? "" : result;
}
function computeReleaseNotes(currentVersion, isFullChangelog, feed, latestRelease) {
if (!isFullChangelog) {
return getNoteValue(latestRelease);
}
const releaseNotes = [];
for (const release of feed.getElements("entry")) {
// noinspection TypeScriptValidateJSTypes
const versionRelease = /\/tag\/v?([^/]+)$/.exec(release.element("link").attribute("href"))[1];
if (semver.lt(currentVersion, versionRelease)) {
releaseNotes.push({
version: versionRelease,
note: getNoteValue(release),
});
}
}
return releaseNotes.sort((a, b) => semver.rcompare(a.version, b.version));
}
//# sourceMappingURL=GitHubProvider.js.map
;