| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| */ |
| |
| const fs = require('fs-extra'); |
| const path = require('path'); |
| const events = require('cordova-common').events; |
| const { deepMerge } = require('./util'); |
| |
| const PLATFORM_MAPPING = { |
| linux: 'linux', |
| mac: 'darwin', |
| windows: 'win32', |
| win: 'win32' |
| }; |
| |
| class ElectronBuilder { |
| constructor (buildOptions, api) { |
| this.api = api; |
| this.isDevelopment = buildOptions.debug; |
| this.buildConfig = buildOptions && buildOptions.buildConfig && fs.existsSync(buildOptions.buildConfig) ? require(buildOptions.buildConfig) : false; |
| } |
| |
| configure () { |
| this.buildSettings = this.configureUserBuildSettings() |
| .configureBuildSettings(); |
| |
| // Replace the templated placeholders with the project defined settings into the buildSettings. |
| this.injectProjectConfigToBuildSettings(); |
| |
| return this; |
| } |
| |
| configureUserBuildSettings () { |
| if (this.buildConfig && this.buildConfig.electron) { |
| let userBuildSettings = {}; |
| |
| for (const platform in this.buildConfig.electron) { |
| if (platform !== 'mac' && platform !== 'linux' && platform !== 'windows') continue; |
| |
| const platformConfigs = this.buildConfig.electron[platform]; |
| /** |
| * In this scenario, the user has added a valid platform to build for but has not provided any custom build configurations. |
| * This will fetch thew platform's default build configuration. |
| * Each platform's default build configurations are located in the "./build/" directory. |
| */ |
| if (Object.keys(platformConfigs).length === 0) { |
| userBuildSettings = deepMerge(userBuildSettings, this.fetchPlatformDefaults(PLATFORM_MAPPING[platform])); |
| continue; |
| } |
| |
| /** |
| * Validate that the platform configuration properties provided are valid. |
| * Any invalid property will be warned and iggnored. |
| * If there is there are no valid properties |
| */ |
| if (!this.__validateUserPlatformBuildSettings(platformConfigs)) { |
| throw Error(`The platform "${platform}" contains an invalid property. Valid properties are: package, arch, signing`); |
| } |
| |
| // Electron uses "win" as it's key, not "windows", so we will update here. We use windows in our settings for clarity. |
| this.__formatAppendUserSettings( |
| (platform === 'windows' ? 'win' : platform), |
| platformConfigs, |
| userBuildSettings |
| ); |
| |
| this.___overridablePerPlatformOptions( |
| (platform === 'windows' ? 'win' : platform), |
| platformConfigs, |
| userBuildSettings |
| ); |
| } |
| |
| this.userBuildSettings = userBuildSettings; |
| } |
| |
| return this; |
| } |
| |
| __formatAppendUserSettings (platform, platformConfigs, userBuildSettings) { |
| // Add the platform at the root level to trigger build. |
| userBuildSettings[platform] = []; |
| |
| // Add the config placeholder for build configurations (only add once if missing) |
| if (!userBuildSettings.config) userBuildSettings.config = {}; |
| |
| userBuildSettings.config[platform] = { |
| target: [] |
| }; |
| |
| // Apply custom app installer icon when the user is using a custom build configuration |
| // eslint-disable-next-line no-template-curly-in-string |
| userBuildSettings.config[platform].icon = '${APP_INSTALLER_ICON}'; |
| |
| // Only macOS has a build type distinction. (development or distribution) |
| // eslint-disable-next-line no-template-curly-in-string |
| if (platform === 'mac') userBuildSettings.config[platform].type = '${BUILD_TYPE}'; |
| |
| if (platformConfigs.package) { |
| platformConfigs.package.forEach((target) => { |
| if (target === 'mas') { |
| userBuildSettings.config.mas = {}; |
| } |
| |
| if (typeof target === 'object' && Object.keys(target).length !== 0) { |
| const targetKey = Object.keys(target)[0]; |
| userBuildSettings.config[targetKey] = target[targetKey]; |
| target = targetKey; |
| } |
| |
| /** |
| * The target of arch values are not validated as electron-builder will handle this. |
| * If the arch value is missing, 64-bit will be defaulted. |
| */ |
| userBuildSettings.config[platform].target.push({ |
| target, |
| arch: platformConfigs.arch || ['x64'] |
| }); |
| }); |
| } else { |
| /** |
| * We will fetch and use the defaults when a package type is not identified. |
| * If the arch value is identified, we will update each default package with the correct arch. |
| */ |
| const platformDefaults = this.fetchPlatformDefaults(PLATFORM_MAPPING[platform]); |
| const platformTargetPackages = platformDefaults.config[platform].target; |
| |
| if (platformConfigs.arch) { |
| platformTargetPackages.forEach((pkg, i) => { |
| platformTargetPackages[i].arch = platformConfigs.arch; |
| }); |
| } |
| |
| userBuildSettings.config[platform].target = platformTargetPackages; |
| } |
| |
| if (platformConfigs.signing) { |
| this.__appendUserSigning(platform, platformConfigs.signing, userBuildSettings); |
| } |
| } |
| |
| ___overridablePerPlatformOptions (platform, platformConfigs, userBuildSettings) { |
| const PLATFORM_TOP_LEVEL_OPTIONS = { |
| allPlatforms: [ |
| 'appId', |
| 'artifactName', |
| 'asar', |
| 'compression', |
| 'detectUpdateChannel', |
| 'electronUpdaterCompatibility', |
| 'extraFiles', |
| 'extraResources', |
| 'fileAssociations', |
| 'files', |
| 'forceCodeSigning', |
| 'generateUpdatesFilesForAllChannels', |
| 'icon', |
| 'publish' |
| ], |
| linux: [ |
| 'category', |
| 'description', |
| 'desktop', |
| 'executableName', |
| 'maintainer', |
| 'mimeTypes', |
| 'synopsis', |
| 'vendor' |
| ], |
| mac: [ |
| 'binaries', |
| 'bundleShortVersion', |
| 'bundleVersion', |
| 'category', |
| 'darkModeSupport', |
| 'electronLanguages', |
| 'extendInfo', |
| 'extraDistFiles', |
| 'helperBundleId', |
| 'minimumSystemVersion' |
| ], |
| win: [ |
| 'legalTrademarks', |
| 'publisherName', |
| 'requestedExecutionLevel', |
| 'rfc3161TimeStampServer', |
| 'signAndEditExecutable', |
| 'signDlls', |
| 'timeStampServer', |
| 'verifyUpdateCodeSignature' |
| ] |
| }; |
| |
| for (const option in platformConfigs) { |
| if ( |
| PLATFORM_TOP_LEVEL_OPTIONS.allPlatforms.includes(option) || |
| PLATFORM_TOP_LEVEL_OPTIONS[platform].includes(option) |
| ) { |
| userBuildSettings.config[platform][option] = platformConfigs[option]; |
| } |
| } |
| } |
| |
| __appendUserSigning (platform, signingConfigs, userBuildSettings) { |
| if (platform === 'linux') { |
| events.emit('warn', 'The provided signing information for the Linux platform is ignored. Linux does not support signing.'); |
| return this; |
| } |
| |
| const config = this.isDevelopment ? signingConfigs.debug : signingConfigs.release; |
| if (platform === 'mac' && config) { |
| this.__appendMacUserSigning(config, userBuildSettings.config.mac); |
| } |
| |
| const masConfig = this.isDevelopment ? null : (signingConfigs.store || null); |
| if (platform === 'mac' && masConfig) { |
| // Requirements is not available for mas. |
| if (masConfig.requirements) delete masConfig.requirements; |
| this.__appendMacUserSigning(masConfig, userBuildSettings.config.mas); |
| } |
| |
| if (platform === 'win' && config) { |
| this.__appendWindowsUserSigning(config, userBuildSettings.config.win); |
| } |
| } |
| |
| __validateUserPlatformBuildSettings (platformConfigs) { |
| return !!( |
| platformConfigs.package || |
| platformConfigs.arch || |
| platformConfigs.signing |
| ); |
| } |
| |
| __appendMacUserSigning (config, buildConfigs) { |
| if (config.identity || process.env.CSC_LINK || process.env.CSC_NAME) buildConfigs.identity = config.identity || process.env.CSC_LINK || process.env.CSC_NAME; |
| |
| const entitlements = config.entitlements; |
| if (entitlements && fs.existsSync(entitlements)) { |
| buildConfigs.entitlements = entitlements; |
| } else if (entitlements) { |
| events.emit('warn', `The provided entitlements file does not exist in the given path => ${entitlements}`); |
| } |
| |
| const entitlementsInherit = config.entitlementsInherit; |
| if (entitlementsInherit && fs.existsSync(entitlementsInherit)) { |
| buildConfigs.entitlementsInherit = entitlementsInherit; |
| } else if (entitlementsInherit) { |
| events.emit('warn', `The provided entitlements inherit file does not exist in the given path => ${entitlementsInherit}`); |
| } |
| |
| const requirements = config.requirements; |
| if (requirements && fs.existsSync(requirements)) { |
| buildConfigs.requirements = requirements; |
| } else if (requirements) { |
| events.emit('warn', `The provided requirements file does not exist in the given path => ${requirements}`); |
| } |
| |
| const provisioningProfile = config.provisioningProfile; |
| if (provisioningProfile && fs.existsSync(provisioningProfile)) { |
| buildConfigs.provisioningProfile = provisioningProfile; |
| } else if (provisioningProfile) { |
| events.emit('warn', `The provided provisioning profile does not exist in the given path => ${provisioningProfile}`); |
| } |
| } |
| |
| __appendWindowsUserSigning (config, buildConfigs) { |
| const certificateFile = config.certificateFile; |
| if (certificateFile && fs.existsSync(certificateFile)) { |
| buildConfigs.certificateFile = certificateFile; |
| |
| if (config.certificatePassword || process.env.CSC_KEY_PASSWORD) buildConfigs.certificatePassword = config.certificatePassword || process.env.CSC_KEY_PASSWORD; |
| } else if (certificateFile) { |
| events.emit('warn', `The provided certificate file does not exist in the given path => ${certificateFile}`); |
| } |
| |
| if (config.certificateSubjectName) buildConfigs.certificateSubjectName = config.certificateSubjectName; |
| if (config.certificateSha1) buildConfigs.certificateSha1 = config.certificateSha1; |
| if (config.signingHashAlgorithms) buildConfigs.signingHashAlgorithms = config.signingHashAlgorithms; |
| |
| const additionalCertificateFile = config.additionalCertificateFile; |
| if (additionalCertificateFile && fs.existsSync(additionalCertificateFile)) { |
| buildConfigs.additionalCertificateFile = additionalCertificateFile; |
| } else if (additionalCertificateFile) { |
| events.emit('warn', `The provided addition certificate file does not exist in the given path => ${additionalCertificateFile}`); |
| } |
| } |
| |
| configureBuildSettings () { |
| const baseConfig = require(path.resolve(__dirname, './build/base.json')); |
| const platformConfig = this.userBuildSettings || this.fetchPlatformDefaults(process.platform); |
| |
| return deepMerge(baseConfig, platformConfig); |
| } |
| |
| injectProjectConfigToBuildSettings () { |
| // const isDevelopment = false; |
| const packageJson = require(path.join(this.api.locations.www, 'package.json')); |
| const userConfig = { |
| APP_ID: packageJson.name, |
| APP_TITLE: packageJson.displayName, |
| APP_INSTALLER_ICON: 'installer.png', |
| APP_BUILD_DIR: this.api.locations.build, |
| APP_BUILD_RES_DIR: this.api.locations.buildRes, |
| APP_WWW_DIR: this.api.locations.www, |
| BUILD_TYPE: this.isDevelopment ? 'development' : 'distribution' |
| }; |
| |
| // convert to string for string replacement |
| let buildSettingsString = JSON.stringify(this.buildSettings); |
| |
| Object.keys(userConfig).forEach((key) => { |
| const regex = new RegExp(`\\$\\{${key}\\}`, 'g'); |
| const value = userConfig[key].replace(/\\/g, '\\\\'); |
| buildSettingsString = buildSettingsString.replace(regex, value); |
| }); |
| |
| // update build settings with formated data |
| this.buildSettings = JSON.parse(buildSettingsString); |
| |
| return this; |
| } |
| |
| fetchPlatformDefaults (platform) { |
| const platformFile = path.resolve(__dirname, `./build/${platform}.json`); |
| |
| if (!fs.existsSync(platformFile)) { |
| throw Error(`Your platform "${platform}" is not supported as a default target platform for Electron.`); |
| } |
| |
| return require(platformFile); |
| } |
| |
| build () { |
| return require('electron-builder').build(this.buildSettings); |
| } |
| } |
| |
| module.exports.run = (buildOptions, api) => require('./check_reqs') |
| .run() |
| .then(() => (new ElectronBuilder(buildOptions, api)) |
| .configure() |
| .build() |
| ) |
| .catch(error => { |
| throw error; |
| }); |