1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153 |
- /**
- 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.
- */
- 'use strict';
- var Q = require('q');
- var fs = require('fs');
- var path = require('path');
- var shell = require('shelljs');
- var xcode = require('xcode');
- var unorm = require('unorm');
- var plist = require('plist');
- var URL = require('url');
- var events = require('cordova-common').events;
- var xmlHelpers = require('cordova-common').xmlHelpers;
- var ConfigParser = require('cordova-common').ConfigParser;
- var CordovaError = require('cordova-common').CordovaError;
- var PlatformJson = require('cordova-common').PlatformJson;
- var PlatformMunger = require('cordova-common').ConfigChanges.PlatformMunger;
- var PluginInfoProvider = require('cordova-common').PluginInfoProvider;
- var FileUpdater = require('cordova-common').FileUpdater;
- var projectFile = require('./projectFile');
- // launch storyboard and related constants
- var LAUNCHIMAGE_BUILD_SETTING = 'ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME';
- var LAUNCHIMAGE_BUILD_SETTING_VALUE = 'LaunchImage';
- var UI_LAUNCH_STORYBOARD_NAME = 'UILaunchStoryboardName';
- var CDV_LAUNCH_STORYBOARD_NAME = 'CDVLaunchScreen';
- var IMAGESET_COMPACT_SIZE_CLASS = 'compact';
- var CDV_ANY_SIZE_CLASS = 'any';
- module.exports.prepare = function (cordovaProject, options) {
- var self = this;
- var platformJson = PlatformJson.load(this.locations.root, 'ios');
- var munger = new PlatformMunger('ios', this.locations.root, platformJson, new PluginInfoProvider());
- this._config = updateConfigFile(cordovaProject.projectConfig, munger, this.locations);
- // Update own www dir with project's www assets and plugins' assets and js-files
- return Q.when(updateWww(cordovaProject, this.locations))
- .then(function () {
- // update project according to config.xml changes.
- return updateProject(self._config, self.locations);
- })
- .then(function () {
- updateIcons(cordovaProject, self.locations);
- updateSplashScreens(cordovaProject, self.locations);
- updateLaunchStoryboardImages(cordovaProject, self.locations);
- updateFileResources(cordovaProject, self.locations);
- })
- .then(function () {
- events.emit('verbose', 'Prepared iOS project successfully');
- });
- };
- module.exports.clean = function (options) {
- // A cordovaProject isn't passed into the clean() function, because it might have
- // been called from the platform shell script rather than the CLI. Check for the
- // noPrepare option passed in by the non-CLI clean script. If that's present, or if
- // there's no config.xml found at the project root, then don't clean prepared files.
- var projectRoot = path.resolve(this.root, '../..');
- var projectConfigFile = path.join(projectRoot, 'config.xml');
- if ((options && options.noPrepare) || !fs.existsSync(projectConfigFile) ||
- !fs.existsSync(this.locations.configXml)) {
- return Q();
- }
- var projectConfig = new ConfigParser(this.locations.configXml);
- var self = this;
- return Q().then(function () {
- cleanWww(projectRoot, self.locations);
- cleanIcons(projectRoot, projectConfig, self.locations);
- cleanSplashScreens(projectRoot, projectConfig, self.locations);
- cleanLaunchStoryboardImages(projectRoot, projectConfig, self.locations);
- cleanFileResources(projectRoot, projectConfig, self.locations);
- });
- };
- /**
- * Updates config files in project based on app's config.xml and config munge,
- * generated by plugins.
- *
- * @param {ConfigParser} sourceConfig A project's configuration that will
- * be merged into platform's config.xml
- * @param {ConfigChanges} configMunger An initialized ConfigChanges instance
- * for this platform.
- * @param {Object} locations A map of locations for this platform
- *
- * @return {ConfigParser} An instance of ConfigParser, that
- * represents current project's configuration. When returned, the
- * configuration is already dumped to appropriate config.xml file.
- */
- function updateConfigFile (sourceConfig, configMunger, locations) {
- events.emit('verbose', 'Generating platform-specific config.xml from defaults for iOS at ' + locations.configXml);
- // First cleanup current config and merge project's one into own
- // Overwrite platform config.xml with defaults.xml.
- shell.cp('-f', locations.defaultConfigXml, locations.configXml);
- // Then apply config changes from global munge to all config files
- // in project (including project's config)
- configMunger.reapply_global_munge().save_all();
- events.emit('verbose', 'Merging project\'s config.xml into platform-specific iOS config.xml');
- // Merge changes from app's config.xml into platform's one
- var config = new ConfigParser(locations.configXml);
- xmlHelpers.mergeXml(sourceConfig.doc.getroot(),
- config.doc.getroot(), 'ios', /* clobber= */true);
- config.write();
- return config;
- }
- /**
- * Logs all file operations via the verbose event stream, indented.
- */
- function logFileOp (message) {
- events.emit('verbose', ' ' + message);
- }
- /**
- * Updates platform 'www' directory by replacing it with contents of
- * 'platform_www' and app www. Also copies project's overrides' folder into
- * the platform 'www' folder
- *
- * @param {Object} cordovaProject An object which describes cordova project.
- * @param {boolean} destinations An object that contains destinations
- * paths for www files.
- */
- function updateWww (cordovaProject, destinations) {
- var sourceDirs = [
- path.relative(cordovaProject.root, cordovaProject.locations.www),
- path.relative(cordovaProject.root, destinations.platformWww)
- ];
- // If project contains 'merges' for our platform, use them as another overrides
- var merges_path = path.join(cordovaProject.root, 'merges', 'ios');
- if (fs.existsSync(merges_path)) {
- events.emit('verbose', 'Found "merges/ios" folder. Copying its contents into the iOS project.');
- sourceDirs.push(path.join('merges', 'ios'));
- }
- var targetDir = path.relative(cordovaProject.root, destinations.www);
- events.emit(
- 'verbose', 'Merging and updating files from [' + sourceDirs.join(', ') + '] to ' + targetDir);
- FileUpdater.mergeAndUpdateDir(
- sourceDirs, targetDir, { rootDir: cordovaProject.root }, logFileOp);
- }
- /**
- * Cleans all files from the platform 'www' directory.
- */
- function cleanWww (projectRoot, locations) {
- var targetDir = path.relative(projectRoot, locations.www);
- events.emit('verbose', 'Cleaning ' + targetDir);
- // No source paths are specified, so mergeAndUpdateDir() will clear the target directory.
- FileUpdater.mergeAndUpdateDir(
- [], targetDir, { rootDir: projectRoot, all: true }, logFileOp);
- }
- /**
- * Updates project structure and AndroidManifest according to project's configuration.
- *
- * @param {ConfigParser} platformConfig A project's configuration that will
- * be used to update project
- * @param {Object} locations A map of locations for this platform (In/Out)
- */
- function updateProject (platformConfig, locations) {
- // CB-6992 it is necessary to normalize characters
- // because node and shell scripts handles unicode symbols differently
- // We need to normalize the name to NFD form since iOS uses NFD unicode form
- var name = unorm.nfd(platformConfig.name());
- var pkg = platformConfig.getAttribute('ios-CFBundleIdentifier') || platformConfig.packageName();
- var version = platformConfig.version();
- var displayName = platformConfig.shortName && platformConfig.shortName();
- var originalName = path.basename(locations.xcodeCordovaProj);
- // Update package id (bundle id)
- var plistFile = path.join(locations.xcodeCordovaProj, originalName + '-Info.plist');
- var infoPlist = plist.parse(fs.readFileSync(plistFile, 'utf8'));
- infoPlist['CFBundleIdentifier'] = pkg;
- // Update version (bundle version)
- infoPlist['CFBundleShortVersionString'] = version;
- var CFBundleVersion = platformConfig.getAttribute('ios-CFBundleVersion') || default_CFBundleVersion(version);
- infoPlist['CFBundleVersion'] = CFBundleVersion;
- if (platformConfig.getAttribute('defaultlocale')) {
- infoPlist['CFBundleDevelopmentRegion'] = platformConfig.getAttribute('defaultlocale');
- }
- if (displayName) {
- infoPlist['CFBundleDisplayName'] = displayName;
- }
- // replace Info.plist ATS entries according to <access> and <allow-navigation> config.xml entries
- var ats = writeATSEntries(platformConfig);
- if (Object.keys(ats).length > 0) {
- infoPlist['NSAppTransportSecurity'] = ats;
- } else {
- delete infoPlist['NSAppTransportSecurity'];
- }
- handleOrientationSettings(platformConfig, infoPlist);
- updateProjectPlistForLaunchStoryboard(platformConfig, infoPlist);
- var info_contents = plist.build(infoPlist);
- info_contents = info_contents.replace(/<string>[\s\r\n]*<\/string>/g, '<string></string>');
- fs.writeFileSync(plistFile, info_contents, 'utf-8');
- events.emit('verbose', 'Wrote out iOS Bundle Identifier "' + pkg + '" and iOS Bundle Version "' + version + '" to ' + plistFile);
- return handleBuildSettings(platformConfig, locations, infoPlist).then(function () {
- if (name === originalName) {
- events.emit('verbose', 'iOS Product Name has not changed (still "' + originalName + '")');
- return Q();
- } else { // CB-11712 <name> was changed, we don't support it'
- var errorString =
- 'The product name change (<name> tag) in config.xml is not supported dynamically.\n' +
- 'To change your product name, you have to remove, then add your ios platform again.\n' +
- 'Make sure you save your plugins beforehand using `cordova plugin save`.\n' +
- '\tcordova plugin save\n' +
- '\tcordova platform rm ios\n' +
- '\tcordova platform add ios\n'
- ;
- return Q.reject(new CordovaError(errorString));
- }
- });
- }
- function handleOrientationSettings (platformConfig, infoPlist) {
- switch (getOrientationValue(platformConfig)) {
- case 'portrait':
- infoPlist['UIInterfaceOrientation'] = [ 'UIInterfaceOrientationPortrait' ];
- infoPlist['UISupportedInterfaceOrientations'] = [ 'UIInterfaceOrientationPortrait', 'UIInterfaceOrientationPortraitUpsideDown' ];
- infoPlist['UISupportedInterfaceOrientations~ipad'] = [ 'UIInterfaceOrientationPortrait', 'UIInterfaceOrientationPortraitUpsideDown' ];
- break;
- case 'landscape':
- infoPlist['UIInterfaceOrientation'] = [ 'UIInterfaceOrientationLandscapeLeft' ];
- infoPlist['UISupportedInterfaceOrientations'] = [ 'UIInterfaceOrientationLandscapeLeft', 'UIInterfaceOrientationLandscapeRight' ];
- infoPlist['UISupportedInterfaceOrientations~ipad'] = [ 'UIInterfaceOrientationLandscapeLeft', 'UIInterfaceOrientationLandscapeRight' ];
- break;
- case 'all':
- infoPlist['UIInterfaceOrientation'] = [ 'UIInterfaceOrientationPortrait' ];
- infoPlist['UISupportedInterfaceOrientations'] = [ 'UIInterfaceOrientationPortrait', 'UIInterfaceOrientationPortraitUpsideDown', 'UIInterfaceOrientationLandscapeLeft', 'UIInterfaceOrientationLandscapeRight' ];
- infoPlist['UISupportedInterfaceOrientations~ipad'] = [ 'UIInterfaceOrientationPortrait', 'UIInterfaceOrientationPortraitUpsideDown', 'UIInterfaceOrientationLandscapeLeft', 'UIInterfaceOrientationLandscapeRight' ];
- break;
- case 'default':
- infoPlist['UISupportedInterfaceOrientations'] = [ 'UIInterfaceOrientationPortrait', 'UIInterfaceOrientationLandscapeLeft', 'UIInterfaceOrientationLandscapeRight' ];
- infoPlist['UISupportedInterfaceOrientations~ipad'] = [ 'UIInterfaceOrientationPortrait', 'UIInterfaceOrientationPortraitUpsideDown', 'UIInterfaceOrientationLandscapeLeft', 'UIInterfaceOrientationLandscapeRight' ];
- delete infoPlist['UIInterfaceOrientation'];
- }
- }
- function handleBuildSettings (platformConfig, locations, infoPlist) {
- var targetDevice = parseTargetDevicePreference(platformConfig.getPreference('target-device', 'ios'));
- var deploymentTarget = platformConfig.getPreference('deployment-target', 'ios');
- var needUpdatedBuildSettingsForLaunchStoryboard = checkIfBuildSettingsNeedUpdatedForLaunchStoryboard(platformConfig, infoPlist);
- // no build settings provided and we don't need to update build settings for launch storyboards,
- // then we don't need to parse and update .pbxproj file
- if (!targetDevice && !deploymentTarget && !needUpdatedBuildSettingsForLaunchStoryboard) {
- return Q();
- }
- var proj = new xcode.project(locations.pbxproj); /* eslint new-cap : 0 */
- try {
- proj.parseSync();
- } catch (err) {
- return Q.reject(new CordovaError('Could not parse project.pbxproj: ' + err));
- }
- if (targetDevice) {
- events.emit('verbose', 'Set TARGETED_DEVICE_FAMILY to ' + targetDevice + '.');
- proj.updateBuildProperty('TARGETED_DEVICE_FAMILY', targetDevice);
- }
- if (deploymentTarget) {
- events.emit('verbose', 'Set IPHONEOS_DEPLOYMENT_TARGET to "' + deploymentTarget + '".');
- proj.updateBuildProperty('IPHONEOS_DEPLOYMENT_TARGET', deploymentTarget);
- }
- updateBuildSettingsForLaunchStoryboard(proj, platformConfig, infoPlist);
- fs.writeFileSync(locations.pbxproj, proj.writeSync(), 'utf-8');
- return Q();
- }
- function mapIconResources (icons, iconsDir) {
- // See https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/IconMatrix.html
- // for launch images sizes reference.
- var platformIcons = [
- {dest: 'icon-20.png', width: 20, height: 20},
- {dest: 'icon-20@2x.png', width: 40, height: 40},
- {dest: 'icon-20@3x.png', width: 60, height: 60},
- {dest: 'icon-40.png', width: 40, height: 40},
- {dest: 'icon-40@2x.png', width: 80, height: 80},
- {dest: 'icon-50.png', width: 50, height: 50},
- {dest: 'icon-50@2x.png', width: 100, height: 100},
- {dest: 'icon-60@2x.png', width: 120, height: 120},
- {dest: 'icon-60@3x.png', width: 180, height: 180},
- {dest: 'icon-72.png', width: 72, height: 72},
- {dest: 'icon-72@2x.png', width: 144, height: 144},
- {dest: 'icon-76.png', width: 76, height: 76},
- {dest: 'icon-76@2x.png', width: 152, height: 152},
- {dest: 'icon-83.5@2x.png', width: 167, height: 167},
- {dest: 'icon-1024.png', width: 1024, height: 1024},
- {dest: 'icon-small.png', width: 29, height: 29},
- {dest: 'icon-small@2x.png', width: 58, height: 58},
- {dest: 'icon-small@3x.png', width: 87, height: 87},
- {dest: 'icon.png', width: 57, height: 57},
- {dest: 'icon@2x.png', width: 114, height: 114},
- {dest: 'AppIcon24x24@2x.png', width: 48, height: 48},
- {dest: 'AppIcon27.5x27.5@2x.png', width: 55, height: 55},
- {dest: 'AppIcon29x29@2x.png', width: 58, height: 58},
- {dest: 'AppIcon29x29@3x.png', width: 87, height: 87},
- {dest: 'AppIcon40x40@2x.png', width: 80, height: 80},
- {dest: 'AppIcon44x44@2x.png', width: 88, height: 88},
- {dest: 'AppIcon86x86@2x.png', width: 172, height: 172},
- {dest: 'AppIcon98x98@2x.png', width: 196, height: 196}
- ];
- var pathMap = {};
- platformIcons.forEach(function (item) {
- var icon = icons.getBySize(item.width, item.height) || icons.getDefault();
- if (icon) {
- var target = path.join(iconsDir, item.dest);
- pathMap[target] = icon.src;
- }
- });
- return pathMap;
- }
- function getIconsDir (projectRoot, platformProjDir) {
- var iconsDir;
- var xcassetsExists = folderExists(path.join(projectRoot, platformProjDir, 'Images.xcassets/'));
- if (xcassetsExists) {
- iconsDir = path.join(platformProjDir, 'Images.xcassets/AppIcon.appiconset/');
- } else {
- iconsDir = path.join(platformProjDir, 'Resources/icons/');
- }
- return iconsDir;
- }
- function updateIcons (cordovaProject, locations) {
- var icons = cordovaProject.projectConfig.getIcons('ios');
- if (icons.length === 0) {
- events.emit('verbose', 'This app does not have icons defined');
- return;
- }
- var platformProjDir = path.relative(cordovaProject.root, locations.xcodeCordovaProj);
- var iconsDir = getIconsDir(cordovaProject.root, platformProjDir);
- var resourceMap = mapIconResources(icons, iconsDir);
- events.emit('verbose', 'Updating icons at ' + iconsDir);
- FileUpdater.updatePaths(
- resourceMap, { rootDir: cordovaProject.root }, logFileOp);
- }
- function cleanIcons (projectRoot, projectConfig, locations) {
- var icons = projectConfig.getIcons('ios');
- if (icons.length > 0) {
- var platformProjDir = path.relative(projectRoot, locations.xcodeCordovaProj);
- var iconsDir = getIconsDir(projectRoot, platformProjDir);
- var resourceMap = mapIconResources(icons, iconsDir);
- Object.keys(resourceMap).forEach(function (targetIconPath) {
- resourceMap[targetIconPath] = null;
- });
- events.emit('verbose', 'Cleaning icons at ' + iconsDir);
- // Source paths are removed from the map, so updatePaths() will delete the target files.
- FileUpdater.updatePaths(
- resourceMap, { rootDir: projectRoot, all: true }, logFileOp);
- }
- }
- function mapSplashScreenResources (splashScreens, splashScreensDir) {
- var platformSplashScreens = [
- {dest: 'Default~iphone.png', width: 320, height: 480},
- {dest: 'Default@2x~iphone.png', width: 640, height: 960},
- {dest: 'Default-Portrait~ipad.png', width: 768, height: 1024},
- {dest: 'Default-Portrait@2x~ipad.png', width: 1536, height: 2048},
- {dest: 'Default-Landscape~ipad.png', width: 1024, height: 768},
- {dest: 'Default-Landscape@2x~ipad.png', width: 2048, height: 1536},
- {dest: 'Default-568h@2x~iphone.png', width: 640, height: 1136},
- {dest: 'Default-667h.png', width: 750, height: 1334},
- {dest: 'Default-736h.png', width: 1242, height: 2208},
- {dest: 'Default-Landscape-736h.png', width: 2208, height: 1242},
- {dest: 'Default-2436h.png', width: 1125, height: 2436},
- {dest: 'Default-Landscape-2436h.png', width: 2436, height: 1125}
- ];
- var pathMap = {};
- platformSplashScreens.forEach(function (item) {
- var splash = splashScreens.getBySize(item.width, item.height);
- if (splash) {
- var target = path.join(splashScreensDir, item.dest);
- pathMap[target] = splash.src;
- }
- });
- return pathMap;
- }
- function getSplashScreensDir (projectRoot, platformProjDir) {
- var splashScreensDir;
- var xcassetsExists = folderExists(path.join(projectRoot, platformProjDir, 'Images.xcassets/'));
- if (xcassetsExists) {
- splashScreensDir = path.join(platformProjDir, 'Images.xcassets/LaunchImage.launchimage/');
- } else {
- splashScreensDir = path.join(platformProjDir, 'Resources/splash/');
- }
- return splashScreensDir;
- }
- function updateSplashScreens (cordovaProject, locations) {
- var splashScreens = cordovaProject.projectConfig.getSplashScreens('ios');
- if (splashScreens.length === 0) {
- events.emit('verbose', 'This app does not have splash screens defined');
- return;
- }
- var platformProjDir = path.relative(cordovaProject.root, locations.xcodeCordovaProj);
- var splashScreensDir = getSplashScreensDir(cordovaProject.root, platformProjDir);
- var resourceMap = mapSplashScreenResources(splashScreens, splashScreensDir);
- events.emit('verbose', 'Updating splash screens at ' + splashScreensDir);
- FileUpdater.updatePaths(
- resourceMap, { rootDir: cordovaProject.root }, logFileOp);
- }
- function cleanSplashScreens (projectRoot, projectConfig, locations) {
- var splashScreens = projectConfig.getSplashScreens('ios');
- if (splashScreens.length > 0) {
- var platformProjDir = path.relative(projectRoot, locations.xcodeCordovaProj);
- var splashScreensDir = getSplashScreensDir(projectRoot, platformProjDir);
- var resourceMap = mapIconResources(splashScreens, splashScreensDir);
- Object.keys(resourceMap).forEach(function (targetSplashPath) {
- resourceMap[targetSplashPath] = null;
- });
- events.emit('verbose', 'Cleaning splash screens at ' + splashScreensDir);
- // Source paths are removed from the map, so updatePaths() will delete the target files.
- FileUpdater.updatePaths(
- resourceMap, { rootDir: projectRoot, all: true }, logFileOp);
- }
- }
- function updateFileResources (cordovaProject, locations) {
- const platformDir = path.relative(cordovaProject.root, locations.root);
- const files = cordovaProject.projectConfig.getFileResources('ios');
- const project = projectFile.parse(locations);
- // if there are resource-file elements in config.xml
- if (files.length === 0) {
- events.emit('verbose', 'This app does not have additional resource files defined');
- return;
- }
- let resourceMap = {};
- files.forEach(function (res) {
- let src = res.src;
- let target = res.target;
- if (!target) {
- target = src;
- }
- let targetPath = path.join(project.resources_dir, target);
- targetPath = path.relative(cordovaProject.root, targetPath);
- project.xcode.addResourceFile(target);
- resourceMap[targetPath] = src;
- });
- events.emit('verbose', 'Updating resource files at ' + platformDir);
- FileUpdater.updatePaths(
- resourceMap, { rootDir: cordovaProject.root }, logFileOp);
- project.write();
- }
- function cleanFileResources (projectRoot, projectConfig, locations) {
- const platformDir = path.relative(projectRoot, locations.root);
- const files = projectConfig.getFileResources('ios', true);
- if (files.length > 0) {
- events.emit('verbose', 'Cleaning resource files at ' + platformDir);
- const project = projectFile.parse(locations);
- var resourceMap = {};
- files.forEach(function (res) {
- let src = res.src;
- let target = res.target;
- if (!target) {
- target = src;
- }
- let targetPath = path.join(project.resources_dir, target);
- targetPath = path.relative(projectRoot, targetPath);
- const resfile = path.join('Resources', path.basename(targetPath));
- project.xcode.removeResourceFile(resfile);
- resourceMap[targetPath] = null;
- });
- FileUpdater.updatePaths(
- resourceMap, {rootDir: projectRoot, all: true}, logFileOp);
- project.write();
- }
- }
- /**
- * Returns an array of images for each possible idiom, scale, and size class. The images themselves are
- * located in the platform's splash images by their pattern (@scale~idiom~sizesize). All possible
- * combinations are returned, but not all will have a `filename` property. If the latter isn't present,
- * the device won't attempt to load an image matching the same traits. If the filename is present,
- * the device will try to load the image if it corresponds to the traits.
- *
- * The resulting return looks like this:
- *
- * [
- * {
- * idiom: 'universal|ipad|iphone',
- * scale: '1x|2x|3x',
- * width: 'any|com',
- * height: 'any|com',
- * filename: undefined|'Default@scale~idiom~widthheight.png',
- * src: undefined|'path/to/original/matched/image/from/splash/screens.png',
- * target: undefined|'path/to/asset/library/Default@scale~idiom~widthheight.png'
- * }, ...
- * ]
- *
- * @param {Array<Object>} splashScreens splash screens as defined in config.xml for this platform
- * @param {string} launchStoryboardImagesDir project-root/Images.xcassets/LaunchStoryboard.imageset/
- * @return {Array<Object>}
- */
- function mapLaunchStoryboardContents (splashScreens, launchStoryboardImagesDir) {
- var platformLaunchStoryboardImages = [];
- var idioms = ['universal', 'ipad', 'iphone'];
- var scalesForIdiom = {
- universal: ['1x', '2x', '3x'],
- ipad: ['1x', '2x'],
- iphone: ['1x', '2x', '3x']
- };
- var sizes = ['com', 'any'];
- idioms.forEach(function (idiom) {
- scalesForIdiom[idiom].forEach(function (scale) {
- sizes.forEach(function (width) {
- sizes.forEach(function (height) {
- var item = {
- idiom: idiom,
- scale: scale,
- width: width,
- height: height
- };
- /* examples of the search pattern:
- * scale ~ idiom ~ width height
- * @2x ~ universal ~ any any
- * @3x ~ iphone ~ com any
- * @2x ~ ipad ~ com any
- */
- var searchPattern = '@' + scale + '~' + idiom + '~' + width + height;
- /* because old node versions don't have Array.find, the below is
- * functionally equivalent to this:
- * var launchStoryboardImage = splashScreens.find(function(item) {
- * return item.src.indexOf(searchPattern) >= 0;
- * });
- */
- var launchStoryboardImage = splashScreens.reduce(function (p, c) {
- return (c.src.indexOf(searchPattern) >= 0) ? c : p;
- }, undefined);
- if (launchStoryboardImage) {
- item.filename = 'Default' + searchPattern + '.png';
- item.src = launchStoryboardImage.src;
- item.target = path.join(launchStoryboardImagesDir, item.filename);
- }
- platformLaunchStoryboardImages.push(item);
- });
- });
- });
- });
- return platformLaunchStoryboardImages;
- }
- /**
- * Returns a dictionary representing the source and destination paths for the launch storyboard images
- * that need to be copied.
- *
- * The resulting return looks like this:
- *
- * {
- * 'target-path': 'source-path',
- * ...
- * }
- *
- * @param {Array<Object>} splashScreens splash screens as defined in config.xml for this platform
- * @param {string} launchStoryboardImagesDir project-root/Images.xcassets/LaunchStoryboard.imageset/
- * @return {Object}
- */
- function mapLaunchStoryboardResources (splashScreens, launchStoryboardImagesDir) {
- var platformLaunchStoryboardImages = mapLaunchStoryboardContents(splashScreens, launchStoryboardImagesDir);
- var pathMap = {};
- platformLaunchStoryboardImages.forEach(function (item) {
- if (item.target) {
- pathMap[item.target] = item.src;
- }
- });
- return pathMap;
- }
- /**
- * Builds the object that represents the contents.json file for the LaunchStoryboard image set.
- *
- * The resulting return looks like this:
- *
- * {
- * images: [
- * {
- * idiom: 'universal|ipad|iphone',
- * scale: '1x|2x|3x',
- * width-class: undefined|'compact',
- * height-class: undefined|'compact'
- * }, ...
- * ],
- * info: {
- * author: 'Xcode',
- * version: 1
- * }
- * }
- *
- * A bit of minor logic is used to map from the array of images returned from mapLaunchStoryboardContents
- * to the format requried by Xcode.
- *
- * @param {Array<Object>} splashScreens splash screens as defined in config.xml for this platform
- * @param {string} launchStoryboardImagesDir project-root/Images.xcassets/LaunchStoryboard.imageset/
- * @return {Object}
- */
- function getLaunchStoryboardContentsJSON (splashScreens, launchStoryboardImagesDir) {
- var platformLaunchStoryboardImages = mapLaunchStoryboardContents(splashScreens, launchStoryboardImagesDir);
- var contentsJSON = {
- images: [],
- info: {
- author: 'Xcode',
- version: 1
- }
- };
- contentsJSON.images = platformLaunchStoryboardImages.map(function (item) {
- var newItem = {
- idiom: item.idiom,
- scale: item.scale
- };
- // Xcode doesn't want any size class property if the class is "any"
- // If our size class is "com", Xcode wants "compact".
- if (item.width !== CDV_ANY_SIZE_CLASS) {
- newItem['width-class'] = IMAGESET_COMPACT_SIZE_CLASS;
- }
- if (item.height !== CDV_ANY_SIZE_CLASS) {
- newItem['height-class'] = IMAGESET_COMPACT_SIZE_CLASS;
- }
- // Xcode doesn't want a filename property if there's no image for these traits
- if (item.filename) {
- newItem.filename = item.filename;
- }
- return newItem;
- });
- return contentsJSON;
- }
- /**
- * Determines if the project's build settings may need to be updated for launch storyboard support
- *
- */
- function checkIfBuildSettingsNeedUpdatedForLaunchStoryboard (platformConfig, infoPlist) {
- var hasLaunchStoryboardImages = platformHasLaunchStoryboardImages(platformConfig);
- var hasLegacyLaunchImages = platformHasLegacyLaunchImages(platformConfig);
- var currentLaunchStoryboard = infoPlist[UI_LAUNCH_STORYBOARD_NAME];
- if (hasLaunchStoryboardImages && currentLaunchStoryboard === CDV_LAUNCH_STORYBOARD_NAME && !hasLegacyLaunchImages) {
- // don't need legacy launch images if we are using our launch storyboard
- // so we do need to update the project file
- events.emit('verbose', 'Need to update build settings because project is using our launch storyboard.');
- return true;
- } else if (hasLegacyLaunchImages && !currentLaunchStoryboard) {
- // we do need to ensure legacy launch images are used if there's no launch storyboard present
- // so we do need to update the project file
- events.emit('verbose', 'Need to update build settings because project is using legacy launch images and no storyboard.');
- return true;
- }
- events.emit('verbose', 'No need to update build settings for launch storyboard support.');
- return false;
- }
- function updateBuildSettingsForLaunchStoryboard (proj, platformConfig, infoPlist) {
- var hasLaunchStoryboardImages = platformHasLaunchStoryboardImages(platformConfig);
- var hasLegacyLaunchImages = platformHasLegacyLaunchImages(platformConfig);
- var currentLaunchStoryboard = infoPlist[UI_LAUNCH_STORYBOARD_NAME];
- if (hasLaunchStoryboardImages && currentLaunchStoryboard === CDV_LAUNCH_STORYBOARD_NAME && !hasLegacyLaunchImages) {
- // don't need legacy launch images if we are using our launch storyboard
- events.emit('verbose', 'Removed ' + LAUNCHIMAGE_BUILD_SETTING + ' because project is using our launch storyboard.');
- proj.removeBuildProperty(LAUNCHIMAGE_BUILD_SETTING);
- } else if (hasLegacyLaunchImages && !currentLaunchStoryboard) {
- // we do need to ensure legacy launch images are used if there's no launch storyboard present
- events.emit('verbose', 'Set ' + LAUNCHIMAGE_BUILD_SETTING + ' to ' + LAUNCHIMAGE_BUILD_SETTING_VALUE + ' because project is using legacy launch images and no storyboard.');
- proj.updateBuildProperty(LAUNCHIMAGE_BUILD_SETTING, LAUNCHIMAGE_BUILD_SETTING_VALUE);
- } else {
- events.emit('verbose', 'Did not update build settings for launch storyboard support.');
- }
- }
- function splashScreensHaveLaunchStoryboardImages (contentsJSON) {
- /* do we have any launch images do we have for our launch storyboard?
- * Again, for old Node versions, the below code is equivalent to this:
- * return !!contentsJSON.images.find(function (item) {
- * return item.filename !== undefined;
- * });
- */
- return !!contentsJSON.images.reduce(function (p, c) {
- return (c.filename !== undefined) ? c : p;
- }, undefined);
- }
- function platformHasLaunchStoryboardImages (platformConfig) {
- var splashScreens = platformConfig.getSplashScreens('ios');
- var contentsJSON = getLaunchStoryboardContentsJSON(splashScreens, ''); // note: we don't need a file path here; we're just counting
- return splashScreensHaveLaunchStoryboardImages(contentsJSON);
- }
- function platformHasLegacyLaunchImages (platformConfig) {
- var splashScreens = platformConfig.getSplashScreens('ios');
- return !!splashScreens.reduce(function (p, c) {
- return (c.width !== undefined || c.height !== undefined) ? c : p;
- }, undefined);
- }
- /**
- * Updates the project's plist based upon our launch storyboard images. If there are no images, then we should
- * fall back to the regular launch images that might be supplied (that is, our app will be scaled on an iPad Pro),
- * and if there are some images, we need to alter the UILaunchStoryboardName property to point to
- * CDVLaunchScreen.
- *
- * There's some logic here to avoid overwriting changes the user might have made to their plist if they are using
- * their own launch storyboard.
- */
- function updateProjectPlistForLaunchStoryboard (platformConfig, infoPlist) {
- var currentLaunchStoryboard = infoPlist[UI_LAUNCH_STORYBOARD_NAME];
- events.emit('verbose', 'Current launch storyboard ' + currentLaunchStoryboard);
- var hasLaunchStoryboardImages = platformHasLaunchStoryboardImages(platformConfig);
- if (hasLaunchStoryboardImages && !currentLaunchStoryboard) {
- // only change the launch storyboard if we have images to use AND the current value is blank
- // if it's not blank, we've either done this before, or the user has their own launch storyboard
- events.emit('verbose', 'Changing info plist to use our launch storyboard');
- infoPlist[UI_LAUNCH_STORYBOARD_NAME] = CDV_LAUNCH_STORYBOARD_NAME;
- return;
- }
- if (!hasLaunchStoryboardImages && currentLaunchStoryboard === CDV_LAUNCH_STORYBOARD_NAME) {
- // only revert to using the launch images if we have don't have any images for the launch storyboard
- // but only clear it if current launch storyboard is our storyboard; the user might be using their
- // own storyboard instead.
- events.emit('verbose', 'Changing info plist to use legacy launch images');
- delete infoPlist[UI_LAUNCH_STORYBOARD_NAME];
- return;
- }
- events.emit('verbose', 'Not changing launch storyboard setting in info plist.');
- }
- /**
- * Returns the directory for the Launch Storyboard image set, if image sets are being used. If they aren't
- * being used, returns null.
- *
- * @param {string} projectRoot The project's root directory
- * @param {string} platformProjDir The platform's project directory
- */
- function getLaunchStoryboardImagesDir (projectRoot, platformProjDir) {
- var launchStoryboardImagesDir;
- var xcassetsExists = folderExists(path.join(projectRoot, platformProjDir, 'Images.xcassets/'));
- if (xcassetsExists) {
- launchStoryboardImagesDir = path.join(platformProjDir, 'Images.xcassets/LaunchStoryboard.imageset/');
- } else {
- // if we don't have a asset library for images, we can't do the storyboard.
- launchStoryboardImagesDir = null;
- }
- return launchStoryboardImagesDir;
- }
- /**
- * Update the images for the Launch Storyboard and updates the image set's contents.json file appropriately.
- *
- * @param {Object} cordovaProject The cordova project
- * @param {Object} locations A dictionary containing useful location paths
- */
- function updateLaunchStoryboardImages (cordovaProject, locations) {
- var splashScreens = cordovaProject.projectConfig.getSplashScreens('ios');
- var platformProjDir = path.relative(cordovaProject.root, locations.xcodeCordovaProj);
- var launchStoryboardImagesDir = getLaunchStoryboardImagesDir(cordovaProject.root, platformProjDir);
- if (launchStoryboardImagesDir) {
- var resourceMap = mapLaunchStoryboardResources(splashScreens, launchStoryboardImagesDir);
- var contentsJSON = getLaunchStoryboardContentsJSON(splashScreens, launchStoryboardImagesDir);
- events.emit('verbose', 'Updating launch storyboard images at ' + launchStoryboardImagesDir);
- FileUpdater.updatePaths(
- resourceMap, { rootDir: cordovaProject.root }, logFileOp);
- events.emit('verbose', 'Updating Storyboard image set contents.json');
- fs.writeFileSync(path.join(cordovaProject.root, launchStoryboardImagesDir, 'Contents.json'),
- JSON.stringify(contentsJSON, null, 2));
- }
- }
- /**
- * Removes the images from the launch storyboard's image set and updates the image set's contents.json
- * file appropriately.
- *
- * @param {string} projectRoot Path to the project root
- * @param {Object} projectConfig The project's config.xml
- * @param {Object} locations A dictionary containing useful location paths
- */
- function cleanLaunchStoryboardImages (projectRoot, projectConfig, locations) {
- var splashScreens = projectConfig.getSplashScreens('ios');
- var platformProjDir = path.relative(projectRoot, locations.xcodeCordovaProj);
- var launchStoryboardImagesDir = getLaunchStoryboardImagesDir(projectRoot, platformProjDir);
- if (launchStoryboardImagesDir) {
- var resourceMap = mapLaunchStoryboardResources(splashScreens, launchStoryboardImagesDir);
- var contentsJSON = getLaunchStoryboardContentsJSON(splashScreens, launchStoryboardImagesDir);
- Object.keys(resourceMap).forEach(function (targetPath) {
- resourceMap[targetPath] = null;
- });
- events.emit('verbose', 'Cleaning storyboard image set at ' + launchStoryboardImagesDir);
- // Source paths are removed from the map, so updatePaths() will delete the target files.
- FileUpdater.updatePaths(
- resourceMap, { rootDir: projectRoot, all: true }, logFileOp);
- // delete filename from contents.json
- contentsJSON.images.forEach(function (image) {
- image.filename = undefined;
- });
- events.emit('verbose', 'Updating Storyboard image set contents.json');
- fs.writeFileSync(path.join(projectRoot, launchStoryboardImagesDir, 'Contents.json'),
- JSON.stringify(contentsJSON, null, 2));
- }
- }
- /**
- * Queries ConfigParser object for the orientation <preference> value. Warns if
- * global preference value is not supported by platform.
- *
- * @param {Object} platformConfig ConfigParser object
- *
- * @return {String} Global/platform-specific orientation in lower-case
- * (or empty string if both are undefined).
- */
- function getOrientationValue (platformConfig) {
- var ORIENTATION_DEFAULT = 'default';
- var orientation = platformConfig.getPreference('orientation');
- if (!orientation) {
- return '';
- }
- orientation = orientation.toLowerCase();
- // Check if the given global orientation is supported
- if (['default', 'portrait', 'landscape', 'all'].indexOf(orientation) >= 0) {
- return orientation;
- }
- events.emit('warn', 'Unrecognized value for Orientation preference: ' + orientation +
- '. Defaulting to value: ' + ORIENTATION_DEFAULT + '.');
- return ORIENTATION_DEFAULT;
- }
- /*
- Parses all <access> and <allow-navigation> entries and consolidates duplicates (for ATS).
- Returns an object with a Hostname as the key, and the value an object with properties:
- {
- Hostname, // String
- NSExceptionAllowsInsecureHTTPLoads, // boolean
- NSIncludesSubdomains, // boolean
- NSExceptionMinimumTLSVersion, // String
- NSExceptionRequiresForwardSecrecy, // boolean
- NSRequiresCertificateTransparency, // boolean
- // the three below _only_ show when the Hostname is '*'
- // if any of the three are set, it disables setting NSAllowsArbitraryLoads
- // (Apple already enforces this in ATS)
- NSAllowsArbitraryLoadsInWebContent, // boolean (default: false)
- NSAllowsLocalNetworking, // boolean (default: false)
- NSAllowsArbitraryLoadsForMedia, // boolean (default:false)
- }
- */
- function processAccessAndAllowNavigationEntries (config) {
- var accesses = config.getAccesses();
- var allow_navigations = config.getAllowNavigations();
- return allow_navigations
- // we concat allow_navigations and accesses, after processing accesses
- .concat(accesses.map(function (obj) {
- // map accesses to a common key interface using 'href', not origin
- obj.href = obj.origin;
- delete obj.origin;
- return obj;
- }))
- // we reduce the array to an object with all the entries processed (key is Hostname)
- .reduce(function (previousReturn, currentElement) {
- var options = {
- minimum_tls_version: currentElement.minimum_tls_version,
- requires_forward_secrecy: currentElement.requires_forward_secrecy,
- requires_certificate_transparency: currentElement.requires_certificate_transparency,
- allows_arbitrary_loads_for_media: currentElement.allows_arbitrary_loads_in_media || currentElement.allows_arbitrary_loads_for_media,
- allows_arbitrary_loads_in_web_content: currentElement.allows_arbitrary_loads_in_web_content,
- allows_local_networking: currentElement.allows_local_networking
- };
- var obj = parseWhitelistUrlForATS(currentElement.href, options);
- if (obj) {
- // we 'union' duplicate entries
- var item = previousReturn[obj.Hostname];
- if (!item) {
- item = {};
- }
- for (var o in obj) {
- if (obj.hasOwnProperty(o)) {
- item[o] = obj[o];
- }
- }
- previousReturn[obj.Hostname] = item;
- }
- return previousReturn;
- }, {});
- }
- /*
- Parses a URL and returns an object with these keys:
- {
- Hostname, // String
- NSExceptionAllowsInsecureHTTPLoads, // boolean (default: false)
- NSIncludesSubdomains, // boolean (default: false)
- NSExceptionMinimumTLSVersion, // String (default: 'TLSv1.2')
- NSExceptionRequiresForwardSecrecy, // boolean (default: true)
- NSRequiresCertificateTransparency, // boolean (default: false)
- // the three below _only_ apply when the Hostname is '*'
- // if any of the three are set, it disables setting NSAllowsArbitraryLoads
- // (Apple already enforces this in ATS)
- NSAllowsArbitraryLoadsInWebContent, // boolean (default: false)
- NSAllowsLocalNetworking, // boolean (default: false)
- NSAllowsArbitraryLoadsForMedia, // boolean (default:false)
- }
- null is returned if the URL cannot be parsed, or is to be skipped for ATS.
- */
- function parseWhitelistUrlForATS (url, options) {
- var href = URL.parse(url);
- var retObj = {};
- retObj.Hostname = href.hostname;
- // Guiding principle: we only set values in retObj if they are NOT the default
- if (url === '*') {
- retObj.Hostname = '*';
- var val;
- val = (options.allows_arbitrary_loads_in_web_content === 'true');
- if (options.allows_arbitrary_loads_in_web_content && val) { // default is false
- retObj.NSAllowsArbitraryLoadsInWebContent = true;
- }
- val = (options.allows_arbitrary_loads_for_media === 'true');
- if (options.allows_arbitrary_loads_for_media && val) { // default is false
- retObj.NSAllowsArbitraryLoadsForMedia = true;
- }
- val = (options.allows_local_networking === 'true');
- if (options.allows_local_networking && val) { // default is false
- retObj.NSAllowsLocalNetworking = true;
- }
- return retObj;
- }
- if (!retObj.Hostname) {
- // check origin, if it allows subdomains (wildcard in hostname), we set NSIncludesSubdomains to YES. Default is NO
- var subdomain1 = '/*.'; // wildcard in hostname
- var subdomain2 = '*://*.'; // wildcard in hostname and protocol
- var subdomain3 = '*://'; // wildcard in protocol only
- if (href.pathname.indexOf(subdomain1) === 0) {
- retObj.NSIncludesSubdomains = true;
- retObj.Hostname = href.pathname.substring(subdomain1.length);
- } else if (href.pathname.indexOf(subdomain2) === 0) {
- retObj.NSIncludesSubdomains = true;
- retObj.Hostname = href.pathname.substring(subdomain2.length);
- } else if (href.pathname.indexOf(subdomain3) === 0) {
- retObj.Hostname = href.pathname.substring(subdomain3.length);
- } else {
- // Handling "scheme:*" case to avoid creating of a blank key in NSExceptionDomains.
- return null;
- }
- }
- if (options.minimum_tls_version && options.minimum_tls_version !== 'TLSv1.2') { // default is TLSv1.2
- retObj.NSExceptionMinimumTLSVersion = options.minimum_tls_version;
- }
- var rfs = (options.requires_forward_secrecy === 'true');
- if (options.requires_forward_secrecy && !rfs) { // default is true
- retObj.NSExceptionRequiresForwardSecrecy = false;
- }
- var rct = (options.requires_certificate_transparency === 'true');
- if (options.requires_certificate_transparency && rct) { // default is false
- retObj.NSRequiresCertificateTransparency = true;
- }
- // if the scheme is HTTP, we set NSExceptionAllowsInsecureHTTPLoads to YES. Default is NO
- if (href.protocol === 'http:') {
- retObj.NSExceptionAllowsInsecureHTTPLoads = true;
- } else if (!href.protocol && href.pathname.indexOf('*:/') === 0) { // wilcard in protocol
- retObj.NSExceptionAllowsInsecureHTTPLoads = true;
- }
- return retObj;
- }
- /*
- App Transport Security (ATS) writer from <access> and <allow-navigation> tags
- in config.xml
- */
- function writeATSEntries (config) {
- var pObj = processAccessAndAllowNavigationEntries(config);
- var ats = {};
- for (var hostname in pObj) {
- if (pObj.hasOwnProperty(hostname)) {
- var entry = pObj[hostname];
- // Guiding principle: we only set values if they are available
- if (hostname === '*') {
- // always write this, for iOS 9, since in iOS 10 it will be overriden if
- // any of the other three keys are written
- ats['NSAllowsArbitraryLoads'] = true;
- // at least one of the overriding keys is present
- if (entry.NSAllowsArbitraryLoadsInWebContent) {
- ats['NSAllowsArbitraryLoadsInWebContent'] = true;
- }
- if (entry.NSAllowsArbitraryLoadsForMedia) {
- ats['NSAllowsArbitraryLoadsForMedia'] = true;
- }
- if (entry.NSAllowsLocalNetworking) {
- ats['NSAllowsLocalNetworking'] = true;
- }
- continue;
- }
- var exceptionDomain = {};
- for (var key in entry) {
- if (entry.hasOwnProperty(key) && key !== 'Hostname') {
- exceptionDomain[key] = entry[key];
- }
- }
- if (!ats['NSExceptionDomains']) {
- ats['NSExceptionDomains'] = {};
- }
- ats['NSExceptionDomains'][hostname] = exceptionDomain;
- }
- }
- return ats;
- }
- function folderExists (folderPath) {
- try {
- var stat = fs.statSync(folderPath);
- return stat && stat.isDirectory();
- } catch (e) {
- return false;
- }
- }
- // Construct a default value for CFBundleVersion as the version with any
- // -rclabel stripped=.
- function default_CFBundleVersion (version) {
- return version.split('-')[0];
- }
- // Converts cordova specific representation of target device to XCode value
- function parseTargetDevicePreference (value) {
- if (!value) return null;
- var map = {'universal': '"1,2"', 'handset': '"1"', 'tablet': '"2"'};
- if (map[value.toLowerCase()]) {
- return map[value.toLowerCase()];
- }
- events.emit('warn', 'Unrecognized value for target-device preference: ' + value + '.');
- return null;
- }
|