| /* |
| 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 { ConfigParser, xmlHelpers, events, CordovaError } = require('cordova-common'); |
| const ManifestJsonParser = require('./ManifestJsonParser'); |
| const PackageJsonParser = require('./PackageJsonParser'); |
| const SettingJsonParser = require('./SettingJsonParser'); |
| |
| module.exports.prepare = function (cordovaProject, options) { |
| // First cleanup current config and merge project's one into own |
| const defaultConfigPath = path.join(this.locations.platformRootDir, 'cordova', 'defaults.xml'); |
| const ownConfigPath = this.locations.configXml; |
| const sourceCfg = cordovaProject.projectConfig; |
| |
| // If defaults.xml is present, overwrite platform config.xml with it. |
| // Otherwise save whatever is there as defaults so it can be |
| // restored or copy project config into platform if none exists. |
| if (fs.existsSync(defaultConfigPath)) { |
| this.events.emit('verbose', `Generating config.xml from defaults for platform "${this.platform}"`); |
| fs.copySync(defaultConfigPath, ownConfigPath); |
| } else if (fs.existsSync(ownConfigPath)) { |
| this.events.emit('verbose', `Generating defaults.xml from own config.xml for platform "${this.platform}"`); |
| fs.copySync(ownConfigPath, defaultConfigPath); |
| } else { |
| this.events.emit('verbose', `case 3 "${this.platform}"`); |
| fs.copySync(sourceCfg.path, ownConfigPath); |
| } |
| |
| // merge our configs |
| this.config = new ConfigParser(ownConfigPath); |
| xmlHelpers.mergeXml(sourceCfg.doc.getroot(), this.config.doc.getroot(), this.platform, true); |
| this.config.write(); |
| |
| // Update own www dir with project's www assets and plugins' assets and js-files |
| this.parser.update_www(cordovaProject, this.locations); |
| // Update icons |
| updateIcons(cordovaProject, this.locations); |
| // Update splash screens |
| updateSplashScreens(cordovaProject, this.config, this.locations); |
| |
| // Copy or Create manifest.json |
| const srcManifestPath = path.join(cordovaProject.locations.www, 'manifest.json'); |
| if (fs.existsSync(srcManifestPath)) { |
| // just blindly copy it to our output/www |
| // todo: validate it? ensure all properties we expect exist? |
| const manifestPath = path.join(this.locations.www, 'manifest.json'); |
| this.events.emit('verbose', `Copying ${srcManifestPath} => ${manifestPath}`); |
| fs.copySync(srcManifestPath, manifestPath); |
| } else { |
| this.events.emit('verbose', `Creating new manifest file in => ${this.path}`); |
| |
| (new ManifestJsonParser(this.locations.www)) |
| .configure(this.config) |
| .write(); |
| } |
| |
| const projectPackageJson = JSON.parse(fs.readFileSync(path.join(cordovaProject.root, 'package.json'), 'utf8')); |
| |
| (new PackageJsonParser(this.locations.www, cordovaProject.root)) |
| .configure(this.config, projectPackageJson) |
| .write(); |
| |
| const userElectronSettings = cordovaProject.projectConfig.getPlatformPreference('ElectronSettingsFilePath', 'electron'); |
| const userElectronSettingsPath = userElectronSettings && fs.existsSync(path.resolve(cordovaProject.root, userElectronSettings)) |
| ? path.resolve(cordovaProject.root, userElectronSettings) |
| : undefined; |
| |
| // update Electron settings in .json file |
| (new SettingJsonParser(this.locations.www)) |
| .configure(this.config, options.options, userElectronSettingsPath) |
| .write(); |
| |
| // update project according to config.xml changes. |
| return this.parser.update_project(this.config, options); |
| }; |
| |
| /** |
| * Update Electron Splash Screen image. |
| */ |
| function updateSplashScreens (cordovaProject, config, locations) { |
| const splashScreens = cordovaProject.projectConfig.getSplashScreens('electron'); |
| |
| // Skip if there are no splash screens defined in config.xml |
| if (!splashScreens.length) { |
| events.emit('verbose', 'This app does not have splash screens defined.'); |
| return; |
| } |
| |
| const splashScreen = prepareSplashScreens(splashScreens); |
| const resourceMap = createResourceMap(cordovaProject, locations, splashScreen); |
| |
| updatePathToSplashScreen(config, locations, resourceMap); |
| |
| events.emit('verbose', 'Updating splash screens'); |
| copyResources(cordovaProject.root, resourceMap); |
| } |
| |
| /** |
| * Get splashScreen image. Choose only one image, if the user provided multiple. |
| */ |
| function prepareSplashScreens (splashScreens) { |
| let splashScreen; |
| // choose one icon for a target |
| const chooseOne = (defaultSplash, splash) => { |
| events.emit('verbose', `Found extra splash screen image: ${defaultSplash.src} and ignoring in favor of ${splash.src}.`); |
| |
| defaultSplash = splash; |
| |
| return defaultSplash; |
| }; |
| |
| // iterate over remaining icon elements to find the icons for the app and installer |
| for (const image of splashScreens) { |
| image.extension = path.extname(image.src); |
| splashScreen = splashScreen ? chooseOne(splashScreen, image) : image; |
| } |
| |
| return { splashScreen }; |
| } |
| |
| /** |
| * Update path to Splash Screen in the config.xml |
| */ |
| function updatePathToSplashScreen (config, locations, resourceMap) { |
| const elementKeys = Object.keys(resourceMap[0]); |
| const splashScreenPath = resourceMap[0][elementKeys]; |
| const splash = config.doc.find('splash'); |
| const preferences = config.doc.findall('preference'); |
| |
| splash.attrib.src = path.relative(locations.www, splashScreenPath); |
| for (const preference of preferences) { |
| if (preference.attrib.name === 'SplashScreen') { |
| preference.attrib.value = splash.attrib.src; |
| } |
| } |
| config.write(); |
| } |
| |
| /** |
| * Update Electron App and Installer icons. |
| */ |
| function updateIcons (cordovaProject, locations) { |
| const icons = cordovaProject.projectConfig.getIcons('electron'); |
| |
| // Skip if there are no app defined icons in config.xml |
| if (!icons.length) { |
| events.emit('verbose', 'This app does not have icons defined'); |
| return; |
| } |
| |
| const filteredIcons = icons.filter(icon => checkIconsAttributes(icon)); |
| |
| if (!filteredIcons.length) { |
| throw new CordovaError('No icon match the required size. Please ensure that ".png" icon is at least 512x512 and has a src attribute.'); |
| } |
| |
| const chosenIcons = prepareIcons(filteredIcons); |
| const resourceMap = createResourceMap(cordovaProject, locations, chosenIcons); |
| |
| events.emit('verbose', 'Updating icons'); |
| copyResources(cordovaProject.root, resourceMap); |
| } |
| |
| /** |
| * Check if all required attributes are set. |
| */ |
| function checkIconsAttributes (icon) { |
| if (((icon.height && icon.width) >= 512 || (icon.height && icon.width) === undefined) && icon.src) return true; |
| |
| events.emit('info', `The following${icon.target ? ` ${icon.target}` : ''} icon with a size of width=${icon.width} height=${icon.height} does not meet the requirements and will be ignored.`); |
| return false; |
| } |
| |
| /** |
| * Find and select icons for the app and installer. |
| * Also, set high resolution icons, if provided by a user. |
| */ |
| function prepareIcons (icons) { |
| let customIcon; |
| let appIcon; |
| let installerIcon; |
| |
| // choose one icon for a target |
| const chooseOne = (defaultIcon, icon) => { |
| events.emit('verbose', `Found extra icon for target ${icon.target}: ${defaultIcon.src} and ignoring in favor of ${icon.src}.`); |
| |
| defaultIcon = icon; |
| |
| return defaultIcon; |
| }; |
| |
| // find if there is high resolution images that has DPI suffix |
| const highResAndRemainingIcons = findHighResIcons(icons); |
| const highResIcons = highResAndRemainingIcons.highResIcons; |
| const remainingIcons = highResAndRemainingIcons.remainingIcons; |
| |
| // iterate over remaining icon elements to find the icons for the app and installer |
| for (const icon of remainingIcons) { |
| const size = icon.width || icon.height; |
| icon.extension = path.extname(icon.src); |
| |
| switch (icon.target) { |
| case 'app': |
| appIcon = appIcon ? chooseOne(appIcon, icon) : icon; |
| break; |
| case 'installer': |
| installerIcon = installerIcon ? chooseOne(installerIcon, icon) : icon; |
| break; |
| case undefined: |
| if ((size >= 512 || size === undefined) && !Object.keys(highResIcons).length) { |
| customIcon = customIcon ? chooseOne(customIcon, icon) : icon; |
| } |
| break; |
| } |
| } |
| |
| return { customIcon, appIcon, installerIcon, highResIcons }; |
| } |
| |
| /** |
| * Find and high resolution icons and return remaining icons, |
| * unless an icon has a specified target. |
| */ |
| function findHighResIcons (icons) { |
| // find icons that are not in the highResIcons, unless they have a target set |
| const findRemainingIcons = (icons, highResIcons) => icons.filter( |
| (icon) => (icon.target || (!icon.target && !highResIcons.includes(icon))) |
| ? Object.assign(icon) |
| : false |
| ); |
| |
| const highResIcons = icons.filter(icon => { |
| if (icon.src.includes('@')) { |
| const extension = path.extname(icon.src); |
| const suffix = icon.src.split('@').pop().slice(0, -extension.length); |
| |
| return Object.assign(icon, { suffix, extension }); |
| } |
| }); |
| |
| let remainingIcons = findRemainingIcons(icons, highResIcons); |
| // set normal image that has standard resolution |
| const has1x = highResIcons.find(obj => obj.suffix === '1x'); |
| if (!has1x && Object.keys(highResIcons).length) { |
| const highResIcon = highResIcons[Object.keys(highResIcons)[0]]; |
| let baseIcon = remainingIcons.find(obj => obj.src === highResIcon.src.split('@')[0] + highResIcon.extension); |
| |
| if (!baseIcon) { |
| throw new CordovaError('Base icon for high resolution images was not found.'); |
| } |
| |
| const extension = path.extname(baseIcon.src); |
| const suffix = '1x'; |
| |
| baseIcon = Object.assign(baseIcon, { suffix, extension }); |
| |
| highResIcons.push(baseIcon); |
| |
| remainingIcons = findRemainingIcons(icons, highResIcons); |
| } |
| |
| return { highResIcons, remainingIcons }; |
| } |
| |
| /** |
| * Map resources to the appropriate target directory and name. |
| */ |
| function createResourceMap (cordovaProject, locations, resources) { |
| const resourceMap = []; |
| |
| for (const key in resources) { |
| const resource = resources[key]; |
| |
| if (!resource) { |
| continue; |
| } |
| |
| let targetPath; |
| switch (key) { |
| case 'customIcon': |
| // Copy icon for the App |
| targetPath = path.join(locations.www, 'img', `app${resource.extension}`); |
| resourceMap.push(mapResources(cordovaProject.root, resource.src, targetPath)); |
| // Copy icon for the Installer |
| targetPath = path.join(locations.buildRes, `installer${resource.extension}`); |
| resourceMap.push(mapResources(cordovaProject.root, resource.src, targetPath)); |
| break; |
| case 'appIcon': |
| targetPath = path.join(locations.www, 'img', `app${resource.extension}`); |
| resourceMap.push(mapResources(cordovaProject.root, resource.src, targetPath)); |
| break; |
| case 'installerIcon': |
| targetPath = path.join(locations.buildRes, `installer${resource.extension}`); |
| resourceMap.push(mapResources(cordovaProject.root, resource.src, targetPath)); |
| break; |
| case 'highResIcons': |
| for (const key in resource) { |
| const highResIcon = resource[key]; |
| targetPath = path.join(locations.www, 'img', 'icon'); |
| targetPath += highResIcon.suffix === '1x' ? highResIcon.extension : `@${highResIcon.suffix}${highResIcon.extension}`; |
| resourceMap.push(mapResources(cordovaProject.root, highResIcon.src, targetPath)); |
| } |
| break; |
| case 'splashScreen': |
| targetPath = path.join(locations.www, '.cdv', `splashScreen${resource.extension}`); |
| resourceMap.push(mapResources(cordovaProject.root, resource.src, targetPath)); |
| break; |
| } |
| } |
| return resourceMap; |
| } |
| |
| /** |
| * Get a map containing resources of a specified name (or directory) to the target directory. |
| */ |
| function mapResources (rootDir, sourcePath, targetPath) { |
| return fs.existsSync(path.join(rootDir, sourcePath)) |
| ? { [sourcePath]: targetPath } |
| : {}; |
| } |
| |
| /** |
| * Copy resources to the target destination according to the resource map. |
| */ |
| function copyResources (rootDir, resourceMap) { |
| resourceMap.forEach(element => { |
| const elementKeys = Object.keys(element); |
| |
| if (elementKeys.length) { |
| const value = elementKeys.map((e) => element[e])[0]; |
| fs.copySync(path.join(rootDir, elementKeys[0]), value); |
| } |
| }); |
| } |