UNPKG

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

371 lines (369 loc) 19.2 kB
"use strict"; const metadata_1 = require("./metadata"); const bluebird_1 = require("bluebird"); const path = require("path"); const fs_extra_p_1 = require("fs-extra-p"); const util_1 = require("./util/util"); const archive_1 = require("./targets/archive"); const asarUtil_1 = require("./asarUtil"); const log_1 = require("./util/log"); const filter_1 = require("./util/filter"); const dirPackager_1 = require("./packager/dirPackager"); const fileMatcher_1 = require("./fileMatcher"); //noinspection JSUnusedLocalSymbols const __awaiter = require("./util/awaiter"); class Target { constructor(name) { this.name = name; } finishBuild() { return bluebird_1.Promise.resolve(); } } exports.Target = Target; class TargetEx extends Target {} exports.TargetEx = TargetEx; class PlatformPackager { constructor(info) { this.info = info; this.devMetadata = info.devMetadata; this.platformSpecificBuildOptions = this.normalizePlatformSpecificBuildOptions(info.devMetadata.build[this.platform.buildConfigurationKey]); this.appInfo = this.prepareAppInfo(info.appInfo); this.options = info.options; this.projectDir = info.projectDir; this.buildResourcesDir = path.resolve(this.projectDir, this.relativeBuildResourcesDirname); this.resourceList = fs_extra_p_1.readdir(this.buildResourcesDir).catch(e => { if (e.code !== "ENOENT") { throw e; } return []; }); } get platform() {} prepareAppInfo(appInfo) { return appInfo; } normalizePlatformSpecificBuildOptions(options) { return options == null ? Object.create(null) : options; } getCscPassword() { const password = this.options.cscKeyPassword || process.env.CSC_KEY_PASSWORD; if (util_1.isEmptyOrSpaces(password)) { log_1.log("CSC_KEY_PASSWORD is not defined, empty password will be used"); return ""; } else { return password.trim(); } } get relativeBuildResourcesDirname() { return util_1.use(this.devMetadata.directories, it => it.buildResources) || "build"; } computeAppOutDir(outDir, arch) { return path.join(outDir, `${ this.platform.buildConfigurationKey }${ getArchSuffix(arch) }${ this.platform === metadata_1.Platform.MAC ? "" : "-unpacked" }`); } dispatchArtifactCreated(file, artifactName) { this.info.eventEmitter.emit("artifactCreated", { file: file, artifactName: artifactName, platform: this.platform, packager: this }); } getExtraFileMatchers(isResources, appOutDir, fileMatchOptions, customBuildOptions) { const base = isResources ? this.getResourcesDir(appOutDir) : this.platform === metadata_1.Platform.MAC ? path.join(appOutDir, `${ this.appInfo.productFilename }.app`, "Contents") : appOutDir; return this.getFileMatchers(isResources ? "extraResources" : "extraFiles", this.projectDir, base, true, fileMatchOptions, customBuildOptions); } doPack(outDir, appOutDir, platformName, arch, platformSpecificBuildOptions) { return __awaiter(this, void 0, void 0, function* () { const asarOptions = this.computeAsarOptions(platformSpecificBuildOptions); const fileMatchOptions = { arch: metadata_1.Arch[arch], os: this.platform.buildConfigurationKey }; const extraResourceMatchers = this.getExtraFileMatchers(true, appOutDir, fileMatchOptions, platformSpecificBuildOptions); const extraFileMatchers = this.getExtraFileMatchers(false, appOutDir, fileMatchOptions, platformSpecificBuildOptions); const resourcesPath = this.platform === metadata_1.Platform.MAC ? path.join(appOutDir, "Electron.app", "Contents", "Resources") : path.join(appOutDir, "resources"); const p = dirPackager_1.pack(this, appOutDir, platformName, metadata_1.Arch[arch], this.info.electronVersion, () => __awaiter(this, void 0, void 0, function* () { const ignoreFiles = new Set([path.resolve(this.info.appDir, outDir), path.resolve(this.info.appDir, this.buildResourcesDir)]); // prune dev or not listed dependencies const result = yield filter_1.devDependencies(this.info.appDir); for (let it of result) { ignoreFiles.add(it); } const patterns = this.getFileMatchers("files", this.info.appDir, path.join(resourcesPath, "app"), false, fileMatchOptions, platformSpecificBuildOptions); let defaultMatcher = patterns != null ? patterns[0] : new fileMatcher_1.FileMatcher(this.info.appDir, path.join(resourcesPath, "app"), fileMatchOptions); if (defaultMatcher.isEmpty()) { defaultMatcher.addPattern("**/*"); } defaultMatcher.addPattern("!**/node_modules/*/{README.md,README,readme.md,readme,test}"); defaultMatcher.addPattern("!**/node_modules/.bin"); defaultMatcher.addPattern("!**/*.{o,hprof,orig,pyc,pyo,rbc,swp}"); defaultMatcher.addPattern("!**/._*"); //noinspection SpellCheckingInspection defaultMatcher.addPattern("!**/{.DS_Store,.git,.hg,.svn,CVS,RCS,SCCS,__pycache__,thumbs.db,.gitignore,.gitattributes,.editorconfig,.idea,appveyor.yml,.travis.yml,circle.yml,npm-debug.log}"); let rawFilter = null; const deprecatedIgnore = this.devMetadata.build.ignore; if (deprecatedIgnore != null) { if (typeof deprecatedIgnore === "function") { log_1.log(`"ignore is specified as function, may be new "files" option will be suit your needs? Please see https://github.com/electron-userland/electron-builder/wiki/Options#BuildMetadata-files`); } else { log_1.warn(`"ignore is deprecated, please use "files", see https://github.com/electron-userland/electron-builder/wiki/Options#BuildMetadata-files`); } rawFilter = fileMatcher_1.deprecatedUserIgnoreFilter(deprecatedIgnore, this.info.appDir); } let excludePatterns = []; if (extraResourceMatchers != null) { for (let i = 0; i < extraResourceMatchers.length; i++) { const patterns = extraResourceMatchers[i].getParsedPatterns(this.info.projectDir); excludePatterns = excludePatterns.concat(patterns); } } if (extraFileMatchers != null) { for (let i = 0; i < extraFileMatchers.length; i++) { const patterns = extraFileMatchers[i].getParsedPatterns(this.info.projectDir); excludePatterns = excludePatterns.concat(patterns); } } const filter = defaultMatcher.createFilter(ignoreFiles, rawFilter, excludePatterns.length ? excludePatterns : null); const promise = asarOptions == null ? filter_1.copyFiltered(this.info.appDir, path.join(resourcesPath, "app"), filter, this.info.devMetadata.build.dereference || this.platform === metadata_1.Platform.WINDOWS) : asarUtil_1.createAsarArchive(this.info.appDir, resourcesPath, asarOptions, filter); const promises = [promise, util_1.unlinkIfExists(path.join(resourcesPath, "default_app.asar")), util_1.unlinkIfExists(path.join(appOutDir, "version"))]; if (this.info.electronVersion != null && this.info.electronVersion[0] === "0") { // electron release >= 0.37.4 - the default_app/ folder is a default_app.asar file promises.push(fs_extra_p_1.remove(path.join(resourcesPath, "default_app"))); } promises.push(this.postInitApp(appOutDir)); yield bluebird_1.Promise.all(promises); })); yield log_1.task(`Packaging for platform ${ platformName } ${ metadata_1.Arch[arch] } using electron ${ this.info.electronVersion } to ${ path.relative(this.projectDir, appOutDir) }`, p); yield this.doCopyExtraFiles(extraResourceMatchers); yield this.doCopyExtraFiles(extraFileMatchers); const afterPack = this.devMetadata.build.afterPack; if (afterPack != null) { yield afterPack({ appOutDir: appOutDir, options: this.devMetadata.build, packager: this }); } yield this.sanityCheckPackage(appOutDir, asarOptions != null); }); } postInitApp(executableFile) { return bluebird_1.Promise.resolve(null); } getIconPath() { return __awaiter(this, void 0, void 0, function* () { return null; }); } computeAsarOptions(customBuildOptions) { let result = this.devMetadata.build.asar; let platformSpecific = customBuildOptions.asar; if (platformSpecific != null) { result = platformSpecific; } if (result === false) { return null; } const buildMetadata = this.devMetadata.build; if (buildMetadata["asar-unpack"] != null) { log_1.warn("asar-unpack is deprecated, please set as asar.unpack"); } if (buildMetadata["asar-unpack-dir"] != null) { log_1.warn("asar-unpack-dir is deprecated, please set as asar.unpackDir"); } if (result == null || result === true) { result = { unpack: buildMetadata["asar-unpack"], unpackDir: buildMetadata["asar-unpack-dir"] }; } return Object.assign(result, { extraMetadata: this.options.extraMetadata }); } doCopyExtraFiles(patterns) { if (patterns == null || patterns.length === 0) { return bluebird_1.Promise.resolve(); } else { const promises = []; for (let i = 0; i < patterns.length; i++) { if (patterns[i].isEmpty()) { patterns[i].addPattern("**/*"); } promises.push(filter_1.copyFiltered(patterns[i].from, patterns[i].to, patterns[i].createFilter(), this.platform === metadata_1.Platform.WINDOWS)); } return bluebird_1.Promise.all(promises); } } getFileMatchers(name, defaultSrc, defaultDest, allowAdvancedMatching, fileMatchOptions, customBuildOptions) { let globalPatterns = this.devMetadata.build[name]; let platformSpecificPatterns = customBuildOptions[name]; const defaultMatcher = new fileMatcher_1.FileMatcher(defaultSrc, defaultDest, fileMatchOptions); const fileMatchers = []; function addPatterns(patterns) { if (patterns == null) { return; } else if (!Array.isArray(patterns)) { defaultMatcher.addPattern(patterns); return; } for (let i = 0; i < patterns.length; i++) { const pattern = patterns[i]; if (typeof pattern === "string") { defaultMatcher.addPattern(pattern); } else if (allowAdvancedMatching) { const from = pattern.from ? path.isAbsolute(pattern.from) ? pattern.from : path.join(defaultSrc, pattern.from) : defaultSrc; const to = pattern.to ? path.isAbsolute(pattern.to) ? pattern.to : path.join(defaultDest, pattern.to) : defaultDest; fileMatchers.push(new fileMatcher_1.FileMatcher(from, to, fileMatchOptions, pattern.filter)); } else { throw new Error(`Advanced file copying not supported for "${ name }"`); } } } addPatterns(globalPatterns); addPatterns(platformSpecificPatterns); if (!defaultMatcher.isEmpty()) { // Default matcher should be first in the array fileMatchers.unshift(defaultMatcher); } return fileMatchers.length ? fileMatchers : null; } getResourcesDir(appOutDir) { return this.platform === metadata_1.Platform.MAC ? this.getOSXResourcesDir(appOutDir) : path.join(appOutDir, "resources"); } getOSXResourcesDir(appOutDir) { return path.join(appOutDir, `${ this.appInfo.productFilename }.app`, "Contents", "Resources"); } checkFileInPackage(resourcesDir, file, messagePrefix, isAsar) { return __awaiter(this, void 0, void 0, function* () { const relativeFile = path.relative(this.info.appDir, path.resolve(this.info.appDir, file)); if (isAsar) { yield asarUtil_1.checkFileInArchive(path.join(resourcesDir, "app.asar"), relativeFile, messagePrefix); return; } const pathParsed = path.parse(file); // Even when packaging to asar is disabled, it does not imply that the main file can not be inside an .asar archive. // This may occur when the packaging is done manually before processing with electron-builder. if (pathParsed.dir.indexOf(".asar") !== -1) { // The path needs to be split to the part with an asar archive which acts like a directory and the part with // the path to main file itself. (e.g. path/arch.asar/dir/index.js -> path/arch.asar, dir/index.js) const pathSplit = pathParsed.dir.split(path.sep); let partWithAsarIndex = 0; pathSplit.some((pathPart, index) => { partWithAsarIndex = index; return pathPart.endsWith(".asar"); }); const asarPath = path.join.apply(path, pathSplit.slice(0, partWithAsarIndex + 1)); let mainPath = pathSplit.length > partWithAsarIndex + 1 ? path.join.apply(pathSplit.slice(partWithAsarIndex + 1)) : ""; mainPath += path.join(mainPath, pathParsed.base); yield asarUtil_1.checkFileInArchive(path.join(resourcesDir, "app", asarPath), mainPath, messagePrefix); } else { const outStat = yield util_1.statOrNull(path.join(resourcesDir, "app", relativeFile)); if (outStat == null) { throw new Error(`${ messagePrefix } "${ relativeFile }" does not exist. Seems like a wrong configuration.`); } else if (!outStat.isFile()) { throw new Error(`${ messagePrefix } "${ relativeFile }" is not a file. Seems like a wrong configuration.`); } } }); } sanityCheckPackage(appOutDir, isAsar) { return __awaiter(this, void 0, void 0, function* () { const outStat = yield util_1.statOrNull(appOutDir); if (outStat == null) { throw new Error(`Output directory "${ appOutDir }" does not exist. Seems like a wrong configuration.`); } else if (!outStat.isDirectory()) { throw new Error(`Output directory "${ appOutDir }" is not a directory. Seems like a wrong configuration.`); } const resourcesDir = this.getResourcesDir(appOutDir); yield this.checkFileInPackage(resourcesDir, this.appInfo.metadata.main || "index.js", "Application entry file", isAsar); yield this.checkFileInPackage(resourcesDir, "package.json", "Application", isAsar); }); } archiveApp(format, appOutDir, outFile) { return __awaiter(this, void 0, void 0, function* () { return archive_1.archiveApp(this.devMetadata.build.compression, format, outFile, this.platform === metadata_1.Platform.MAC ? path.join(appOutDir, `${ this.appInfo.productFilename }.app`) : appOutDir); }); } generateName(ext, arch, deployment) { let classifier = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; let c = null; if (arch === metadata_1.Arch.x64) { if (ext === "AppImage") { c = "x86_64"; } else if (ext === "deb") { c = "amd64"; } } else { c = metadata_1.Arch[arch]; } if (c == null) { c = classifier; } else if (classifier != null) { c += `-${ classifier }`; } return this.generateName2(ext, c, deployment); } generateName2(ext, classifier, deployment) { const dotExt = ext == null ? "" : `.${ ext }`; return `${ deployment ? this.appInfo.name : this.appInfo.productFilename }-${ this.appInfo.version }${ classifier == null ? "" : `-${ classifier }` }${ dotExt }`; } getDefaultIcon(ext) { return __awaiter(this, void 0, void 0, function* () { const resourceList = yield this.resourceList; const name = `icon.${ ext }`; if (resourceList.indexOf(name) !== -1) { return path.join(this.buildResourcesDir, name); } else { log_1.warn("Application icon is not set, default Electron icon will be used"); return null; } }); } getTempFile(suffix) { return this.info.tempDirManager.getTempFile(suffix); } getFileAssociations() { return util_1.asArray(this.devMetadata.build.fileAssociations).concat(util_1.asArray(this.platformSpecificBuildOptions.fileAssociations)); } getResource(custom, name) { return __awaiter(this, void 0, void 0, function* () { let result = custom; if (result === undefined) { const resourceList = yield this.resourceList; if (resourceList.indexOf(name) !== -1) { return path.join(this.buildResourcesDir, name); } } else { return path.resolve(this.projectDir, result); } return null; }); } } exports.PlatformPackager = PlatformPackager; function getArchSuffix(arch) { return arch === metadata_1.Arch.x64 ? "" : `-${ metadata_1.Arch[arch] }`; } exports.getArchSuffix = getArchSuffix; // fpm bug - rpm build --description is not escaped, well... decided to replace quite to smart quote // http://leancrew.com/all-this/2010/11/smart-quotes-in-javascript/ function smarten(s) { // opening singles s = s.replace(/(^|[-\u2014\s(\["])'/g, "$1\u2018"); // closing singles & apostrophes s = s.replace(/'/g, "\u2019"); // opening doubles s = s.replace(/(^|[-\u2014/\[(\u2018\s])"/g, "$1\u201c"); // closing doubles s = s.replace(/"/g, "\u201d"); return s; } exports.smarten = smarten; // remove leading dot function normalizeExt(ext) { return ext.startsWith(".") ? ext.substring(1) : ext; } exports.normalizeExt = normalizeExt; //# sourceMappingURL=platformPackager.js.map