vrrv-installer-builder
Version:
A complete solution to package and build a ready for distribution Electron app for MacOS, Windows and Linux with “auto update” support out of the box
231 lines (227 loc) • 13.7 kB
JavaScript
;
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
const metadata_1 = require("../metadata");
const util_1 = require("../util/util");
const path = require("path");
const bluebird_1 = require("bluebird");
const binDownload_1 = require("../util/binDownload");
const uuid_1345_1 = require("uuid-1345");
const platformPackager_1 = require("../platformPackager");
const archive_1 = require("./archive");
const log_1 = require("../util/log");
const fs_extra_p_1 = require("fs-extra-p");
const semver = require("semver");
//noinspection JSUnusedLocalSymbols
const __awaiter = require("../util/awaiter");
const NSIS_VERSION = "3.0.1";
//noinspection SpellCheckingInspection
const NSIS_SHA2 = "23280f66c07c923da6f29a3c318377720c8ecd7af4de3755256d1ecf60d07f74";
//noinspection SpellCheckingInspection
const ELECTRON_BUILDER_NS_UUID = "50e065bc-3134-11e6-9bab-38c9862bdaf3";
const nsisPathPromise = binDownload_1.getBinFromBintray("nsis", NSIS_VERSION, NSIS_SHA2);
class NsisTarget extends platformPackager_1.Target {
constructor(packager, outDir) {
super("nsis");
this.packager = packager;
this.outDir = outDir;
this.archs = new Map();
this.nsisTemplatesDir = path.join(__dirname, "..", "..", "templates", "nsis");
this.options = packager.info.devMetadata.build.nsis || Object.create(null);
// CFBundleTypeName
// https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-101685
// CFBundleTypeExtensions
}
build(arch, appOutDir) {
return __awaiter(this, void 0, void 0, function* () {
const packager = this.packager;
const archSuffix = metadata_1.Arch[arch];
const archiveFile = path.join(this.outDir, `${ packager.appInfo.name }-${ packager.appInfo.version }-${ archSuffix }.nsis.7z`);
this.archs.set(arch, log_1.task(`Creating NSIS ${ archSuffix } package`, archive_1.archiveApp(packager.devMetadata.build.compression, "7z", archiveFile, appOutDir, true)));
});
}
finishBuild() {
return log_1.task("Building NSIS installer", this.buildInstaller().then(() => bluebird_1.Promise.map(this.archs.values(), it => fs_extra_p_1.unlink(it))));
}
buildInstaller() {
return __awaiter(this, void 0, void 0, function* () {
const packager = this.packager;
const iconPath = yield packager.getIconPath();
const appInfo = packager.appInfo;
const version = appInfo.version;
const installerPath = path.join(this.outDir, `${ appInfo.productFilename } Setup ${ version }.exe`);
const guid = this.options.guid || (yield bluebird_1.Promise.promisify(uuid_1345_1.v5)({ namespace: ELECTRON_BUILDER_NS_UUID, name: appInfo.id }));
const defines = {
APP_ID: appInfo.id,
APP_GUID: guid,
PRODUCT_NAME: appInfo.productName,
PRODUCT_FILENAME: appInfo.productFilename,
APP_DESCRIPTION: appInfo.description,
VERSION: version,
COMPANY_NAME: appInfo.companyName,
PROJECT_DIR: this.packager.projectDir,
BUILD_RESOURCES_DIR: this.packager.buildResourcesDir
};
if (iconPath != null) {
defines.MUI_ICON = iconPath;
defines.MUI_UNICON = iconPath;
}
for (let _ref of this.archs) {
var _ref2 = _slicedToArray(_ref, 2);
let arch = _ref2[0];
let file = _ref2[1];
defines[arch === metadata_1.Arch.x64 ? "APP_64" : "APP_32"] = yield file;
}
const oneClick = this.options.oneClick !== false;
const installerHeader = oneClick ? null : yield this.packager.getResource(this.options.installerHeader, "installerHeader.bmp");
if (installerHeader != null) {
defines.MUI_HEADERIMAGE = null;
defines.MUI_HEADERIMAGE_RIGHT = null;
defines.MUI_HEADERIMAGE_BITMAP = installerHeader;
}
const installerHeaderIcon = oneClick ? yield this.packager.getResource(this.options.installerHeaderIcon, "installerHeaderIcon.ico") : null;
if (installerHeaderIcon != null) {
defines.HEADER_ICO = installerHeaderIcon;
}
if (this.options.perMachine === true) {
defines.INSTALL_MODE_PER_ALL_USERS = null;
}
if (!oneClick || this.options.perMachine === true) {
defines.INSTALL_MODE_PER_ALL_USERS_REQUIRED = null;
}
if (oneClick) {
if (this.options.runAfterFinish !== false) {
defines.RUN_AFTER_FINISH = null;
}
} else if (this.options.allowElevation !== false) {
defines.MULTIUSER_INSTALLMODE_ALLOW_ELEVATION = null;
}
// Error: invalid VIProductVersion format, should be X.X.X.X
// so, we must strip beta
const parsedVersion = new semver.SemVer(appInfo.version);
const localeId = this.options.language || "1033";
const versionKey = [`/LANG=${ localeId } ProductName "${ appInfo.productName }"`, `/LANG=${ localeId } ProductVersion "${ appInfo.version }"`, `/LANG=${ localeId } CompanyName "${ appInfo.companyName }"`, `/LANG=${ localeId } LegalCopyright "${ appInfo.copyright }"`, `/LANG=${ localeId } FileDescription "${ appInfo.description }"`, `/LANG=${ localeId } FileVersion "${ appInfo.buildVersion }"`];
util_1.use(this.packager.platformSpecificBuildOptions.legalTrademarks, it => versionKey.push(`/LANG=${ localeId } LegalTrademarks "${ it }"`));
const commands = {
OutFile: `"${ installerPath }"`,
VIProductVersion: `${ parsedVersion.major }.${ parsedVersion.minor }.${ parsedVersion.patch }.${ appInfo.buildNumber || "0" }`,
VIAddVersionKey: versionKey
};
if (packager.devMetadata.build.compression === "store") {
commands.SetCompress = "off";
defines.COMPRESS = "off";
} else {
commands.SetCompressor = "lzma";
// default is 8: test app installer size 37.2 vs 36 if dict size 64
commands.SetCompressorDictSize = "64";
defines.COMPRESS = "auto";
}
if (oneClick) {
defines.ONE_CLICK = null;
}
util_1.debug(defines);
util_1.debug(commands);
if (this.packager.options.effectiveOptionComputed != null && this.packager.options.effectiveOptionComputed([defines, commands])) {
return;
}
const customScriptPath = yield this.packager.getResource(this.options.script, "installer.nsi");
const script = yield fs_extra_p_1.readFile(customScriptPath || path.join(this.nsisTemplatesDir, "installer.nsi"), "utf8");
if (customScriptPath == null) {
const uninstallerPath = yield packager.getTempFile("uninstaller.exe");
const isWin = process.platform === "win32";
defines.BUILD_UNINSTALLER = null;
defines.UNINSTALLER_OUT_FILE = isWin ? uninstallerPath : path.win32.join("Z:", uninstallerPath);
yield log_1.subTask(`Executing makensis — uninstaller`, this.executeMakensis(defines, commands, false, script));
yield util_1.exec(isWin ? installerPath : "wine", isWin ? [] : [installerPath]);
yield packager.sign(uninstallerPath);
delete defines.BUILD_UNINSTALLER;
// platform-specific path, not wine
defines.UNINSTALLER_OUT_FILE = uninstallerPath;
} else {
log_1.log("Custom NSIS script is used - uninstaller is not signed by electron-builder");
}
yield log_1.subTask(`Executing makensis — installer`, this.executeMakensis(defines, commands, true, script));
yield packager.sign(installerPath);
this.packager.dispatchArtifactCreated(installerPath, `${ appInfo.name }-Setup-${ version }.exe`);
});
}
executeMakensis(defines, commands, isInstaller, originalScript) {
return __awaiter(this, void 0, void 0, function* () {
const args = this.options.warningsAsErrors === false ? [] : ["-WX"];
for (let name of Object.keys(defines)) {
const value = defines[name];
if (value == null) {
args.push(`-D${ name }`);
} else {
args.push(`-D${ name }=${ value }`);
}
}
for (let name of Object.keys(commands)) {
const value = commands[name];
if (Array.isArray(value)) {
for (let c of value) {
args.push(`-X${ name } ${ c }`);
}
} else {
args.push(`-X${ name } ${ value }`);
}
}
args.push("-");
const binDir = process.platform === "darwin" ? "mac" : process.platform === "win32" ? "Bin" : "linux";
const nsisPath = yield nsisPathPromise;
let script = originalScript;
const packager = this.packager;
const customInclude = yield packager.getResource(this.options.include, "installer.nsh");
if (customInclude != null) {
script = `!include "${ customInclude }"\n!addincludedir "${ packager.buildResourcesDir }"\n${ script }`;
}
const fileAssociations = packager.getFileAssociations();
if (fileAssociations.length !== 0) {
script = "!include FileAssociation.nsh\n" + script;
if (isInstaller) {
let registerFileAssociationsScript = "";
for (let item of fileAssociations) {
const extensions = util_1.asArray(item.ext).map(platformPackager_1.normalizeExt);
for (let ext of extensions) {
const customIcon = yield packager.getResource(item.icon, `${ extensions[0] }.ico`);
let installedIconPath = "$appExe,0";
if (customIcon != null) {
installedIconPath = `$INSTDIR\\resources\\${ path.basename(customIcon) }`;
//noinspection SpellCheckingInspection
registerFileAssociationsScript += ` File "/oname=${ installedIconPath }" "${ customIcon }"\n`;
}
const icon = `"${ installedIconPath }"`;
const commandText = `"Open with ${ packager.appInfo.productName }"`;
const command = '"$appExe $\\"%1$\\""';
registerFileAssociationsScript += ` !insertmacro APP_ASSOCIATE "${ ext }" "${ item.name }" "${ item.description || "" }" ${ icon } ${ commandText } ${ command }\n`;
}
}
script = `!macro registerFileAssociations\n${ registerFileAssociationsScript }!macroend\n${ script }`;
} else {
let unregisterFileAssociationsScript = "";
for (let item of fileAssociations) {
for (let ext of util_1.asArray(item.ext)) {
unregisterFileAssociationsScript += ` !insertmacro APP_UNASSOCIATE "${ platformPackager_1.normalizeExt(ext) }" "${ item.name }"\n`;
}
}
script = `!macro unregisterFileAssociations\n${ unregisterFileAssociationsScript }!macroend\n${ script }`;
}
}
if (util_1.debug.enabled) {
process.stdout.write("\n\nNSIS script:\n\n" + script + "\n\n---\nEnd of NSIS script.\n\n");
}
yield new bluebird_1.Promise((resolve, reject) => {
const command = path.join(nsisPath, binDir, process.platform === "win32" ? "makensis.exe" : "makensis");
const childProcess = util_1.doSpawn(command, args, {
// we use NSIS_CONFIG_CONST_DATA_PATH=no to build makensis on Linux, but in any case it doesn't use stubs as MacOS/Windows version, so, we explicitly set NSISDIR
env: Object.assign({}, process.env, { NSISDIR: nsisPath }),
cwd: this.nsisTemplatesDir
}, true);
util_1.handleProcess("close", childProcess, command, resolve, reject);
childProcess.stdin.end(script);
});
});
}
}
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = NsisTarget;
//# sourceMappingURL=nsis.js.map