diff options
| author | Jules Laplace <julescarbon@gmail.com> | 2020-08-31 23:07:20 +0200 |
|---|---|---|
| committer | Jules Laplace <julescarbon@gmail.com> | 2020-08-31 23:07:20 +0200 |
| commit | 22721a013bdd10d5eb395ba18453585f5f3f1f7f (patch) | |
| tree | 5a920e31d6026ed5dc55265e5fd057febccc50e3 /StoneIsland/platforms/ios/cordova/lib | |
| parent | d22d51a1ae49680015326857360eb699f31efced (diff) | |
rebuild the ios platform and the plugins
Diffstat (limited to 'StoneIsland/platforms/ios/cordova/lib')
21 files changed, 1630 insertions, 1412 deletions
diff --git a/StoneIsland/platforms/ios/cordova/lib/BridgingHeader.js b/StoneIsland/platforms/ios/cordova/lib/BridgingHeader.js new file mode 100644 index 00000000..9e200b50 --- /dev/null +++ b/StoneIsland/platforms/ios/cordova/lib/BridgingHeader.js @@ -0,0 +1,123 @@ +/* + 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'; + +const fs = require('fs-extra'); +const CordovaError = require('cordova-common').CordovaError; + +function BridgingHeader (bridgingHeaderPath) { + this.path = bridgingHeaderPath; + this.bridgingHeaders = null; + if (!fs.existsSync(this.path)) { + throw new CordovaError('BridgingHeader.h is not found.'); + } + this.bridgingHeaders = this.__parseForBridgingHeader(fs.readFileSync(this.path, 'utf8')); +} + +BridgingHeader.prototype.addHeader = function (plugin_id, header_path) { + this.bridgingHeaders.push({ type: 'code', code: `#import "${header_path}"\n` }); +}; + +BridgingHeader.prototype.removeHeader = function (plugin_id, header_path) { + this.bridgingHeaders = this.bridgingHeaders.filter(function (line) { + if (this.found) { + return true; + } + if (line.type === 'code') { + const re = new RegExp(`#import\\s+"${preg_quote(header_path)}"(\\s*|\\s.+)(\\n|$)`); + if (re.test(line.code)) { + this.found = true; + return false; + } + } + return true; + }, { found: false }); +}; + +BridgingHeader.prototype.write = function () { + const text = this.__stringifyForBridgingHeader(this.bridgingHeaders); + fs.writeFileSync(this.path, text, 'utf8'); +}; + +BridgingHeader.prototype.__stringifyForBridgingHeader = function (bridgingHeaders) { + return bridgingHeaders.map(obj => obj.code).join(''); +}; + +BridgingHeader.prototype.__parseForBridgingHeader = function (text) { + let i = 0; + const list = []; + let type = 'code'; + let start = 0; + while (i < text.length) { + switch (type) { + case 'comment': + if (i + 1 < text.length && text[i] === '*' && text[i + 1] === '/') { + i += 2; + list.push({ type, code: text.slice(start, i) }); + type = 'code'; + start = i; + } else { + i += 1; + } + break; + case 'line-comment': + if (i < text.length && text[i] === '\n') { + i += 1; + list.push({ type, code: text.slice(start, i) }); + type = 'code'; + start = i; + } else { + i += 1; + } + break; + case 'code': + default: + if (i + 1 < text.length && text[i] === '/' && text[i + 1] === '*') { // comment + if (start < i) { + list.push({ type, code: text.slice(start, i) }); + } + type = 'comment'; + start = i; + } else if (i + 1 < text.length && text[i] === '/' && text[i + 1] === '/') { // line comment + if (start < i) { + list.push({ type, code: text.slice(start, i) }); + } + type = 'line-comment'; + start = i; + } else if (i < text.length && text[i] === '\n') { + i += 1; + list.push({ type, code: text.slice(start, i) }); + start = i; + } else { + i += 1; + } + break; + } + } + if (start < i) { + list.push({ type, code: text.slice(start, i) }); + } + return list; +}; + +function preg_quote (str, delimiter) { + return (`${str}`).replace(new RegExp(`[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\${delimiter || ''}-]`, 'g'), '\\$&'); +} + +module.exports.BridgingHeader = BridgingHeader; diff --git a/StoneIsland/platforms/ios/cordova/lib/Podfile.js b/StoneIsland/platforms/ios/cordova/lib/Podfile.js index 49173c4c..f1672974 100755..100644 --- a/StoneIsland/platforms/ios/cordova/lib/Podfile.js +++ b/StoneIsland/platforms/ios/cordova/lib/Podfile.js @@ -18,27 +18,37 @@ */ 'use strict'; -var fs = require('fs'); -var path = require('path'); -var util = require('util'); -var events = require('cordova-common').events; -var Q = require('q'); -var superspawn = require('cordova-common').superspawn; -var CordovaError = require('cordova-common').CordovaError; +const fs = require('fs-extra'); +const path = require('path'); +const util = require('util'); +const { + CordovaError, + events, + superspawn: { spawn } +} = require('cordova-common'); Podfile.FILENAME = 'Podfile'; +Podfile.declarationRegexpMap = { + 'use_frameworks!': 'use[-_]frameworks!?', + 'inhibit_all_warnings!': 'inhibit[-_]all[-_]warnings!?' +}; -function Podfile (podFilePath, projectName) { +function Podfile (podFilePath, projectName, minDeploymentTarget) { + this.declarationToken = '##INSERT_DECLARATION##'; + this.sourceToken = '##INSERT_SOURCE##'; this.podToken = '##INSERT_POD##'; this.path = podFilePath; this.projectName = projectName; + this.minDeploymentTarget = minDeploymentTarget || '11.0'; this.contents = null; + this.sources = null; + this.declarations = null; this.pods = null; this.__dirty = false; // check whether it is named Podfile - var filename = this.path.split(path.sep).pop(); + const filename = this.path.split(path.sep).pop(); if (filename !== Podfile.FILENAME) { throw new CordovaError(util.format('Podfile: The file at %s is not `%s`.', this.path, Podfile.FILENAME)); } @@ -55,33 +65,101 @@ function Podfile (podFilePath, projectName) { } else { events.emit('verbose', 'Podfile found in platforms/ios'); // parse for pods - this.pods = this.__parseForPods(fs.readFileSync(this.path, 'utf8')); + const fileText = fs.readFileSync(this.path, 'utf8'); + this.declarations = this.__parseForDeclarations(fileText); + this.sources = this.__parseForSources(fileText); + this.pods = this.__parseForPods(fileText); } } +Podfile.prototype.__parseForDeclarations = function (text) { + // split by \n + const arr = text.split('\n'); + + // getting lines between "platform :ios, '11.0'"" and "target 'HelloCordova'" do + const declarationsPreRE = new RegExp('platform :ios,\\s+\'[^\']+\''); + const declarationsPostRE = new RegExp('target\\s+\'[^\']+\'\\s+do'); + const declarationRE = new RegExp('^\\s*[^#]'); + + return arr.reduce((acc, line) => { + switch (acc.state) { + case 0: + if (declarationsPreRE.exec(line)) { + acc.state = 1; // Start to read + } + break; + case 1: + if (declarationsPostRE.exec(line)) { + acc.state = 2; // Finish to read + } else { + acc.lines.push(line); + } + break; + case 2: + default: + // do nothing; + } + return acc; + }, { state: 0, lines: [] }) + .lines + .filter(line => declarationRE.exec(line)) + .reduce((obj, line) => { + obj[line] = line; + return obj; + }, {}); +}; + +Podfile.prototype.__parseForSources = function (text) { + // split by \n + const arr = text.split('\n'); + + const sourceRE = new RegExp('source \'(.*)\''); + return arr.filter(line => { + const m = sourceRE.exec(line); + + return (m !== null); + }) + .reduce((obj, line) => { + const m = sourceRE.exec(line); + if (m !== null) { + const source = m[1]; + obj[source] = source; + } + return obj; + }, {}); +}; + Podfile.prototype.__parseForPods = function (text) { // split by \n - var arr = text.split('\n'); + const arr = text.split('\n'); // aim is to match (space insignificant around the comma, comma optional): // pod 'Foobar', '1.2' // pod 'Foobar', 'abc 123 1.2' // pod 'PonyDebugger', :configurations => ['Debug', 'Beta'] - var podRE = new RegExp('pod \'([^\']*)\'\\s*,?\\s*(.*)'); + // var podRE = new RegExp('pod \'([^\']*)\'\\s*,?\\s*(.*)'); + const podRE = new RegExp('pod \'([^\']*)\'\\s*(?:,\\s*\'([^\']*)\'\\s*)?,?\\s*(.*)'); // only grab lines that don't have the pod spec' - return arr.filter(function (line) { - var m = podRE.exec(line); + return arr.filter(line => { + const m = podRE.exec(line); return (m !== null); }) - .reduce(function (obj, line) { - var m = podRE.exec(line); + .reduce((obj, line) => { + const m = podRE.exec(line); if (m !== null) { - // strip out any single quotes around the value m[2] - var podSpec = m[2].replace(/^\'|\'$/g, ''); /* eslint no-useless-escape : 0 */ - obj[m[1]] = podSpec; // i.e pod 'Foo', '1.2' ==> { 'Foo' : '1.2'} + const podspec = { + name: m[1] + }; + if (m[2]) { + podspec.spec = m[2]; + } + if (m[3]) { + podspec.options = m[3]; + } + obj[m[1]] = podspec; // i.e pod 'Foo', '1.2' ==> { 'Foo' : '1.2'} } return obj; @@ -89,20 +167,22 @@ Podfile.prototype.__parseForPods = function (text) { }; Podfile.prototype.escapeSingleQuotes = function (string) { - return string.replace('\'', '\\\''); + return string.replace(/'/g, '\\\''); }; Podfile.prototype.getTemplate = function () { // Escaping possible ' in the project name - var projectName = this.escapeSingleQuotes(this.projectName); + const projectName = this.escapeSingleQuotes(this.projectName); return util.format( '# DO NOT MODIFY -- auto-generated by Apache Cordova\n' + - 'platform :ios, \'8.0\'\n' + + '%s\n' + + 'platform :ios, \'%s\'\n' + + '%s\n' + 'target \'%s\' do\n' + '\tproject \'%s.xcodeproj\'\n' + '%s\n' + 'end\n', - projectName, projectName, this.podToken); + this.sourceToken, this.minDeploymentTarget, this.declarationToken, projectName, projectName, this.podToken); }; Podfile.prototype.addSpec = function (name, spec) { @@ -114,6 +194,14 @@ Podfile.prototype.addSpec = function (name, spec) { throw new CordovaError('Podfile addSpec: name is not specified.'); } + if (typeof spec === 'string') { + if (spec.startsWith(':')) { + spec = { name, options: spec }; + } else { + spec = { name, spec }; + } + } + this.pods[name] = spec; this.__dirty = true; @@ -137,7 +225,60 @@ Podfile.prototype.existsSpec = function (name) { return (name in this.pods); }; +Podfile.prototype.addSource = function (src) { + this.sources[src] = src; + this.__dirty = true; + + events.emit('verbose', util.format('Added source line for `%s`', src)); +}; + +Podfile.prototype.removeSource = function (src) { + if (this.existsSource(src)) { + delete this.sources[src]; + this.__dirty = true; + } + + events.emit('verbose', util.format('Removed source line for `%s`', src)); +}; + +Podfile.prototype.existsSource = function (src) { + return (src in this.sources); +}; + +Podfile.prototype.addDeclaration = function (declaration) { + this.declarations[declaration] = declaration; + this.__dirty = true; + + events.emit('verbose', util.format('Added declaration line for `%s`', declaration)); +}; + +Podfile.prototype.removeDeclaration = function (declaration) { + if (this.existsDeclaration(declaration)) { + delete this.declarations[declaration]; + this.__dirty = true; + } + + events.emit('verbose', util.format('Removed source line for `%s`', declaration)); +}; + +Podfile.proofDeclaration = declaration => { + const list = Object.keys(Podfile.declarationRegexpMap).filter(key => { + const regexp = new RegExp(Podfile.declarationRegexpMap[key]); + return regexp.test(declaration); + }); + if (list.length > 0) { + return list[0]; + } + return declaration; +}; + +Podfile.prototype.existsDeclaration = function (declaration) { + return (declaration in this.declarations); +}; + Podfile.prototype.clear = function () { + this.sources = {}; + this.declarations = {}; this.pods = {}; this.__dirty = true; }; @@ -148,28 +289,65 @@ Podfile.prototype.destroy = function () { }; Podfile.prototype.write = function () { - var text = this.getTemplate(); - var self = this; + let text = this.getTemplate(); - var podsString = - Object.keys(this.pods).map(function (key) { - var name = key; - var spec = self.pods[key]; + const podsString = + Object.keys(this.pods).map(key => { + const name = key; + const json = this.pods[key]; - if (spec.length) { - if (spec.indexOf(':') === 0) { - // don't quote it, it's a specification (starts with ':') - return util.format('\tpod \'%s\', %s', name, spec); + if (typeof json === 'string') { // compatibility for using framework tag. + const spec = json; + if (spec.length) { + if (spec.indexOf(':') === 0) { + // don't quote it, it's a specification (starts with ':') + return util.format('\tpod \'%s\', %s', name, spec); + } else { + // quote it, it's a version + return util.format('\tpod \'%s\', \'%s\'', name, spec); + } } else { - // quote it, it's a version - return util.format('\tpod \'%s\', \'%s\'', name, spec); + return util.format('\tpod \'%s\'', name); } } else { - return util.format('\tpod \'%s\'', name); + const list = [`'${name}'`]; + if ('spec' in json && json.spec.length) { + list.push(`'${json.spec}'`); + } + + let options = ['tag', 'branch', 'commit', 'git', 'podspec'] + .filter(tag => tag in json) + .map(tag => `:${tag} => '${json[tag]}'`); + + if ('configurations' in json) { + options.push(`:configurations => [${json.configurations.split(',').map(conf => `'${conf.trim()}'`).join(',')}]`); + } + if ('options' in json) { + options = [json.options]; + } + if (options.length > 0) { + list.push(options.join(', ')); + } + return util.format('\tpod %s', list.join(', ')); } }).join('\n'); - text = text.replace(this.podToken, podsString); + const sourcesString = + Object.keys(this.sources).map(key => { + const source = this.sources[key]; + return util.format('source \'%s\'', source); + }).join('\n'); + + const declarationString = + Object.keys(this.declarations).map(key => { + const declaration = this.declarations[key]; + return declaration; + }).join('\n'); + + text = text.replace(this.podToken, podsString) + .replace(this.sourceToken, sourcesString) + .replace(this.declarationToken, declarationString); + fs.writeFileSync(this.path, text, 'utf8'); this.__dirty = false; @@ -184,45 +362,43 @@ Podfile.prototype.before_install = function (toolOptions) { toolOptions = toolOptions || {}; // Template tokens in order: project name, project name, debug | release - var template = + const template = '// DO NOT MODIFY -- auto-generated by Apache Cordova\n' + '#include "Pods/Target Support Files/Pods-%s/Pods-%s.%s.xcconfig"'; - var debugContents = util.format(template, this.projectName, this.projectName, 'debug'); - var releaseContents = util.format(template, this.projectName, this.projectName, 'release'); + const debugContents = util.format(template, this.projectName, this.projectName, 'debug'); + const releaseContents = util.format(template, this.projectName, this.projectName, 'release'); - var debugConfigPath = path.join(this.path, '..', 'pods-debug.xcconfig'); - var releaseConfigPath = path.join(this.path, '..', 'pods-release.xcconfig'); + const debugConfigPath = path.join(this.path, '..', 'pods-debug.xcconfig'); + const releaseConfigPath = path.join(this.path, '..', 'pods-release.xcconfig'); fs.writeFileSync(debugConfigPath, debugContents, 'utf8'); fs.writeFileSync(releaseConfigPath, releaseContents, 'utf8'); - return Q.resolve(toolOptions); + return Promise.resolve(toolOptions); }; Podfile.prototype.install = function (requirementsCheckerFunction) { - var opts = {}; + const opts = {}; opts.cwd = path.join(this.path, '..'); // parent path of this Podfile opts.stdio = 'pipe'; - var first = true; - var self = this; + opts.printCommand = true; + let first = true; if (!requirementsCheckerFunction) { - requirementsCheckerFunction = Q(); + requirementsCheckerFunction = Promise.resolve(); } return requirementsCheckerFunction() - .then(function (toolOptions) { - return self.before_install(toolOptions); - }) - .then(function (toolOptions) { + .then(toolOptions => this.before_install(toolOptions)) + .then(toolOptions => { if (toolOptions.ignore) { events.emit('verbose', '==== pod install start ====\n'); events.emit('verbose', toolOptions.ignoreMessage); - return Q.resolve(); + return Promise.resolve(); } else { - return superspawn.spawn('pod', ['install', '--verbose'], opts) - .progress(function (stdio) { + return spawn('pod', ['install', '--verbose'], opts) + .progress(stdio => { if (stdio.stderr) { console.error(stdio.stderr); } if (stdio.stdout) { if (first) { @@ -234,11 +410,8 @@ Podfile.prototype.install = function (requirementsCheckerFunction) { }); } }) - .then(function () { // done + .then(() => { // done events.emit('verbose', '==== pod install end ====\n'); - }) - .fail(function (error) { - throw error; }); }; diff --git a/StoneIsland/platforms/ios/cordova/lib/PodsJson.js b/StoneIsland/platforms/ios/cordova/lib/PodsJson.js index 04705273..598d9607 100755..100644 --- a/StoneIsland/platforms/ios/cordova/lib/PodsJson.js +++ b/StoneIsland/platforms/ios/cordova/lib/PodsJson.js @@ -17,20 +17,23 @@ under the License. */ -var fs = require('fs'); -var path = require('path'); -var util = require('util'); -var events = require('cordova-common').events; -var CordovaError = require('cordova-common').CordovaError; +const fs = require('fs-extra'); +const path = require('path'); +const util = require('util'); +const events = require('cordova-common').events; +const CordovaError = require('cordova-common').CordovaError; PodsJson.FILENAME = 'pods.json'; +PodsJson.LIBRARY = 'libraries'; +PodsJson.SOURCE = 'sources'; +PodsJson.DECLARATION = 'declarations'; function PodsJson (podsJsonPath) { this.path = podsJsonPath; this.contents = null; this.__dirty = false; - var filename = this.path.split(path.sep).pop(); + const filename = this.path.split(path.sep).pop(); if (filename !== PodsJson.FILENAME) { throw new CordovaError(util.format('PodsJson: The file at %s is not `%s`.', this.path, PodsJson.FILENAME)); } @@ -43,25 +46,81 @@ function PodsJson (podsJsonPath) { } else { events.emit('verbose', 'pods.json found in platforms/ios'); // load contents - this.contents = fs.readFileSync(this.path, 'utf8'); - this.contents = JSON.parse(this.contents); + const contents = fs.readFileSync(this.path, 'utf8'); + this.contents = JSON.parse(contents); } + this.__updateFormatIfNecessary(); } -PodsJson.prototype.get = function (name) { - return this.contents[name]; +PodsJson.prototype.__isOldFormat = function () { + if (this.contents !== null) { + if (this.contents.declarations === undefined || + this.contents.sources === undefined || + this.contents.libraries === undefined) { + return true; + } + } + return false; }; -PodsJson.prototype.remove = function (name) { - if (this.contents[name]) { - delete this.contents[name]; +PodsJson.prototype.__updateFormatIfNecessary = function () { + if (this.__isOldFormat()) { + this.contents = { + declarations: {}, + sources: {}, + libraries: this.contents + }; this.__dirty = true; - events.emit('verbose', util.format('Remove from pods.json for `%s`', name)); + events.emit('verbose', 'Update format of pods.json'); } }; +PodsJson.prototype.getLibraries = function () { + return this.contents[PodsJson.LIBRARY]; +}; + +PodsJson.prototype.__get = function (kind, name) { + return this.contents[kind][name]; +}; + +PodsJson.prototype.getLibrary = function (name) { + return this.__get(PodsJson.LIBRARY, name); +}; + +PodsJson.prototype.getSource = function (name) { + return this.__get(PodsJson.SOURCE, name); +}; + +PodsJson.prototype.getDeclaration = function (name) { + return this.__get(PodsJson.DECLARATION, name); +}; + +PodsJson.prototype.__remove = function (kind, name) { + if (this.contents[kind][name]) { + delete this.contents[kind][name]; + this.__dirty = true; + events.emit('verbose', util.format('Remove from pods.json for `%s` - `%s`', name)); + } +}; + +PodsJson.prototype.removeLibrary = function (name) { + this.__remove(PodsJson.LIBRARY, name); +}; + +PodsJson.prototype.removeSource = function (name) { + this.__remove(PodsJson.SOURCE, name); +}; + +PodsJson.prototype.removeDeclaration = function (name) { + this.__remove(PodsJson.DECLARATION, name); +}; + PodsJson.prototype.clear = function () { - this.contents = {}; + this.contents = { + declarations: {}, + sources: {}, + libraries: {} + }; this.__dirty = true; }; @@ -78,34 +137,69 @@ PodsJson.prototype.write = function () { } }; -PodsJson.prototype.set = function (name, type, spec, count) { - this.setJson(name, { name: name, type: type, spec: spec, count: count }); -}; - -PodsJson.prototype.increment = function (name) { - var val = this.get(name); +PodsJson.prototype.__increment = function (kind, name) { + const val = this.__get(kind, name); if (val) { val.count++; - this.setJson(val); } }; -PodsJson.prototype.decrement = function (name) { - var val = this.get(name); +PodsJson.prototype.incrementLibrary = function (name) { + this.__increment(PodsJson.LIBRARY, name); +}; + +PodsJson.prototype.incrementSource = function (name) { + this.__increment(PodsJson.SOURCE, name); +}; + +PodsJson.prototype.incrementDeclaration = function (name) { + this.__increment(PodsJson.DECLARATION, name); +}; + +PodsJson.prototype.__decrement = function (kind, name) { + const val = this.__get(kind, name); if (val) { val.count--; if (val.count <= 0) { - this.remove(name); - } else { - this.setJson(val); + this.__remove(kind, name); } } }; -PodsJson.prototype.setJson = function (name, json) { - this.contents[name] = json; +PodsJson.prototype.decrementLibrary = function (name) { + this.__decrement(PodsJson.LIBRARY, name); +}; + +PodsJson.prototype.decrementSource = function (name) { + this.__decrement(PodsJson.SOURCE, name); +}; + +PodsJson.prototype.decrementDeclaration = function (name) { + this.__decrement(PodsJson.DECLARATION, name); +}; + +PodsJson.prototype.__setJson = function (kind, name, json) { + this.contents[kind][name] = Object.assign({}, json); this.__dirty = true; - events.emit('verbose', util.format('Set pods.json for `%s`', name)); + events.emit('verbose', util.format('Set pods.json for `%s` - `%s`', kind, name)); +}; + +// sample json for library +// { name: "Eureka", spec: "4.2.0", "swift-version": "4.1", count: 1 } +PodsJson.prototype.setJsonLibrary = function (name, json) { + this.__setJson(PodsJson.LIBRARY, name, json); +}; + +// sample json for source +// { source: "https://github.com/brightcove/BrightcoveSpecs.git", count: 1 } +PodsJson.prototype.setJsonSource = function (name, json) { + this.__setJson(PodsJson.SOURCE, name, json); +}; + +// sample json for declaration +// { declaration: ""} +PodsJson.prototype.setJsonDeclaration = function (name, json) { + this.__setJson(PodsJson.DECLARATION, name, json); }; PodsJson.prototype.isDirty = function () { diff --git a/StoneIsland/platforms/ios/cordova/lib/build.js b/StoneIsland/platforms/ios/cordova/lib/build.js index f51b084c..a7203875 100755..100644 --- a/StoneIsland/platforms/ios/cordova/lib/build.js +++ b/StoneIsland/platforms/ios/cordova/lib/build.js @@ -17,38 +17,51 @@ * under the License. */ -var Q = require('q'); -var path = require('path'); -var shell = require('shelljs'); -var spawn = require('./spawn'); -var fs = require('fs'); -var plist = require('plist'); -var util = require('util'); +const path = require('path'); +const which = require('which'); +const { + CordovaError, + events, + superspawn: { spawn } +} = require('cordova-common'); +const fs = require('fs-extra'); +const plist = require('plist'); +const util = require('util'); -var check_reqs = require('./check_reqs'); -var projectFile = require('./projectFile'); - -var events = require('cordova-common').events; - -var projectPath = path.join(__dirname, '..', '..'); -var projectName = null; +const check_reqs = require('./check_reqs'); +const projectFile = require('./projectFile'); // These are regular expressions to detect if the user is changing any of the built-in xcodebuildArgs /* eslint-disable no-useless-escape */ -var buildFlagMatchers = { - 'xcconfig': /^\-xcconfig\s*(.*)$/, - 'workspace': /^\-workspace\s*(.*)/, - 'scheme': /^\-scheme\s*(.*)/, - 'configuration': /^\-configuration\s*(.*)/, - 'sdk': /^\-sdk\s*(.*)/, - 'destination': /^\-destination\s*(.*)/, - 'archivePath': /^\-archivePath\s*(.*)/, - 'configuration_build_dir': /^(CONFIGURATION_BUILD_DIR=.*)/, - 'shared_precomps_dir': /^(SHARED_PRECOMPS_DIR=.*)/ +const buildFlagMatchers = { + workspace: /^\-workspace\s*(.*)/, + scheme: /^\-scheme\s*(.*)/, + configuration: /^\-configuration\s*(.*)/, + sdk: /^\-sdk\s*(.*)/, + destination: /^\-destination\s*(.*)/, + archivePath: /^\-archivePath\s*(.*)/, + configuration_build_dir: /^(CONFIGURATION_BUILD_DIR=.*)/, + shared_precomps_dir: /^(SHARED_PRECOMPS_DIR=.*)/ }; /* eslint-enable no-useless-escape */ /** + * Creates a project object (see projectFile.js/parseProjectFile) from + * a project path and name + * + * @param {*} projectPath + * @param {*} projectName + */ +function createProjectObject (projectPath, projectName) { + const locations = { + root: projectPath, + pbxproj: path.join(projectPath, `${projectName}.xcodeproj`, 'project.pbxproj') + }; + + return projectFile.parse(locations); +} + +/** * Returns a promise that resolves to the default simulator target; the logic here * matches what `cordova emulate ios` does. * @@ -58,13 +71,13 @@ var buildFlagMatchers = { * @return {Promise} */ function getDefaultSimulatorTarget () { - return require('./list-emulator-build-targets').run() - .then(function (emulators) { - var targetEmulator; + return require('./listEmulatorBuildTargets').run() + .then(emulators => { + let targetEmulator; if (emulators.length > 0) { targetEmulator = emulators[0]; } - emulators.forEach(function (emulator) { + emulators.forEach(emulator => { if (emulator.name.indexOf('iPhone') === 0) { targetEmulator = emulator; } @@ -73,120 +86,137 @@ function getDefaultSimulatorTarget () { }); } -module.exports.run = function (buildOpts) { - var emulatorTarget = ''; +module.exports.run = buildOpts => { + let emulatorTarget = ''; + const projectPath = path.join(__dirname, '..', '..'); + let projectName = ''; buildOpts = buildOpts || {}; if (buildOpts.debug && buildOpts.release) { - return Q.reject('Cannot specify "debug" and "release" options together.'); + return Promise.reject(new CordovaError('Cannot specify "debug" and "release" options together.')); } if (buildOpts.device && buildOpts.emulator) { - return Q.reject('Cannot specify "device" and "emulator" options together.'); + return Promise.reject(new CordovaError('Cannot specify "device" and "emulator" options together.')); } if (buildOpts.buildConfig) { if (!fs.existsSync(buildOpts.buildConfig)) { - return Q.reject('Build config file does not exist:' + buildOpts.buildConfig); + return Promise.reject(new CordovaError(`Build config file does not exist: ${buildOpts.buildConfig}`)); } - events.emit('log', 'Reading build config file:', path.resolve(buildOpts.buildConfig)); - var contents = fs.readFileSync(buildOpts.buildConfig, 'utf-8'); - var buildConfig = JSON.parse(contents.replace(/^\ufeff/, '')); // Remove BOM + events.emit('log', `Reading build config file: ${path.resolve(buildOpts.buildConfig)}`); + const contents = fs.readFileSync(buildOpts.buildConfig, 'utf-8'); + const buildConfig = JSON.parse(contents.replace(/^\ufeff/, '')); // Remove BOM if (buildConfig.ios) { - var buildType = buildOpts.release ? 'release' : 'debug'; - var config = buildConfig.ios[buildType]; + const buildType = buildOpts.release ? 'release' : 'debug'; + const config = buildConfig.ios[buildType]; if (config) { - ['codeSignIdentity', 'codeSignResourceRules', 'provisioningProfile', 'developmentTeam', 'packageType', 'buildFlag', 'iCloudContainerEnvironment'].forEach( - function (key) { + ['codeSignIdentity', 'codeSignResourceRules', 'provisioningProfile', 'developmentTeam', 'packageType', 'buildFlag', 'iCloudContainerEnvironment', 'automaticProvisioning'].forEach( + key => { buildOpts[key] = buildOpts[key] || config[key]; }); } } } - return require('./list-devices').run() - .then(function (devices) { + return require('./listDevices').run() + .then(devices => { if (devices.length > 0 && !(buildOpts.emulator)) { // we also explicitly set device flag in options as we pass // those parameters to other api (build as an example) buildOpts.device = true; return check_reqs.check_ios_deploy(); } - }).then(function () { + }).then(() => { // CB-12287: Determine the device we should target when building for a simulator if (!buildOpts.device) { - var newTarget = buildOpts.target || ''; + let newTarget = buildOpts.target || ''; if (newTarget) { // only grab the device name, not the runtime specifier newTarget = newTarget.split(',')[0]; } // a target was given to us, find the matching Xcode destination name - var promise = require('./list-emulator-build-targets').targetForSimIdentifier(newTarget); - return promise.then(function (theTarget) { + const promise = require('./listEmulatorBuildTargets').targetForSimIdentifier(newTarget); + return promise.then(theTarget => { if (!theTarget) { - return getDefaultSimulatorTarget().then(function (defaultTarget) { + return getDefaultSimulatorTarget().then(defaultTarget => { emulatorTarget = defaultTarget.name; - events.emit('log', 'Building for ' + emulatorTarget + ' Simulator'); + events.emit('warn', `No simulator found for "${newTarget}. Falling back to the default target.`); + events.emit('log', `Building for "${emulatorTarget}" Simulator (${defaultTarget.identifier}, ${defaultTarget.simIdentifier}).`); return emulatorTarget; }); } else { emulatorTarget = theTarget.name; - events.emit('log', 'Building for ' + emulatorTarget + ' Simulator'); + events.emit('log', `Building for "${emulatorTarget}" Simulator (${theTarget.identifier}, ${theTarget.simIdentifier}).`); return emulatorTarget; } }); } - }).then(function () { - return check_reqs.run(); - }).then(function () { - return findXCodeProjectIn(projectPath); - }).then(function (name) { + }) + .then(() => check_reqs.run()) + .then(() => findXCodeProjectIn(projectPath)) + .then(name => { projectName = name; - var extraConfig = ''; + let extraConfig = ''; if (buildOpts.codeSignIdentity) { - extraConfig += 'CODE_SIGN_IDENTITY = ' + buildOpts.codeSignIdentity + '\n'; - extraConfig += 'CODE_SIGN_IDENTITY[sdk=iphoneos*] = ' + buildOpts.codeSignIdentity + '\n'; + extraConfig += `CODE_SIGN_IDENTITY = ${buildOpts.codeSignIdentity}\n`; + extraConfig += `CODE_SIGN_IDENTITY[sdk=iphoneos*] = ${buildOpts.codeSignIdentity}\n`; } if (buildOpts.codeSignResourceRules) { - extraConfig += 'CODE_SIGN_RESOURCE_RULES_PATH = ' + buildOpts.codeSignResourceRules + '\n'; + extraConfig += `CODE_SIGN_RESOURCE_RULES_PATH = ${buildOpts.codeSignResourceRules}\n`; } if (buildOpts.provisioningProfile) { - extraConfig += 'PROVISIONING_PROFILE = ' + buildOpts.provisioningProfile + '\n'; + extraConfig += `PROVISIONING_PROFILE = ${buildOpts.provisioningProfile}\n`; } if (buildOpts.developmentTeam) { - extraConfig += 'DEVELOPMENT_TEAM = ' + buildOpts.developmentTeam + '\n'; + extraConfig += `DEVELOPMENT_TEAM = ${buildOpts.developmentTeam}\n`; } - return Q.nfcall(fs.writeFile, path.join(__dirname, '..', 'build-extras.xcconfig'), extraConfig, 'utf-8'); - }).then(function () { - var configuration = buildOpts.release ? 'Release' : 'Debug'; - events.emit('log', 'Building project: ' + path.join(projectPath, projectName + '.xcworkspace')); - events.emit('log', '\tConfiguration: ' + configuration); - events.emit('log', '\tPlatform: ' + (buildOpts.device ? 'device' : 'emulator')); + function writeCodeSignStyle (value) { + const project = createProjectObject(projectPath, projectName); - var buildOutputDir = path.join(projectPath, 'build', (buildOpts.device ? 'device' : 'emulator')); + events.emit('verbose', `Set CODE_SIGN_STYLE Build Property to ${value}.`); + project.xcode.updateBuildProperty('CODE_SIGN_STYLE', value); + events.emit('verbose', `Set ProvisioningStyle Target Attribute to ${value}.`); + project.xcode.addTargetAttribute('ProvisioningStyle', value); + + project.write(); + } + + if (buildOpts.provisioningProfile) { + events.emit('verbose', 'ProvisioningProfile build option set, changing project settings to Manual.'); + writeCodeSignStyle('Manual'); + } else if (buildOpts.automaticProvisioning) { + events.emit('verbose', 'ProvisioningProfile build option NOT set, changing project settings to Automatic.'); + writeCodeSignStyle('Automatic'); + } + + return fs.writeFile(path.join(__dirname, '..', 'build-extras.xcconfig'), extraConfig, 'utf-8'); + }).then(() => { + const configuration = buildOpts.release ? 'Release' : 'Debug'; + + events.emit('log', `Building project: ${path.join(projectPath, `${projectName}.xcworkspace`)}`); + events.emit('log', `\tConfiguration: ${configuration}`); + events.emit('log', `\tPlatform: ${buildOpts.device ? 'device' : 'emulator'}`); + events.emit('log', `\tTarget: ${emulatorTarget}`); + + const buildOutputDir = path.join(projectPath, 'build', (buildOpts.device ? 'device' : 'emulator')); // remove the build/device folder before building - return spawn('rm', [ '-rf', buildOutputDir ], projectPath) - .then(function () { - var xcodebuildArgs = getXcodeBuildArgs(projectName, projectPath, configuration, buildOpts.device, buildOpts.buildFlag, emulatorTarget); - return spawn('xcodebuild', xcodebuildArgs, projectPath); - }); + fs.removeSync(buildOutputDir); - }).then(function () { + const xcodebuildArgs = getXcodeBuildArgs(projectName, projectPath, configuration, buildOpts.device, buildOpts.buildFlag, emulatorTarget, buildOpts.automaticProvisioning); + return spawn('xcodebuild', xcodebuildArgs, { cwd: projectPath, printCommand: true, stdio: 'inherit' }); + }).then(() => { if (!buildOpts.device || buildOpts.noSign) { return; } - var locations = { - root: projectPath, - pbxproj: path.join(projectPath, projectName + '.xcodeproj', 'project.pbxproj') - }; - - var bundleIdentifier = projectFile.parse(locations).getPackageName(); - var exportOptions = {'compileBitcode': false, 'method': 'development'}; + const project = createProjectObject(projectPath, projectName); + const bundleIdentifier = project.getPackageName(); + const exportOptions = { compileBitcode: false, method: 'development' }; if (buildOpts.packageType) { exportOptions.method = buildOpts.packageType; @@ -201,7 +231,7 @@ module.exports.run = function (buildOpts) { } if (buildOpts.provisioningProfile && bundleIdentifier) { - exportOptions.provisioningProfiles = { [ bundleIdentifier ]: String(buildOpts.provisioningProfile) }; + exportOptions.provisioningProfiles = { [bundleIdentifier]: String(buildOpts.provisioningProfile) }; exportOptions.signingStyle = 'manual'; } @@ -209,13 +239,13 @@ module.exports.run = function (buildOpts) { exportOptions.signingCertificate = buildOpts.codeSignIdentity; } - var exportOptionsPlist = plist.build(exportOptions); - var exportOptionsPath = path.join(projectPath, 'exportOptions.plist'); + const exportOptionsPlist = plist.build(exportOptions); + const exportOptionsPath = path.join(projectPath, 'exportOptions.plist'); - var buildOutputDir = path.join(projectPath, 'build', 'device'); + const buildOutputDir = path.join(projectPath, 'build', 'device'); function checkSystemRuby () { - var ruby_cmd = shell.which('ruby'); + const ruby_cmd = which.sync('ruby', { nothrow: true }); if (ruby_cmd !== '/usr/bin/ruby') { events.emit('warn', 'Non-system Ruby in use. This may cause packaging to fail.\n' + @@ -225,11 +255,11 @@ module.exports.run = function (buildOpts) { } function packageArchive () { - var xcodearchiveArgs = getXcodeArchiveArgs(projectName, projectPath, buildOutputDir, exportOptionsPath); - return spawn('xcodebuild', xcodearchiveArgs, projectPath); + const xcodearchiveArgs = getXcodeArchiveArgs(projectName, projectPath, buildOutputDir, exportOptionsPath, buildOpts.automaticProvisioning); + return spawn('xcodebuild', xcodearchiveArgs, { cwd: projectPath, printCommand: true, stdio: 'inherit' }); } - return Q.nfcall(fs.writeFile, exportOptionsPath, exportOptionsPlist, 'utf-8') + return fs.writeFile(exportOptionsPath, exportOptionsPlist, 'utf-8') .then(checkSystemRuby) .then(packageArchive); }); @@ -242,20 +272,17 @@ module.exports.run = function (buildOpts) { */ function findXCodeProjectIn (projectPath) { // 'Searching for Xcode project in ' + projectPath); - var xcodeProjFiles = shell.ls(projectPath).filter(function (name) { - return path.extname(name) === '.xcodeproj'; - }); + const xcodeProjFiles = fs.readdirSync(projectPath).filter(name => path.extname(name) === '.xcodeproj'); if (xcodeProjFiles.length === 0) { - return Q.reject('No Xcode project found in ' + projectPath); + return Promise.reject(new CordovaError(`No Xcode project found in ${projectPath}`)); } if (xcodeProjFiles.length > 1) { - events.emit('warn', 'Found multiple .xcodeproj directories in \n' + - projectPath + '\nUsing first one'); + events.emit('warn', `Found multiple .xcodeproj directories in \n${projectPath}\nUsing first one`); } - var projectName = path.basename(xcodeProjFiles[0], '.xcodeproj'); - return Q.resolve(projectName); + const projectName = path.basename(xcodeProjFiles[0], '.xcodeproj'); + return Promise.resolve(projectName); } module.exports.findXCodeProjectIn = findXCodeProjectIn; @@ -268,21 +295,21 @@ module.exports.findXCodeProjectIn = findXCodeProjectIn; * @param {Boolean} isDevice Flag that specify target for package (device/emulator) * @param {Array} buildFlags * @param {String} emulatorTarget Target for emulator (rather than default) + * @param {Boolean} autoProvisioning Whether to allow Xcode to automatically update provisioning * @return {Array} Array of arguments that could be passed directly to spawn method */ -function getXcodeBuildArgs (projectName, projectPath, configuration, isDevice, buildFlags, emulatorTarget) { - var xcodebuildArgs; - var options; - var buildActions; - var settings; - var customArgs = {}; +function getXcodeBuildArgs (projectName, projectPath, configuration, isDevice, buildFlags, emulatorTarget, autoProvisioning) { + let options; + let buildActions; + let settings; + const customArgs = {}; customArgs.otherFlags = []; if (buildFlags) { if (typeof buildFlags === 'string' || buildFlags instanceof String) { parseBuildFlag(buildFlags, customArgs); } else { // buildFlags is an Array of strings - buildFlags.forEach(function (flag) { + buildFlags.forEach(flag => { parseBuildFlag(flag, customArgs); }); } @@ -290,36 +317,38 @@ function getXcodeBuildArgs (projectName, projectPath, configuration, isDevice, b if (isDevice) { options = [ - '-xcconfig', customArgs.xcconfig || path.join(__dirname, '..', 'build-' + configuration.toLowerCase() + '.xcconfig'), - '-workspace', customArgs.workspace || projectName + '.xcworkspace', + '-workspace', customArgs.workspace || `${projectName}.xcworkspace`, '-scheme', customArgs.scheme || projectName, '-configuration', customArgs.configuration || configuration, '-destination', customArgs.destination || 'generic/platform=iOS', - '-archivePath', customArgs.archivePath || projectName + '.xcarchive' + '-archivePath', customArgs.archivePath || `${projectName}.xcarchive` ]; - buildActions = [ 'archive' ]; + buildActions = ['archive']; settings = [ - customArgs.configuration_build_dir || 'CONFIGURATION_BUILD_DIR=' + path.join(projectPath, 'build', 'device'), - customArgs.shared_precomps_dir || 'SHARED_PRECOMPS_DIR=' + path.join(projectPath, 'build', 'sharedpch') + customArgs.configuration_build_dir || `CONFIGURATION_BUILD_DIR=${path.join(projectPath, 'build', 'device')}`, + customArgs.shared_precomps_dir || `SHARED_PRECOMPS_DIR=${path.join(projectPath, 'build', 'sharedpch')}` ]; // Add other matched flags to otherFlags to let xcodebuild present an appropriate error. // This is preferable to just ignoring the flags that the user has passed in. if (customArgs.sdk) { customArgs.otherFlags = customArgs.otherFlags.concat(['-sdk', customArgs.sdk]); } + + if (autoProvisioning) { + options = options.concat(['-allowProvisioningUpdates']); + } } else { // emulator options = [ - '-xcconfig', customArgs.xcconfig || path.join(__dirname, '..', 'build-' + configuration.toLowerCase() + '.xcconfig'), - '-workspace', customArgs.project || projectName + '.xcworkspace', + '-workspace', customArgs.project || `${projectName}.xcworkspace`, '-scheme', customArgs.scheme || projectName, '-configuration', customArgs.configuration || configuration, '-sdk', customArgs.sdk || 'iphonesimulator', - '-destination', customArgs.destination || 'platform=iOS Simulator,name=' + emulatorTarget + '-destination', customArgs.destination || `platform=iOS Simulator,name=${emulatorTarget}` ]; - buildActions = [ 'build' ]; + buildActions = ['build']; settings = [ - customArgs.configuration_build_dir || 'CONFIGURATION_BUILD_DIR=' + path.join(projectPath, 'build', 'emulator'), - customArgs.shared_precomps_dir || 'SHARED_PRECOMPS_DIR=' + path.join(projectPath, 'build', 'sharedpch') + customArgs.configuration_build_dir || `CONFIGURATION_BUILD_DIR=${path.join(projectPath, 'build', 'emulator')}`, + customArgs.shared_precomps_dir || `SHARED_PRECOMPS_DIR=${path.join(projectPath, 'build', 'sharedpch')}` ]; // Add other matched flags to otherFlags to let xcodebuild present an appropriate error. // This is preferable to just ignoring the flags that the user has passed in. @@ -327,8 +356,8 @@ function getXcodeBuildArgs (projectName, projectPath, configuration, isDevice, b customArgs.otherFlags = customArgs.otherFlags.concat(['-archivePath', customArgs.archivePath]); } } - xcodebuildArgs = options.concat(buildActions).concat(settings).concat(customArgs.otherFlags); - return xcodebuildArgs; + + return options.concat(buildActions).concat(settings).concat(customArgs.otherFlags); } /** @@ -337,21 +366,22 @@ function getXcodeBuildArgs (projectName, projectPath, configuration, isDevice, b * @param {String} projectPath Path to project file. Will be used to set CWD for xcodebuild * @param {String} outputPath Output directory to contain the IPA * @param {String} exportOptionsPath Path to the exportOptions.plist file + * @param {Boolean} autoProvisioning Whether to allow Xcode to automatically update provisioning * @return {Array} Array of arguments that could be passed directly to spawn method */ -function getXcodeArchiveArgs (projectName, projectPath, outputPath, exportOptionsPath) { +function getXcodeArchiveArgs (projectName, projectPath, outputPath, exportOptionsPath, autoProvisioning) { return [ '-exportArchive', - '-archivePath', projectName + '.xcarchive', + '-archivePath', `${projectName}.xcarchive`, '-exportOptionsPlist', exportOptionsPath, '-exportPath', outputPath - ]; + ].concat(autoProvisioning ? ['-allowProvisioningUpdates'] : []); } function parseBuildFlag (buildFlag, args) { - var matched; - for (var key in buildFlagMatchers) { - var found = buildFlag.match(buildFlagMatchers[key]); + let matched; + for (const key in buildFlagMatchers) { + const found = buildFlag.match(buildFlagMatchers[key]); if (found) { matched = true; // found[0] is the whole match, found[1] is the first match in parentheses. diff --git a/StoneIsland/platforms/ios/cordova/lib/check_reqs.js b/StoneIsland/platforms/ios/cordova/lib/check_reqs.js index 21a22081..33dc684f 100755..100644 --- a/StoneIsland/platforms/ios/cordova/lib/check_reqs.js +++ b/StoneIsland/platforms/ios/cordova/lib/check_reqs.js @@ -19,37 +19,28 @@ 'use strict'; -const Q = require('q'); -const shell = require('shelljs'); -const util = require('util'); +const which = require('which'); const versions = require('./versions'); +const { CordovaError } = require('cordova-common'); -const SUPPORTED_OS_PLATFORMS = [ 'darwin' ]; +const SUPPORTED_OS_PLATFORMS = ['darwin']; -const XCODEBUILD_MIN_VERSION = '7.0.0'; +const XCODEBUILD_MIN_VERSION = '9.0.0'; const XCODEBUILD_NOT_FOUND_MESSAGE = - 'Please install version ' + XCODEBUILD_MIN_VERSION + ' or greater from App Store'; + `Please install version ${XCODEBUILD_MIN_VERSION} or greater from App Store`; const IOS_DEPLOY_MIN_VERSION = '1.9.2'; const IOS_DEPLOY_NOT_FOUND_MESSAGE = - 'Please download, build and install version ' + IOS_DEPLOY_MIN_VERSION + ' or greater' + - ' from https://github.com/phonegap/ios-deploy into your path, or do \'npm install -g ios-deploy\''; + `Please download, build and install version ${IOS_DEPLOY_MIN_VERSION} or greater from https://github.com/ios-control/ios-deploy into your path, or do 'npm install -g ios-deploy'`; -const COCOAPODS_MIN_VERSION = '1.0.1'; -const COCOAPODS_NOT_FOUND_MESSAGE = - 'Please install version ' + COCOAPODS_MIN_VERSION + ' or greater from https://cocoapods.org/'; -const COCOAPODS_NOT_SYNCED_MESSAGE = - 'The CocoaPods repo has not been synced yet, this will take a long time (approximately 500MB as of Sept 2016). Please run `pod setup` first to sync the repo.'; -const COCOAPODS_SYNCED_MIN_SIZE = 475; // in megabytes -const COCOAPODS_SYNC_ERROR_MESSAGE = - 'The CocoaPods repo has been created, but there appears to be a sync error. The repo size should be at least ' + COCOAPODS_SYNCED_MIN_SIZE + '. Please run `pod setup --verbose` to sync the repo.'; -const COCOAPODS_REPO_NOT_FOUND_MESSAGE = 'The CocoaPods repo at ~/.cocoapods was not found.'; +const COCOAPODS_MIN_VERSION = '1.8.0'; +const COCOAPODS_NOT_FOUND_MESSAGE = `Please install version ${COCOAPODS_MIN_VERSION} or greater from https://cocoapods.org/`; /** * Checks if xcode util is available * @return {Promise} Returns a promise either resolved with xcode version or rejected */ -module.exports.run = module.exports.check_xcodebuild = function () { +module.exports.run = module.exports.check_xcodebuild = () => { return checkTool('xcodebuild', XCODEBUILD_MIN_VERSION, XCODEBUILD_NOT_FOUND_MESSAGE); }; @@ -57,81 +48,27 @@ module.exports.run = module.exports.check_xcodebuild = function () { * Checks if ios-deploy util is available * @return {Promise} Returns a promise either resolved with ios-deploy version or rejected */ -module.exports.check_ios_deploy = function () { +module.exports.check_ios_deploy = () => { return checkTool('ios-deploy', IOS_DEPLOY_MIN_VERSION, IOS_DEPLOY_NOT_FOUND_MESSAGE); }; -module.exports.check_os = function () { +module.exports.check_os = () => { // Build iOS apps available for OSX platform only, so we reject on others platforms - return os_platform_is_supported() ? - Q.resolve(process.platform) : - Q.reject('Cordova tooling for iOS requires Apple macOS'); + return os_platform_is_supported() + ? Promise.resolve(process.platform) + : Promise.reject(new CordovaError('Cordova tooling for iOS requires Apple macOS')); }; function os_platform_is_supported () { return (SUPPORTED_OS_PLATFORMS.indexOf(process.platform) !== -1); } -function check_cocoapod_tool (toolChecker) { - toolChecker = toolChecker || checkTool; - if (os_platform_is_supported()) { // CB-12856 - return toolChecker('pod', COCOAPODS_MIN_VERSION, COCOAPODS_NOT_FOUND_MESSAGE, 'CocoaPods'); - } else { - return Q.resolve({ - 'ignore': true, - 'ignoreMessage': `CocoaPods check and installation ignored on ${process.platform}` - }); - } -} - /** - * Checks if cocoapods repo size is what is expected + * Checks if cocoapods is available. * @return {Promise} Returns a promise either resolved or rejected */ -module.exports.check_cocoapods_repo_size = function () { - return check_cocoapod_tool() - .then(function (toolOptions) { - // check size of ~/.cocoapods repo - let commandString = util.format('du -sh %s/.cocoapods', process.env.HOME); - let command = shell.exec(commandString, { silent: true }); - // command.output is e.g "750M path/to/.cocoapods", we just scan the number - let size = toolOptions.ignore ? 0 : parseFloat(command.output); - - if (toolOptions.ignore || command.code === 0) { // success, parse output - return Q.resolve(size, toolOptions); - } else { // error, perhaps not found - return Q.reject(util.format('%s (%s)', COCOAPODS_REPO_NOT_FOUND_MESSAGE, command.output)); - } - }) - .then(function (repoSize, toolOptions) { - if (toolOptions.ignore || COCOAPODS_SYNCED_MIN_SIZE <= repoSize) { // success, expected size - return Q.resolve(toolOptions); - } else { - return Q.reject(COCOAPODS_SYNC_ERROR_MESSAGE); - } - }); -}; - -/** - * Checks if cocoapods is available, and whether the repo is synced (because it takes a long time to download) - * @return {Promise} Returns a promise either resolved or rejected - */ -module.exports.check_cocoapods = function (toolChecker) { - return check_cocoapod_tool(toolChecker) - // check whether the cocoapods repo has been synced through `pod repo` command - // a value of '0 repos' means it hasn't been synced - .then(function (toolOptions) { - let code = shell.exec('pod repo | grep -e "^0 repos"', { silent: true }).code; - let repoIsSynced = (code !== 0); - - if (toolOptions.ignore || repoIsSynced) { - // return check_cocoapods_repo_size(); - // we could check the repo size above, but it takes too long. - return Q.resolve(toolOptions); - } else { - return Q.reject(COCOAPODS_NOT_SYNCED_MESSAGE); - } - }); +module.exports.check_cocoapods = toolChecker => { + return checkTool('pod', COCOAPODS_MIN_VERSION, COCOAPODS_NOT_FOUND_MESSAGE, 'CocoaPods'); }; /** @@ -146,18 +83,17 @@ function checkTool (tool, minVersion, message, toolFriendlyName) { toolFriendlyName = toolFriendlyName || tool; // Check whether tool command is available at all - let tool_command = shell.which(tool); + const tool_command = which.sync(tool, { nothrow: true }); if (!tool_command) { - return Q.reject(toolFriendlyName + ' was not found. ' + (message || '')); + return Promise.reject(new CordovaError(`${toolFriendlyName} was not found. ${message || ''}`)); } // check if tool version is greater than specified one - return versions.get_tool_version(tool).then(function (version) { + return versions.get_tool_version(tool).then(version => { version = version.trim(); - return versions.compareVersions(version, minVersion) >= 0 ? - Q.resolve({ 'version': version }) : - Q.reject('Cordova needs ' + toolFriendlyName + ' version ' + minVersion + - ' or greater, you have version ' + version + '. ' + (message || '')); + return versions.compareVersions(version, minVersion) >= 0 + ? Promise.resolve({ version }) + : Promise.reject(new CordovaError(`Cordova needs ${toolFriendlyName} version ${minVersion} or greater, you have version ${version}. ${message || ''}`)); }); } @@ -168,7 +104,7 @@ function checkTool (tool, minVersion, message, toolFriendlyName) { * @param {Boolean} isFatal Marks the requirement as fatal. If such requirement will fail * next requirements' checks will be skipped. */ -let Requirement = function (id, name, isFatal) { +const Requirement = function (id, name, isFatal) { this.id = id; this.name = name; this.installed = false; @@ -182,8 +118,7 @@ let Requirement = function (id, name, isFatal) { * * @return Promise<Requirement[]> Array of requirements. Due to implementation, promise is always fulfilled. */ -module.exports.check_all = function () { - +module.exports.check_all = () => { const requirements = [ new Requirement('os', 'Apple macOS', true), new Requirement('xcode', 'Xcode'), @@ -191,10 +126,10 @@ module.exports.check_all = function () { new Requirement('CocoaPods', 'CocoaPods') ]; - let result = []; + const result = []; let fatalIsHit = false; - let checkFns = [ + const checkFns = [ module.exports.check_os, module.exports.check_xcodebuild, module.exports.check_ios_deploy, @@ -202,27 +137,25 @@ module.exports.check_all = function () { ]; // Then execute requirement checks one-by-one - return checkFns.reduce(function (promise, checkFn, idx) { - return promise.then(function () { + return checkFns.reduce((promise, checkFn, idx) => { + return promise.then(() => { // If fatal requirement is failed, // we don't need to check others - if (fatalIsHit) return Q(); + if (fatalIsHit) return Promise.resolve(); - let requirement = requirements[idx]; + const requirement = requirements[idx]; return checkFn() - .then(function (version) { + .then(version => { requirement.installed = true; requirement.metadata.version = version; result.push(requirement); - }, function (err) { + }, err => { if (requirement.isFatal) fatalIsHit = true; requirement.metadata.reason = err; result.push(requirement); }); }); - }, Q()) - .then(function () { - // When chain is completed, return requirements array to upstream API - return result; - }); + }, Promise.resolve()) + // When chain is completed, return requirements array to upstream API + .then(() => result); }; diff --git a/StoneIsland/platforms/ios/cordova/lib/clean.js b/StoneIsland/platforms/ios/cordova/lib/clean.js index 20e8ac66..72aac0ba 100755..100644 --- a/StoneIsland/platforms/ios/cordova/lib/clean.js +++ b/StoneIsland/platforms/ios/cordova/lib/clean.js @@ -17,26 +17,31 @@ * under the License. */ -var Q = require('q'); -var path = require('path'); -var shell = require('shelljs'); -var spawn = require('./spawn'); +const path = require('path'); +const fs = require('fs-extra'); +const { + CordovaError, + superspawn: { spawn } +} = require('cordova-common'); -var projectPath = path.join(__dirname, '..', '..'); +const projectPath = path.join(__dirname, '..', '..'); -module.exports.run = function () { - var projectName = shell.ls(projectPath).filter(function (name) { - return path.extname(name) === '.xcodeproj'; - })[0]; +module.exports.run = () => { + const projectName = fs.readdirSync(projectPath).filter(name => path.extname(name) === '.xcodeproj'); if (!projectName) { - return Q.reject('No Xcode project found in ' + projectPath); + return Promise.reject(new CordovaError(`No Xcode project found in ${projectPath}`)); } - return spawn('xcodebuild', ['-project', projectName, '-configuration', 'Debug', '-alltargets', 'clean'], projectPath) - .then(function () { - return spawn('xcodebuild', ['-project', projectName, '-configuration', 'Release', '-alltargets', 'clean'], projectPath); - }).then(function () { - return shell.rm('-rf', path.join(projectPath, 'build')); - }); + const xcodebuildClean = configName => { + return spawn( + 'xcodebuild', + ['-project', projectName, '-configuration', configName, '-alltargets', 'clean'], + { cwd: projectPath, printCommand: true, stdio: 'inherit' } + ); + }; + + return xcodebuildClean('Debug') + .then(() => xcodebuildClean('Release')) + .then(() => fs.removeSync(path.join(projectPath, 'build'))); }; diff --git a/StoneIsland/platforms/ios/cordova/lib/copy-www-build-step.js b/StoneIsland/platforms/ios/cordova/lib/copy-www-build-step.js deleted file mode 100755 index e05aacf2..00000000 --- a/StoneIsland/platforms/ios/cordova/lib/copy-www-build-step.js +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env node - -/* - 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. -*/ - -// This script copies the www directory into the Xcode project. - -// This script should not be called directly. -// It is called as a build step from Xcode. - -var BUILT_PRODUCTS_DIR = process.env.BUILT_PRODUCTS_DIR; -var FULL_PRODUCT_NAME = process.env.FULL_PRODUCT_NAME; -var COPY_HIDDEN = process.env.COPY_HIDDEN; -var PROJECT_FILE_PATH = process.env.PROJECT_FILE_PATH; - -var path = require('path'); -var fs = require('fs'); -var shell = require('shelljs'); -var srcDir = 'www'; -var dstDir = path.join(BUILT_PRODUCTS_DIR, FULL_PRODUCT_NAME); -var dstWwwDir = path.join(dstDir, 'www'); - -if (!BUILT_PRODUCTS_DIR) { - console.error('The script is meant to be run as an Xcode build step and relies on env variables set by Xcode.'); - process.exit(1); -} - -try { - fs.statSync(srcDir); -} catch (e) { - console.error('Path does not exist: ' + srcDir); - process.exit(2); -} - -// Code signing files must be removed or else there are -// resource signing errors. -shell.rm('-rf', dstWwwDir); -shell.rm('-rf', path.join(dstDir, '_CodeSignature')); -shell.rm('-rf', path.join(dstDir, 'PkgInfo')); -shell.rm('-rf', path.join(dstDir, 'embedded.mobileprovision')); - -// Copy www dir recursively -var code; -if (COPY_HIDDEN) { - code = shell.exec('rsync -Lra "' + srcDir + '" "' + dstDir + '"').code; -} else { - code = shell.exec('rsync -Lra --exclude="- .*" "' + srcDir + '" "' + dstDir + '"').code; -} - -if (code !== 0) { - console.error('Error occured on copying www. Code: ' + code); - process.exit(3); -} - -// Copy the config.xml file. -shell.cp('-f', path.join(path.dirname(PROJECT_FILE_PATH), path.basename(PROJECT_FILE_PATH, '.xcodeproj'), 'config.xml'), - dstDir); diff --git a/StoneIsland/platforms/ios/cordova/lib/list-devices b/StoneIsland/platforms/ios/cordova/lib/list-devices index 047d5950..5f89e21b 100755 --- a/StoneIsland/platforms/ios/cordova/lib/list-devices +++ b/StoneIsland/platforms/ios/cordova/lib/list-devices @@ -19,49 +19,10 @@ under the License. */ +const { run } = require('./listDevices'); -var Q = require('q'), - exec = require('child_process').exec; - -/** - * Gets list of connected iOS devices - * @return {Promise} Promise fulfilled with list of available iOS devices - */ -function listDevices() { - var commands = [ - Q.nfcall(exec, 'system_profiler SPUSBDataType | sed -n -e \'/iPad/,/Serial/p\' | grep "Serial Number:" | awk -F ": " \'{print $2 " iPad"}\''), - Q.nfcall(exec, 'system_profiler SPUSBDataType | sed -n -e \'/iPhone/,/Serial/p\' | grep "Serial Number:" | awk -F ": " \'{print $2 " iPhone"}\''), - Q.nfcall(exec, 'system_profiler SPUSBDataType | sed -n -e \'/iPod/,/Serial/p\' | grep "Serial Number:" | awk -F ": " \'{print $2 " iPod"}\'') - ]; - - // wrap al lexec calls into promises and wait until they're fullfilled - return Q.all(commands).then(function (results) { - var accumulator = []; - results.forEach(function (result) { - var devicefound; - // Each command promise resolves with array [stout, stderr], and we need stdout only - // Append stdout lines to accumulator - devicefound = result[0].trim().split('\n'); - if(devicefound && devicefound.length) { - devicefound.forEach(function(device) { - if (device) { - accumulator.push(device); - } - }); - } - }); - return accumulator; - }); -} - -exports.run = listDevices; - -// Check if module is started as separate script. -// If so, then invoke main method and print out results. -if (!module.parent) { - listDevices().then(function (devices) { - devices.forEach(function (device) { - console.log(device); - }); +run().then(devices => { + devices.forEach(device => { + console.log(device); }); -}
\ No newline at end of file +}); diff --git a/StoneIsland/platforms/ios/cordova/lib/list-emulator-build-targets b/StoneIsland/platforms/ios/cordova/lib/list-emulator-build-targets index ccf61f98..304c4c17 100755 --- a/StoneIsland/platforms/ios/cordova/lib/list-emulator-build-targets +++ b/StoneIsland/platforms/ios/cordova/lib/list-emulator-build-targets @@ -19,99 +19,8 @@ under the License. */ +const { run } = require('./listEmulatorBuildTargets'); -var Q = require('q'), - exec = require('child_process').exec; - -/** - * Returns a list of available simulator build targets of the form - * - * [ - * { name: <xcode-destination-name>, - * identifier: <simctl-identifier>, - * simIdentifier: <cordova emulate target> - * } - * ] - * - */ -function listEmulatorBuildTargets () { - return Q.nfcall(exec, 'xcrun simctl list --json') - .then(function(stdio) { - return JSON.parse(stdio[0]); - }) - .then(function(simInfo) { - var devices = simInfo.devices; - var deviceTypes = simInfo.devicetypes; - return deviceTypes.reduce(function (typeAcc, deviceType) { - if (!deviceType.name.match(/^[iPad|iPhone]/)) { - // ignore targets we don't support (like Apple Watch or Apple TV) - return typeAcc; - } - var availableDevices = Object.keys(devices).reduce(function (availAcc, deviceCategory) { - var availableDevicesInCategory = devices[deviceCategory]; - availableDevicesInCategory.forEach(function (device) { - if (device && device.name === deviceType.name.replace(/\-inch/g, ' inch') && device.isAvailable == true) { - availAcc.push(device); - } - }); - return availAcc; - }, []); - - // var availableDevices = Object.keys(devices).reduce(function (availAcc, deviceCategory) { - // var availableDevicesInCategory = devices[deviceCategory]; - // availableDevicesInCategory.forEach(function (device) { - // if (device.name === deviceType.name.replace(/\-inch/g, ' inch') && - // device.availability.toLowerCase().indexOf('unavailable') < 0) { - // availAcc.push(device); - // } - // }); - // return availAcc; - //}, []); - // we only want device types that have at least one available device - // (regardless of OS); this filters things out like iPhone 4s, which - // is present in deviceTypes, but probably not available on the user's - // system. - if (availableDevices.length > 0) { - typeAcc.push(deviceType); - } - return typeAcc; - }, []); - }) - .then(function(filteredTargets) { - // the simIdentifier, or cordova emulate target name, is the very last part - // of identifier. - return filteredTargets.map(function (target) { - var identifierPieces = target.identifier.split("."); - target.simIdentifier = identifierPieces[identifierPieces.length-1]; - return target; - }); - }); -} - -exports.run = listEmulatorBuildTargets; - -/** - * Given a simIdentifier, return the matching target. - * - * @param {string} simIdentifier a target, like "iPhone-SE" - * @return {Object} the matching target, or undefined if no match - */ -exports.targetForSimIdentifier = function(simIdentifier) { - return listEmulatorBuildTargets() - .then(function(targets) { - return targets.reduce(function(acc, target) { - if (!acc && target.simIdentifier.toLowerCase() === simIdentifier.toLowerCase()) { - acc = target; - } - return acc; - }, undefined); - }); -} - -// Check if module is started as separate script. -// If so, then invoke main method and print out results. -if (!module.parent) { - listEmulatorBuildTargets().then(function (targets) { - console.log(JSON.stringify(targets, null, 2)); - }); -} +run().then(targets => { + console.log(JSON.stringify(targets, null, 2)); +}); diff --git a/StoneIsland/platforms/ios/cordova/lib/list-emulator-images b/StoneIsland/platforms/ios/cordova/lib/list-emulator-images index d8be5766..db9b72af 100755 --- a/StoneIsland/platforms/ios/cordova/lib/list-emulator-images +++ b/StoneIsland/platforms/ios/cordova/lib/list-emulator-images @@ -19,29 +19,10 @@ under the License. */ +const { run } = require('./listEmulatorImages'); -var Q = require('q'), - iossim = require('ios-sim'), - exec = require('child_process').exec, - check_reqs = require('./check_reqs'); - -/** - * Gets list of iOS devices available for simulation - * @return {Promise} Promise fulfilled with list of devices available for simulation - */ -function listEmulatorImages () { - return Q.resolve(iossim.getdevicetypes()); -} - - -exports.run = listEmulatorImages; - -// Check if module is started as separate script. -// If so, then invoke main method and print out results. -if (!module.parent) { - listEmulatorImages().then(function (names) { - names.forEach(function (name) { - console.log(name); - }); +run().then(names => { + names.forEach(name => { + console.log(name); }); -} +}); diff --git a/StoneIsland/platforms/ios/cordova/lib/list-started-emulators b/StoneIsland/platforms/ios/cordova/lib/list-started-emulators index 710fa2f7..7f06cf2e 100755 --- a/StoneIsland/platforms/ios/cordova/lib/list-started-emulators +++ b/StoneIsland/platforms/ios/cordova/lib/list-started-emulators @@ -19,32 +19,10 @@ under the License. */ +const { run } = require('./listStartedEmulators'); -var Q = require('q'), - exec = require('child_process').exec; - -/** - * Gets list of running iOS simulators - * @return {Promise} Promise fulfilled with list of running iOS simulators - */ -function listStartedEmulators () { - // wrap exec call into promise - return Q.nfcall(exec, 'ps aux | grep -i "[i]OS Simulator"') - .then(function () { - return Q.nfcall(exec, 'defaults read com.apple.iphonesimulator "SimulateDevice"'); - }).then(function (stdio) { - return stdio[0].trim().split('\n'); - }); -} - -exports.run = listStartedEmulators; - -// Check if module is started as separate script. -// If so, then invoke main method and print out results. -if (!module.parent) { - listStartedEmulators().then(function (emulators) { - emulators.forEach(function (emulator) { - console.log(emulator); - }); +run().then(emulators => { + emulators.forEach(emulator => { + console.log(emulator); }); -} +}); diff --git a/StoneIsland/platforms/ios/cordova/lib/listDevices.js b/StoneIsland/platforms/ios/cordova/lib/listDevices.js new file mode 100644 index 00000000..86a67ffb --- /dev/null +++ b/StoneIsland/platforms/ios/cordova/lib/listDevices.js @@ -0,0 +1,42 @@ +/* + 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 { superspawn: { spawn } } = require('cordova-common'); + +const DEVICE_REGEX = /-o (iPhone|iPad|iPod)@.*?"USB Serial Number" = "([^"]*)"/gs; + +/** + * Gets list of connected iOS devices + * @return {Promise} Promise fulfilled with list of available iOS devices + */ +function listDevices () { + return spawn('ioreg', ['-p', 'IOUSB', '-l']) + .then(output => { + return [...matchAll(output, DEVICE_REGEX)] + .map(m => m.slice(1).reverse().join(' ')); + }); +} + +// TODO: Should be replaced with String#matchAll once available +function * matchAll (s, r) { + let match; + while ((match = r.exec(s))) yield match; +} + +exports.run = listDevices; diff --git a/StoneIsland/platforms/ios/cordova/lib/listEmulatorBuildTargets.js b/StoneIsland/platforms/ios/cordova/lib/listEmulatorBuildTargets.js new file mode 100644 index 00000000..73e35c05 --- /dev/null +++ b/StoneIsland/platforms/ios/cordova/lib/listEmulatorBuildTargets.js @@ -0,0 +1,95 @@ +/* + 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 { superspawn: { spawn } } = require('cordova-common'); + +/** + * Returns a list of available simulator build targets of the form + * + * [ + * { name: <xcode-destination-name>, + * identifier: <simctl-identifier>, + * simIdentifier: <cordova emulate target> + * } + * ] + * + */ +function listEmulatorBuildTargets () { + return spawn('xcrun', ['simctl', 'list', '--json']) + .then(output => JSON.parse(output)) + .then(function (simInfo) { + var devices = simInfo.devices; + var deviceTypes = simInfo.devicetypes; + return deviceTypes.reduce(function (typeAcc, deviceType) { + if (!deviceType.name.match(/^[iPad|iPhone]/)) { + // ignore targets we don't support (like Apple Watch or Apple TV) + return typeAcc; + } + var availableDevices = Object.keys(devices).reduce(function (availAcc, deviceCategory) { + var availableDevicesInCategory = devices[deviceCategory]; + availableDevicesInCategory.forEach(function (device) { + if (device.name === deviceType.name || device.name === deviceType.name.replace(/-inch/g, ' inch')) { + // Check new flag isAvailable (XCode 10.1+) or legacy string availability (XCode 10 and lower) + if (device.isAvailable || (device.availability && device.availability.toLowerCase().indexOf('unavailable') < 0)) { + availAcc.push(device); + } + } + }); + return availAcc; + }, []); + // we only want device types that have at least one available device + // (regardless of OS); this filters things out like iPhone 4s, which + // is present in deviceTypes, but probably not available on the user's + // system. + if (availableDevices.length > 0) { + typeAcc.push(deviceType); + } + return typeAcc; + }, []); + }) + .then(function (filteredTargets) { + // the simIdentifier, or cordova emulate target name, is the very last part + // of identifier. + return filteredTargets.map(function (target) { + var identifierPieces = target.identifier.split('.'); + target.simIdentifier = identifierPieces[identifierPieces.length - 1]; + return target; + }); + }); +} + +exports.run = listEmulatorBuildTargets; + +/** + * Given a simIdentifier, return the matching target. + * + * @param {string} simIdentifier a target, like "iPhone-SE" + * @return {Object} the matching target, or undefined if no match + */ +exports.targetForSimIdentifier = function (simIdentifier) { + return listEmulatorBuildTargets() + .then(function (targets) { + return targets.reduce(function (acc, target) { + if (!acc && target.simIdentifier.toLowerCase() === simIdentifier.toLowerCase()) { + acc = target; + } + return acc; + }, undefined); + }); +}; diff --git a/StoneIsland/platforms/ios/cordova/lib/listEmulatorImages.js b/StoneIsland/platforms/ios/cordova/lib/listEmulatorImages.js new file mode 100644 index 00000000..09e14340 --- /dev/null +++ b/StoneIsland/platforms/ios/cordova/lib/listEmulatorImages.js @@ -0,0 +1,30 @@ +/* + 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. +*/ + +var iossim = require('ios-sim'); + +/** + * Gets list of iOS devices available for simulation + * @return {Promise} Promise fulfilled with list of devices available for simulation + */ +function listEmulatorImages () { + return Promise.resolve(iossim.getdevicetypes()); +} + +exports.run = listEmulatorImages; diff --git a/StoneIsland/platforms/ios/cordova/lib/listStartedEmulators.js b/StoneIsland/platforms/ios/cordova/lib/listStartedEmulators.js new file mode 100644 index 00000000..ec1cb7f2 --- /dev/null +++ b/StoneIsland/platforms/ios/cordova/lib/listStartedEmulators.js @@ -0,0 +1,50 @@ +/* + 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 { superspawn: { spawn } } = require('cordova-common'); + +/** + * Gets list of running iOS simulators + * @return {Promise} Promise fulfilled with list of running iOS simulators + * + * @todo In the next PR, I will refactor this entire method. + * + * The process no longer contains the pattern "[i]OS Simulator". + * The process is now called "Simulator.app" + * + * Additionaly, `defaults read com.apple.iphonesimulator "SimulateDevice"` is also not valid aymore. + * + * I will replace this entire method to locate the active simulators though `simctl` + * + * Alternativly, remove this file. It is not documented in Cordova and not used anywhere in our code base. + */ +function listStartedEmulators () { + // wrap exec call into promise + return spawn('ps', ['aux']) + .then(output => { + if (output.match(/[i]OS Simulator/)) { + return spawn('defaults', ['read', 'com.apple.iphonesimulator', '"SimulateDevice"']); + } + + return ''; + }) + .then(output => output.split('\n')); +} + +exports.run = listStartedEmulators; diff --git a/StoneIsland/platforms/ios/cordova/lib/plugman/pluginHandlers.js b/StoneIsland/platforms/ios/cordova/lib/plugman/pluginHandlers.js index 1f6920fa..09d5ae51 100755..100644 --- a/StoneIsland/platforms/ios/cordova/lib/plugman/pluginHandlers.js +++ b/StoneIsland/platforms/ios/cordova/lib/plugman/pluginHandlers.js @@ -15,21 +15,20 @@ under the License. */ 'use strict'; -var fs = require('fs'); -var path = require('path'); -var shell = require('shelljs'); -var util = require('util'); -var events = require('cordova-common').events; -var CordovaError = require('cordova-common').CordovaError; +const fs = require('fs-extra'); +const path = require('path'); +const util = require('util'); +const events = require('cordova-common').events; +const CordovaError = require('cordova-common').CordovaError; // These frameworks are required by cordova-ios by default. We should never add/remove them. -var keep_these_frameworks = [ +const keep_these_frameworks = [ 'MobileCoreServices.framework', 'CoreGraphics.framework', 'AssetsLibrary.framework' ]; -var handlers = { +const handlers = { 'source-file': { install: function (obj, plugin, project, options) { installHelper('source-file', obj, plugin.dir, project.projectDir, plugin.id, options, project); @@ -48,47 +47,47 @@ var handlers = { }, 'resource-file': { install: function (obj, plugin, project, options) { - var src = obj.src; - var target = obj.target; - var srcFile = path.resolve(plugin.dir, src); + const src = obj.src; + let target = obj.target; + const srcFile = path.resolve(plugin.dir, src); if (!target) { target = path.basename(src); } - var destFile = path.resolve(project.resources_dir, target); + const destFile = path.resolve(project.resources_dir, target); if (!fs.existsSync(srcFile)) { - throw new CordovaError('Cannot find resource file "' + srcFile + '" for plugin ' + plugin.id + ' in iOS platform'); + throw new CordovaError(`Cannot find resource file "${srcFile}" for plugin ${plugin.id} in iOS platform`); } if (fs.existsSync(destFile)) { - throw new CordovaError('File already exists at destination "' + destFile + '" for resource file specified by plugin ' + plugin.id + ' in iOS platform'); + throw new CordovaError(`File already exists at destination "${destFile}" for resource file specified by plugin ${plugin.id} in iOS platform`); } project.xcode.addResourceFile(path.join('Resources', target)); - var link = !!(options && options.link); + const link = !!(options && options.link); copyFile(plugin.dir, src, project.projectDir, destFile, link); }, uninstall: function (obj, plugin, project, options) { - var src = obj.src; - var target = obj.target; + const src = obj.src; + let target = obj.target; if (!target) { target = path.basename(src); } - var destFile = path.resolve(project.resources_dir, target); + const destFile = path.resolve(project.resources_dir, target); project.xcode.removeResourceFile(path.join('Resources', target)); - shell.rm('-rf', destFile); + fs.removeSync(destFile); } }, - 'framework': { // CB-5238 custom frameworks only + framework: { // CB-5238 custom frameworks only install: function (obj, plugin, project, options) { - var src = obj.src; - var custom = !!(obj.custom); // convert to boolean (if truthy/falsy) - var embed = !!(obj.embed); // convert to boolean (if truthy/falsy) - var link = !embed; // either link or embed can be true, but not both. the other has to be false + const src = obj.src; + const custom = !!(obj.custom); // convert to boolean (if truthy/falsy) + const embed = !!(obj.embed); // convert to boolean (if truthy/falsy) + const link = !embed; // either link or embed can be true, but not both. the other has to be false if (!custom) { - var keepFrameworks = keep_these_frameworks; + const keepFrameworks = keep_these_frameworks; if (keepFrameworks.indexOf(src) < 0) { if (obj.type === 'podspec') { @@ -96,7 +95,7 @@ var handlers = { } else { project.frameworks[src] = project.frameworks[src] || 0; project.frameworks[src]++; - let opt = { customFramework: false, embed: false, link: true, weak: obj.weak }; + const opt = { customFramework: false, embed: false, link: true, weak: obj.weak }; events.emit('verbose', util.format('Adding non-custom framework to project... %s -> %s', src, JSON.stringify(opt))); project.xcode.addFramework(src, opt); events.emit('verbose', util.format('Non-custom framework added to project. %s -> %s', src, JSON.stringify(opt))); @@ -104,41 +103,32 @@ var handlers = { } return; } - var srcFile = path.resolve(plugin.dir, src); - var targetDir = path.resolve(project.plugins_dir, plugin.id, path.basename(src)); - if (!fs.existsSync(srcFile)) throw new CordovaError('Cannot find framework "' + srcFile + '" for plugin ' + plugin.id + ' in iOS platform'); - if (fs.existsSync(targetDir)) throw new CordovaError('Framework "' + targetDir + '" for plugin ' + plugin.id + ' already exists in iOS platform'); - var symlink = !!(options && options.link); + const srcFile = path.resolve(plugin.dir, src); + const targetDir = path.resolve(project.plugins_dir, plugin.id, path.basename(src)); + if (!fs.existsSync(srcFile)) throw new CordovaError(`Cannot find framework "${srcFile}" for plugin ${plugin.id} in iOS platform`); + if (fs.existsSync(targetDir)) throw new CordovaError(`Framework "${targetDir}" for plugin ${plugin.id} already exists in iOS platform`); + const symlink = !!(options && options.link); copyFile(plugin.dir, src, project.projectDir, targetDir, symlink); // frameworks are directories // CB-10773 translate back slashes to forward on win32 - var project_relative = fixPathSep(path.relative(project.projectDir, targetDir)); + const project_relative = fixPathSep(path.relative(project.projectDir, targetDir)); // CB-11233 create Embed Frameworks Build Phase if does not exist - var existsEmbedFrameworks = project.xcode.buildPhaseObject('PBXCopyFilesBuildPhase', 'Embed Frameworks'); + const existsEmbedFrameworks = project.xcode.buildPhaseObject('PBXCopyFilesBuildPhase', 'Embed Frameworks'); if (!existsEmbedFrameworks && embed) { events.emit('verbose', '"Embed Frameworks" Build Phase (Embedded Binaries) does not exist, creating it.'); project.xcode.addBuildPhase([], 'PBXCopyFilesBuildPhase', 'Embed Frameworks', null, 'frameworks'); } - let opt = { customFramework: true, embed: embed, link: link, sign: true }; + const opt = { customFramework: true, embed, link, sign: true }; events.emit('verbose', util.format('Adding custom framework to project... %s -> %s', src, JSON.stringify(opt))); project.xcode.addFramework(project_relative, opt); events.emit('verbose', util.format('Custom framework added to project. %s -> %s', src, JSON.stringify(opt))); }, uninstall: function (obj, plugin, project, options) { - var src = obj.src; + const src = obj.src; if (!obj.custom) { // CB-9825 cocoapod integration for plugins - var keepFrameworks = keep_these_frameworks; + const keepFrameworks = keep_these_frameworks; if (keepFrameworks.indexOf(src) < 0) { - if (obj.type === 'podspec') { - var podsJSON = require(path.join(project.projectDir, 'pods.json')); - if (podsJSON[src]) { - if (podsJSON[src].count > 1) { - podsJSON[src].count = podsJSON[src].count - 1; - } else { - delete podsJSON[src]; - } - } - } else { + if (obj.type !== 'podspec') { // this should be refactored project.frameworks[src] = project.frameworks[src] || 1; project.frameworks[src]--; @@ -153,12 +143,12 @@ var handlers = { return; } - var targetDir = fixPathSep(path.resolve(project.plugins_dir, plugin.id, path.basename(src))); - var pbxFile = project.xcode.removeFramework(targetDir, {customFramework: true}); + const targetDir = fixPathSep(path.resolve(project.plugins_dir, plugin.id, path.basename(src))); + const pbxFile = project.xcode.removeFramework(targetDir, { customFramework: true }); if (pbxFile) { project.xcode.removeFromPbxEmbedFrameworksBuildPhase(pbxFile); } - shell.rm('-rf', targetDir); + fs.removeSync(targetDir); } }, 'lib-file': { @@ -169,7 +159,7 @@ var handlers = { events.emit('verbose', '<lib-file> uninstall is not supported for iOS plugins'); } }, - 'asset': { + asset: { install: function (obj, plugin, project, options) { if (!obj.src) { throw new CordovaError(generateAttributeError('src', 'asset', plugin.id)); @@ -182,7 +172,7 @@ var handlers = { if (options && options.usePlatformWww) copyFile(plugin.dir, obj.src, project.platformWww, obj.target); }, uninstall: function (obj, plugin, project, options) { - var target = obj.target; + const target = obj.target; if (!target) { throw new CordovaError(generateAttributeError('target', 'asset', plugin.id)); @@ -199,58 +189,58 @@ var handlers = { 'js-module': { install: function (obj, plugin, project, options) { // Copy the plugin's files into the www directory. - var moduleSource = path.resolve(plugin.dir, obj.src); - var moduleName = plugin.id + '.' + (obj.name || path.basename(obj.src, path.extname(obj.src))); + const moduleSource = path.resolve(plugin.dir, obj.src); + const moduleName = `${plugin.id}.${obj.name || path.basename(obj.src, path.extname(obj.src))}`; // Read in the file, prepend the cordova.define, and write it back out. - var scriptContent = fs.readFileSync(moduleSource, 'utf-8').replace(/^\ufeff/, ''); // Window BOM + let scriptContent = fs.readFileSync(moduleSource, 'utf-8').replace(/^\ufeff/, ''); // Window BOM if (moduleSource.match(/.*\.json$/)) { - scriptContent = 'module.exports = ' + scriptContent; + scriptContent = `module.exports = ${scriptContent}`; } - scriptContent = 'cordova.define("' + moduleName + '", function(require, exports, module) {\n' + scriptContent + '\n});\n'; + scriptContent = `cordova.define("${moduleName}", function(require, exports, module) {\n${scriptContent}\n});\n`; - var moduleDestination = path.resolve(project.www, 'plugins', plugin.id, obj.src); - shell.mkdir('-p', path.dirname(moduleDestination)); + const moduleDestination = path.resolve(project.www, 'plugins', plugin.id, obj.src); + fs.ensureDirSync(path.dirname(moduleDestination)); fs.writeFileSync(moduleDestination, scriptContent, 'utf-8'); if (options && options.usePlatformWww) { - var platformWwwDestination = path.resolve(project.platformWww, 'plugins', plugin.id, obj.src); - shell.mkdir('-p', path.dirname(platformWwwDestination)); + const platformWwwDestination = path.resolve(project.platformWww, 'plugins', plugin.id, obj.src); + fs.ensureDirSync(path.dirname(platformWwwDestination)); fs.writeFileSync(platformWwwDestination, scriptContent, 'utf-8'); } }, uninstall: function (obj, plugin, project, options) { - var pluginRelativePath = path.join('plugins', plugin.id, obj.src); + const pluginRelativePath = path.join('plugins', plugin.id, obj.src); removeFileAndParents(project.www, pluginRelativePath); if (options && options.usePlatformWww) removeFileAndParents(project.platformWww, pluginRelativePath); } } }; -module.exports.getInstaller = function (type) { +module.exports.getInstaller = type => { if (handlers[type] && handlers[type].install) { return handlers[type].install; } - events.emit('warn', '<' + type + '> is not supported for iOS plugins'); + events.emit('warn', `<${type}> is not supported for iOS plugins`); }; -module.exports.getUninstaller = function (type) { +module.exports.getUninstaller = type => { if (handlers[type] && handlers[type].uninstall) { return handlers[type].uninstall; } - events.emit('warn', '<' + type + '> is not supported for iOS plugins'); + events.emit('warn', `<${type}> is not supported for iOS plugins`); }; function installHelper (type, obj, plugin_dir, project_dir, plugin_id, options, project) { - var srcFile = path.resolve(plugin_dir, obj.src); - var targetDir = path.resolve(project.plugins_dir, plugin_id, obj.targetDir || ''); - var destFile = path.join(targetDir, path.basename(obj.src)); + const srcFile = path.resolve(plugin_dir, obj.src); + const targetDir = path.resolve(project.plugins_dir, plugin_id, obj.targetDir || ''); + const destFile = path.join(targetDir, path.basename(obj.src)); - var project_ref; - var link = !!(options && options.link); + let project_ref; + const link = !!(options && options.link); if (link) { - var trueSrc = fs.realpathSync(srcFile); + const trueSrc = fs.realpathSync(srcFile); // Create a symlink in the expected place, so that uninstall can use it. if (options && options.force) { copyFile(plugin_dir, trueSrc, project_dir, destFile, link); @@ -261,101 +251,98 @@ function installHelper (type, obj, plugin_dir, project_dir, plugin_id, options, // Make the Xcode reference the file directly. // Note: Can't use path.join() here since it collapses 'Plugins/..', and xcode // library special-cases Plugins/ prefix. - project_ref = 'Plugins/' + fixPathSep(path.relative(fs.realpathSync(project.plugins_dir), trueSrc)); + project_ref = `Plugins/${fixPathSep(path.relative(fs.realpathSync(project.plugins_dir), trueSrc))}`; } else { if (options && options.force) { copyFile(plugin_dir, srcFile, project_dir, destFile, link); } else { copyNewFile(plugin_dir, srcFile, project_dir, destFile, link); } - project_ref = 'Plugins/' + fixPathSep(path.relative(project.plugins_dir, destFile)); + project_ref = `Plugins/${fixPathSep(path.relative(project.plugins_dir, destFile))}`; } if (type === 'header-file') { project.xcode.addHeaderFile(project_ref); } else if (obj.framework) { - var opt = { weak: obj.weak }; - var project_relative = path.join(path.basename(project.xcode_path), project_ref); + const opt = { weak: obj.weak }; + const project_relative = path.join(path.basename(project.xcode_path), project_ref); project.xcode.addFramework(project_relative, opt); - project.xcode.addToLibrarySearchPaths({path: project_ref}); + project.xcode.addToLibrarySearchPaths({ path: project_ref }); } else { - project.xcode.addSourceFile(project_ref, obj.compilerFlags ? {compilerFlags: obj.compilerFlags} : {}); + project.xcode.addSourceFile(project_ref, obj.compilerFlags ? { compilerFlags: obj.compilerFlags } : {}); } } function uninstallHelper (type, obj, project_dir, plugin_id, options, project) { - var targetDir = path.resolve(project.plugins_dir, plugin_id, obj.targetDir || ''); - var destFile = path.join(targetDir, path.basename(obj.src)); + const targetDir = path.resolve(project.plugins_dir, plugin_id, obj.targetDir || ''); + const destFile = path.join(targetDir, path.basename(obj.src)); - var project_ref; - var link = !!(options && options.link); + let project_ref; + const link = !!(options && options.link); if (link) { - var trueSrc = fs.readlinkSync(destFile); - project_ref = 'Plugins/' + fixPathSep(path.relative(fs.realpathSync(project.plugins_dir), trueSrc)); + const trueSrc = fs.readlinkSync(destFile); + project_ref = `Plugins/${fixPathSep(path.relative(fs.realpathSync(project.plugins_dir), trueSrc))}`; } else { - project_ref = 'Plugins/' + fixPathSep(path.relative(project.plugins_dir, destFile)); + project_ref = `Plugins/${fixPathSep(path.relative(project.plugins_dir, destFile))}`; } - shell.rm('-rf', targetDir); + fs.removeSync(targetDir); if (type === 'header-file') { project.xcode.removeHeaderFile(project_ref); } else if (obj.framework) { - var project_relative = path.join(path.basename(project.xcode_path), project_ref); + const project_relative = path.join(path.basename(project.xcode_path), project_ref); project.xcode.removeFramework(project_relative); - project.xcode.removeFromLibrarySearchPaths({path: project_ref}); + project.xcode.removeFromLibrarySearchPaths({ path: project_ref }); } else { project.xcode.removeSourceFile(project_ref); } } -var pathSepFix = new RegExp(path.sep.replace(/\\/, '\\\\'), 'g'); +const pathSepFix = new RegExp(path.sep.replace(/\\/, '\\\\'), 'g'); function fixPathSep (file) { return file.replace(pathSepFix, '/'); } function copyFile (plugin_dir, src, project_dir, dest, link) { src = path.resolve(plugin_dir, src); - if (!fs.existsSync(src)) throw new CordovaError('"' + src + '" not found!'); + if (!fs.existsSync(src)) throw new CordovaError(`"${src}" not found!`); // check that src path is inside plugin directory - var real_path = fs.realpathSync(src); - var real_plugin_path = fs.realpathSync(plugin_dir); - if (real_path.indexOf(real_plugin_path) !== 0) { throw new CordovaError('File "' + src + '" is located outside the plugin directory "' + plugin_dir + '"'); } + const real_path = fs.realpathSync(src); + const real_plugin_path = fs.realpathSync(plugin_dir); + if (real_path.indexOf(real_plugin_path) !== 0) { throw new CordovaError(`File "${src}" is located outside the plugin directory "${plugin_dir}"`); } dest = path.resolve(project_dir, dest); // check that dest path is located in project directory - if (dest.indexOf(project_dir) !== 0) { throw new CordovaError('Destination "' + dest + '" for source file "' + src + '" is located outside the project'); } + if (dest.indexOf(project_dir) !== 0) { throw new CordovaError(`Destination "${dest}" for source file "${src}" is located outside the project`); } - shell.mkdir('-p', path.dirname(dest)); + fs.ensureDirSync(path.dirname(dest)); if (link) { linkFileOrDirTree(src, dest); - } else if (fs.statSync(src).isDirectory()) { - // XXX shelljs decides to create a directory when -R|-r is used which sucks. http://goo.gl/nbsjq - shell.cp('-Rf', path.join(src, '/*'), dest); } else { - shell.cp('-f', src, dest); + fs.copySync(src, dest); } } // Same as copy file but throws error if target exists function copyNewFile (plugin_dir, src, project_dir, dest, link) { - var target_path = path.resolve(project_dir, dest); - if (fs.existsSync(target_path)) { throw new CordovaError('"' + target_path + '" already exists!'); } + const target_path = path.resolve(project_dir, dest); + if (fs.existsSync(target_path)) { throw new CordovaError(`"${target_path}" already exists!`); } copyFile(plugin_dir, src, project_dir, dest, !!link); } function linkFileOrDirTree (src, dest) { if (fs.existsSync(dest)) { - shell.rm('-Rf', dest); + fs.removeSync(dest); } if (fs.statSync(src).isDirectory()) { - shell.mkdir('-p', dest); - fs.readdirSync(src).forEach(function (entry) { + fs.ensureDirSync(dest); + fs.readdirSync(src).forEach(entry => { linkFileOrDirTree(path.join(src, entry), path.join(dest, entry)); }); } else { @@ -365,24 +352,24 @@ function linkFileOrDirTree (src, dest) { // checks if file exists and then deletes. Error if doesn't exist function removeFile (project_dir, src) { - var file = path.resolve(project_dir, src); - shell.rm('-Rf', file); + const file = path.resolve(project_dir, src); + fs.removeSync(file); } // deletes file/directory without checking function removeFileF (file) { - shell.rm('-Rf', file); + fs.removeSync(file); } function removeFileAndParents (baseDir, destFile, stopper) { stopper = stopper || '.'; - var file = path.resolve(baseDir, destFile); + const file = path.resolve(baseDir, destFile); if (!fs.existsSync(file)) return; removeFileF(file); // check if directory is empty - var curDir = path.dirname(file); + let curDir = path.dirname(file); while (curDir !== path.resolve(baseDir, stopper)) { if (fs.existsSync(curDir) && fs.readdirSync(curDir).length === 0) { @@ -396,5 +383,5 @@ function removeFileAndParents (baseDir, destFile, stopper) { } function generateAttributeError (attribute, element, id) { - return 'Required attribute "' + attribute + '" not specified in <' + element + '> element from plugin: ' + id; + return `Required attribute "${attribute}" not specified in <${element}> element from plugin: ${id}`; } diff --git a/StoneIsland/platforms/ios/cordova/lib/prepare.js b/StoneIsland/platforms/ios/cordova/lib/prepare.js index 17bbfeb7..c98cb8f4 100755..100644 --- a/StoneIsland/platforms/ios/cordova/lib/prepare.js +++ b/StoneIsland/platforms/ios/cordova/lib/prepare.js @@ -18,53 +18,46 @@ */ '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'); + +const fs = require('fs-extra'); +const path = require('path'); +const unorm = require('unorm'); +const plist = require('plist'); +const URL = require('url'); +const events = require('cordova-common').events; +const xmlHelpers = require('cordova-common').xmlHelpers; +const ConfigParser = require('cordova-common').ConfigParser; +const CordovaError = require('cordova-common').CordovaError; +const PlatformJson = require('cordova-common').PlatformJson; +const PlatformMunger = require('cordova-common').ConfigChanges.PlatformMunger; +const PluginInfoProvider = require('cordova-common').PluginInfoProvider; +const FileUpdater = require('cordova-common').FileUpdater; +const 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'; +const IMAGESET_COMPACT_SIZE_CLASS = 'compact'; +const 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()); + const platformJson = PlatformJson.load(this.locations.root, 'ios'); + const 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); + return updateWww(cordovaProject, this.locations) + // update project according to config.xml changes. + .then(() => updateProject(this._config, this.locations)) + .then(() => { + updateIcons(cordovaProject, this.locations); + updateLaunchStoryboardImages(cordovaProject, this.locations); + updateBackgroundColor(cordovaProject, this.locations); + updateFileResources(cordovaProject, this.locations); }) - .then(function () { - updateIcons(cordovaProject, self.locations); - updateSplashScreens(cordovaProject, self.locations); - updateLaunchStoryboardImages(cordovaProject, self.locations); - updateFileResources(cordovaProject, self.locations); + .then(() => { + alertDeprecatedPreference(this._config); }) - .then(function () { + .then(() => { events.emit('verbose', 'Prepared iOS project successfully'); }); }; @@ -74,22 +67,21 @@ module.exports.clean = function (options) { // 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'); + const projectRoot = path.resolve(this.root, '../..'); + const projectConfigFile = path.join(projectRoot, 'config.xml'); if ((options && options.noPrepare) || !fs.existsSync(projectConfigFile) || !fs.existsSync(this.locations.configXml)) { - return Q(); + return Promise.resolve(); } - var projectConfig = new ConfigParser(this.locations.configXml); + const 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); + return Promise.resolve().then(() => { + cleanWww(projectRoot, this.locations); + cleanIcons(projectRoot, projectConfig, this.locations); + cleanLaunchStoryboardImages(projectRoot, projectConfig, this.locations); + cleanBackgroundColor(projectRoot, projectConfig, this.locations); + cleanFileResources(projectRoot, projectConfig, this.locations); }); }; @@ -108,11 +100,11 @@ module.exports.clean = function (options) { * 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); + 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); + fs.copySync(locations.defaultConfigXml, locations.configXml); // Then apply config changes from global munge to all config files // in project (including project's config) @@ -120,7 +112,7 @@ function updateConfigFile (sourceConfig, configMunger, locations) { 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); + const config = new ConfigParser(locations.configXml); xmlHelpers.mergeXml(sourceConfig.doc.getroot(), config.doc.getroot(), 'ios', /* clobber= */true); @@ -132,7 +124,7 @@ function updateConfigFile (sourceConfig, configMunger, locations) { * Logs all file operations via the verbose event stream, indented. */ function logFileOp (message) { - events.emit('verbose', ' ' + message); + events.emit('verbose', ` ${message}`); } /** @@ -145,31 +137,33 @@ function logFileOp (message) { * paths for www files. */ function updateWww (cordovaProject, destinations) { - var sourceDirs = [ + const 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'); + const 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); + const targetDir = path.relative(cordovaProject.root, destinations.www); events.emit( - 'verbose', 'Merging and updating files from [' + sourceDirs.join(', ') + '] to ' + targetDir); + 'verbose', `Merging and updating files from [${sourceDirs.join(', ')}] to ${targetDir}`); FileUpdater.mergeAndUpdateDir( sourceDirs, targetDir, { rootDir: cordovaProject.root }, logFileOp); + + return Promise.resolve(); } /** * Cleans all files from the platform 'www' directory. */ function cleanWww (projectRoot, locations) { - var targetDir = path.relative(projectRoot, locations.www); - events.emit('verbose', 'Cleaning ' + targetDir); + const 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( @@ -184,169 +178,176 @@ function cleanWww (projectRoot, locations) { * @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(); + const name = unorm.nfd(platformConfig.name()); + const version = platformConfig.version(); + const displayName = platformConfig.shortName && platformConfig.shortName(); - var originalName = path.basename(locations.xcodeCordovaProj); + const 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; + const plistFile = path.join(locations.xcodeCordovaProj, `${originalName}-Info.plist`); + const infoPlist = plist.parse(fs.readFileSync(plistFile, 'utf8')); // Update version (bundle version) - infoPlist['CFBundleShortVersionString'] = version; - var CFBundleVersion = platformConfig.getAttribute('ios-CFBundleVersion') || default_CFBundleVersion(version); - infoPlist['CFBundleVersion'] = CFBundleVersion; + infoPlist.CFBundleShortVersionString = version; + const CFBundleVersion = platformConfig.getAttribute('ios-CFBundleVersion') || default_CFBundleVersion(version); + infoPlist.CFBundleVersion = CFBundleVersion; if (platformConfig.getAttribute('defaultlocale')) { - infoPlist['CFBundleDevelopmentRegion'] = platformConfig.getAttribute('defaultlocale'); + infoPlist.CFBundleDevelopmentRegion = platformConfig.getAttribute('defaultlocale'); } if (displayName) { - infoPlist['CFBundleDisplayName'] = displayName; + infoPlist.CFBundleDisplayName = displayName; } // replace Info.plist ATS entries according to <access> and <allow-navigation> config.xml entries - var ats = writeATSEntries(platformConfig); + const ats = writeATSEntries(platformConfig); if (Object.keys(ats).length > 0) { - infoPlist['NSAppTransportSecurity'] = ats; + infoPlist.NSAppTransportSecurity = ats; } else { - delete infoPlist['NSAppTransportSecurity']; + delete infoPlist.NSAppTransportSecurity; } handleOrientationSettings(platformConfig, infoPlist); - updateProjectPlistForLaunchStoryboard(platformConfig, infoPlist); - var info_contents = plist.build(infoPlist); + /* eslint-disable no-tabs */ + // Write out the plist file with the same formatting as Xcode does + let info_contents = plist.build(infoPlist, { indent: '\t', offset: -1 }); + /* eslint-enable no-tabs */ + 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); + events.emit('verbose', `Wrote out iOS Bundle Version "${version}" to ${plistFile}`); - return handleBuildSettings(platformConfig, locations, infoPlist).then(function () { + return handleBuildSettings(platformConfig, locations, infoPlist).then(() => { if (name === originalName) { - events.emit('verbose', 'iOS Product Name has not changed (still "' + originalName + '")'); - return Q(); + events.emit('verbose', `iOS Product Name has not changed (still "${originalName}")`); + return Promise.resolve(); } else { // CB-11712 <name> was changed, we don't support it' - var errorString = + const 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' - ; + '\tcordova platform add ios\n'; - return Q.reject(new CordovaError(errorString)); + return Promise.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' ]; + 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' ]; + 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' ]; + 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']; + 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); + const pkg = platformConfig.getAttribute('ios-CFBundleIdentifier') || platformConfig.packageName(); + const targetDevice = parseTargetDevicePreference(platformConfig.getPreference('target-device', 'ios')); + const deploymentTarget = platformConfig.getPreference('deployment-target', 'ios'); + const swiftVersion = platformConfig.getPreference('SwiftVersion', 'ios'); + + let project; + + try { + project = projectFile.parse(locations); + } catch (err) { + return Promise.reject(new CordovaError(`Could not parse ${locations.pbxproj}: ${err}`)); + } + + const origPkg = project.xcode.getBuildProperty('PRODUCT_BUNDLE_IDENTIFIER', undefined, platformConfig.name()); // 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(); + if (origPkg === pkg && !targetDevice && !deploymentTarget && !swiftVersion) { + return Promise.resolve(); } - 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 (origPkg !== pkg) { + events.emit('verbose', `Set PRODUCT_BUNDLE_IDENTIFIER to ${pkg}.`); + project.xcode.updateBuildProperty('PRODUCT_BUNDLE_IDENTIFIER', pkg, null, platformConfig.name()); } if (targetDevice) { - events.emit('verbose', 'Set TARGETED_DEVICE_FAMILY to ' + targetDevice + '.'); - proj.updateBuildProperty('TARGETED_DEVICE_FAMILY', targetDevice); + events.emit('verbose', `Set TARGETED_DEVICE_FAMILY to ${targetDevice}.`); + project.xcode.updateBuildProperty('TARGETED_DEVICE_FAMILY', targetDevice); } if (deploymentTarget) { - events.emit('verbose', 'Set IPHONEOS_DEPLOYMENT_TARGET to "' + deploymentTarget + '".'); - proj.updateBuildProperty('IPHONEOS_DEPLOYMENT_TARGET', deploymentTarget); + events.emit('verbose', `Set IPHONEOS_DEPLOYMENT_TARGET to "${deploymentTarget}".`); + project.xcode.updateBuildProperty('IPHONEOS_DEPLOYMENT_TARGET', deploymentTarget); } - updateBuildSettingsForLaunchStoryboard(proj, platformConfig, infoPlist); + if (swiftVersion) { + events.emit('verbose', `Set SwiftVersion to "${swiftVersion}".`); + project.xcode.updateBuildProperty('SWIFT_VERSION', swiftVersion); + } - fs.writeFileSync(locations.pbxproj, proj.writeSync(), 'utf-8'); + project.write(); - return Q(); + return Promise.resolve(); } 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-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} + const 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-29.png', width: 29, height: 29 }, + { dest: 'icon-29@2x.png', width: 58, height: 58 }, + { dest: 'icon-29@3x.png', width: 87, height: 87 }, + { dest: 'icon.png', width: 57, height: 57 }, + { dest: 'icon@2x.png', width: 114, height: 114 }, + { dest: 'icon-24@2x.png', width: 48, height: 48 }, + { dest: 'icon-27.5@2x.png', width: 55, height: 55 }, + { dest: 'icon-44@2x.png', width: 88, height: 88 }, + { dest: 'icon-86@2x.png', width: 172, height: 172 }, + { dest: 'icon-98@2x.png', width: 196, height: 196 } ]; - var pathMap = {}; - platformIcons.forEach(function (item) { - var icon = icons.getBySize(item.width, item.height) || icons.getDefault(); + const pathMap = {}; + platformIcons.forEach(item => { + const icon = icons.getBySize(item.width, item.height) || icons.getDefault(); if (icon) { - var target = path.join(iconsDir, item.dest); + const target = path.join(iconsDir, item.dest); pathMap[target] = icon.src; } }); @@ -354,8 +355,8 @@ function mapIconResources (icons, iconsDir) { } function getIconsDir (projectRoot, platformProjDir) { - var iconsDir; - var xcassetsExists = folderExists(path.join(projectRoot, platformProjDir, 'Images.xcassets/')); + let iconsDir; + const xcassetsExists = folderExists(path.join(projectRoot, platformProjDir, 'Images.xcassets/')); if (xcassetsExists) { iconsDir = path.join(platformProjDir, 'Images.xcassets/AppIcon.appiconset/'); @@ -367,31 +368,31 @@ function getIconsDir (projectRoot, platformProjDir) { } function updateIcons (cordovaProject, locations) { - var icons = cordovaProject.projectConfig.getIcons('ios'); + const 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); + const platformProjDir = path.relative(cordovaProject.root, locations.xcodeCordovaProj); + const iconsDir = getIconsDir(cordovaProject.root, platformProjDir); + const 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'); + const 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) { + const platformProjDir = path.relative(projectRoot, locations.xcodeCordovaProj); + const iconsDir = getIconsDir(projectRoot, platformProjDir); + const resourceMap = mapIconResources(icons, iconsDir); + Object.keys(resourceMap).forEach(targetIconPath => { resourceMap[targetIconPath] = null; }); - events.emit('verbose', 'Cleaning icons at ' + iconsDir); + events.emit('verbose', `Cleaning icons at ${iconsDir}`); // Source paths are removed from the map, so updatePaths() will delete the target files. FileUpdater.updatePaths( @@ -399,74 +400,120 @@ function cleanIcons (projectRoot, projectConfig, locations) { } } -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} - ]; - - 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; +/** + * Returns the directory for the BackgroundColor.colorset asset, or null if no + * xcassets exist. + * + * @param {string} projectRoot The project's root directory + * @param {string} platformProjDir The platform's project directory + */ +function getBackgroundColorDir (projectRoot, platformProjDir) { + if (folderExists(path.join(projectRoot, platformProjDir, 'Images.xcassets/'))) { + return path.join(platformProjDir, 'Images.xcassets', 'BackgroundColor.colorset'); + } else { + return null; + } } -function getSplashScreensDir (projectRoot, platformProjDir) { - var splashScreensDir; - var xcassetsExists = folderExists(path.join(projectRoot, platformProjDir, 'Images.xcassets/')); +function colorPreferenceToComponents (pref) { + if (!pref || !pref.match(/^(#[0-9A-F]{3}|(0x|#)([0-9A-F]{2})?[0-9A-F]{6})$/)) { + return { + platform: 'ios', + reference: 'systemBackgroundColor' + }; + } - if (xcassetsExists) { - splashScreensDir = path.join(platformProjDir, 'Images.xcassets/LaunchImage.launchimage/'); - } else { - splashScreensDir = path.join(platformProjDir, 'Resources/splash/'); + let red = 'FF'; + let green = 'FF'; + let blue = 'FF'; + let alpha = 1.0; + + if (pref[0] === '#' && pref.length === 4) { + red = pref[1] + pref[1]; + green = pref[2] + pref[2]; + blue = pref[3] + pref[3]; } - return splashScreensDir; -} + if (pref.length >= 7 && (pref[0] === '#' || pref.substring(0, 2) === '0x')) { + let offset = pref[0] === '#' ? 1 : 2; -function updateSplashScreens (cordovaProject, locations) { - var splashScreens = cordovaProject.projectConfig.getSplashScreens('ios'); + if (pref.substring(offset).length === 8) { + alpha = parseInt(pref.substring(offset, offset + 2), 16) / 255.0; + offset += 2; + } - if (splashScreens.length === 0) { - events.emit('verbose', 'This app does not have splash screens defined'); - return; + red = pref.substring(offset, offset + 2); + green = pref.substring(offset + 2, offset + 4); + blue = pref.substring(offset + 4, offset + 6); } - 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); + return { + 'color-space': 'srgb', + components: { + red: '0x' + red, + green: '0x' + green, + blue: '0x' + blue, + alpha: alpha.toFixed(3) + } + }; } -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); +/** + * Update the background color Contents.json in xcassets. + * + * @param {Object} cordovaProject The cordova project + * @param {Object} locations A dictionary containing useful location paths + */ +function updateBackgroundColor (cordovaProject, locations) { + const pref = cordovaProject.projectConfig.getPreference('BackgroundColor', 'ios') || ''; - // Source paths are removed from the map, so updatePaths() will delete the target files. - FileUpdater.updatePaths( - resourceMap, { rootDir: projectRoot, all: true }, logFileOp); + const platformProjDir = path.relative(cordovaProject.root, locations.xcodeCordovaProj); + const backgroundColorDir = getBackgroundColorDir(cordovaProject.root, platformProjDir); + + if (backgroundColorDir) { + const contentsJSON = { + colors: [{ + idiom: 'universal', + color: colorPreferenceToComponents(pref) + }], + info: { + author: 'Xcode', + version: 1 + } + }; + + events.emit('verbose', 'Updating Background Color color set Contents.json'); + fs.writeFileSync(path.join(cordovaProject.root, backgroundColorDir, 'Contents.json'), + JSON.stringify(contentsJSON, null, 2)); + } +} + +/** + * Resets the background color Contents.json in xcassets to default. + * + * @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 cleanBackgroundColor (projectRoot, projectConfig, locations) { + const platformProjDir = path.relative(projectRoot, locations.xcodeCordovaProj); + const backgroundColorDir = getBackgroundColorDir(projectRoot, platformProjDir); + + if (backgroundColorDir) { + const contentsJSON = { + colors: [{ + idiom: 'universal', + color: colorPreferenceToComponents(null) + }], + info: { + author: 'Xcode', + version: 1 + } + }; + + events.emit('verbose', 'Cleaning Background Color color set Contents.json'); + fs.writeFileSync(path.join(projectRoot, backgroundColorDir, 'Contents.json'), + JSON.stringify(contentsJSON, null, 2)); } } @@ -482,9 +529,9 @@ function updateFileResources (cordovaProject, locations) { return; } - let resourceMap = {}; - files.forEach(function (res) { - let src = res.src; + const resourceMap = {}; + files.forEach(res => { + const src = res.src; let target = res.target; if (!target) { @@ -494,29 +541,75 @@ function updateFileResources (cordovaProject, locations) { let targetPath = path.join(project.resources_dir, target); targetPath = path.relative(cordovaProject.root, targetPath); - project.xcode.addResourceFile(target); + if (!fs.existsSync(targetPath)) { + project.xcode.addResourceFile(target); + } else { + events.emit('warn', `Overwriting existing resource file at ${targetPath}`); + } resourceMap[targetPath] = src; }); - events.emit('verbose', 'Updating resource files at ' + platformDir); + events.emit('verbose', `Updating resource files at ${platformDir}`); FileUpdater.updatePaths( resourceMap, { rootDir: cordovaProject.root }, logFileOp); project.write(); } +function alertDeprecatedPreference (configParser) { + const deprecatedToNewPreferences = { + MediaPlaybackRequiresUserAction: { + newPreference: 'MediaTypesRequiringUserActionForPlayback', + isDeprecated: true + }, + MediaPlaybackAllowsAirPlay: { + newPreference: 'AllowsAirPlayForMediaPlayback', + isDeprecated: false + } + }; + + Object.keys(deprecatedToNewPreferences).forEach(oldKey => { + if (configParser.getPreference(oldKey)) { + const isDeprecated = deprecatedToNewPreferences[oldKey].isDeprecated; + const verb = isDeprecated ? 'has been' : 'is being'; + const newPreferenceKey = deprecatedToNewPreferences[oldKey].newPreference; + + // Create the Log Message + const log = [`The preference name "${oldKey}" ${verb} deprecated.`]; + if (newPreferenceKey) { + log.push(`It is recommended to replace this preference with "${newPreferenceKey}."`); + } else { + log.push('There is no replacement for this preference.'); + } + + /** + * If the preference has been deprecated, the usage of the old preference is no longer used. + * Therefore, the following line is not appended. It is added only if the old preference is still used. + * We are only keeping the top lines for deprecated items only for an additional major release when + * the pre-warning was not provided in a past major release due to a necessary quick deprecation. + * Typically caused by implementation nature or third-party requirement changes. + */ + if (!isDeprecated) { + log.push('Please note that this preference will be removed in the near future.'); + } + + events.emit('warn', log.join(' ')); + } + }); +} + 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); + events.emit('verbose', `Cleaning resource files at ${platformDir}`); const project = projectFile.parse(locations); - var resourceMap = {}; - files.forEach(function (res) { - let src = res.src; + const resourceMap = {}; + files.forEach(res => { + const src = res.src; let target = res.target; if (!target) { @@ -532,7 +625,7 @@ function cleanFileResources (projectRoot, projectConfig, locations) { }); FileUpdater.updatePaths( - resourceMap, {rootDir: projectRoot, all: true}, logFileOp); + resourceMap, { rootDir: projectRoot, all: true }, logFileOp); project.write(); } @@ -555,7 +648,8 @@ function cleanFileResources (projectRoot, projectConfig, locations) { * 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' + * target: undefined|'path/to/asset/library/Default@scale~idiom~widthheight.png', + * appearence: undefined|'dark'|'light' * }, ... * ] * @@ -564,51 +658,54 @@ function cleanFileResources (projectRoot, projectConfig, locations) { * @return {Array<Object>} */ function mapLaunchStoryboardContents (splashScreens, launchStoryboardImagesDir) { - var platformLaunchStoryboardImages = []; - var idioms = ['universal', 'ipad', 'iphone']; - var scalesForIdiom = { + const platformLaunchStoryboardImages = []; + const idioms = ['universal', 'ipad', 'iphone']; + const scalesForIdiom = { universal: ['1x', '2x', '3x'], ipad: ['1x', '2x'], iphone: ['1x', '2x', '3x'] }; - var sizes = ['com', 'any']; + const sizes = ['com', 'any']; + const appearences = ['', 'dark', 'light']; - 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 - }; + idioms.forEach(idiom => { + scalesForIdiom[idiom].forEach(scale => { + sizes.forEach(width => { + sizes.forEach(height => { + appearences.forEach(appearence => { + const item = { idiom, scale, width, 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; + if (appearence !== '') { + item.appearence = appearence; + } - /* 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); + /* examples of the search pattern: + * scale ~ idiom ~ width height ~ appearence + * @2x ~ universal ~ any any + * @3x ~ iphone ~ com any ~ dark + * @2x ~ ipad ~ com any ~ light + */ + const searchPattern = '@' + scale + '~' + idiom + '~' + width + height + (appearence ? '~' + appearence : ''); - if (launchStoryboardImage) { - item.filename = 'Default' + searchPattern + '.png'; - item.src = launchStoryboardImage.src; - item.target = path.join(launchStoryboardImagesDir, item.filename); - } + /* 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) ? (appearence !== '' ? true : ((item.src.indexOf(searchPattern + '~light') >= 0 || (item.src.indexOf(searchPattern + '~dark') >= 0)) ? false : true)) : false; + * }); + */ + const launchStoryboardImage = splashScreens.reduce( + (p, c) => (c.src.indexOf(searchPattern) >= 0) ? (appearence !== '' ? c : ((c.src.indexOf(searchPattern + '~light') >= 0 || (c.src.indexOf(searchPattern + '~dark') >= 0)) ? p : c)) : p, + undefined + ); - platformLaunchStoryboardImages.push(item); + if (launchStoryboardImage) { + item.filename = `Default${searchPattern}.png`; + item.src = launchStoryboardImage.src; + item.target = path.join(launchStoryboardImagesDir, item.filename); + } + + platformLaunchStoryboardImages.push(item); + }); }); }); }); @@ -632,9 +729,9 @@ function mapLaunchStoryboardContents (splashScreens, launchStoryboardImagesDir) * @return {Object} */ function mapLaunchStoryboardResources (splashScreens, launchStoryboardImagesDir) { - var platformLaunchStoryboardImages = mapLaunchStoryboardContents(splashScreens, launchStoryboardImagesDir); - var pathMap = {}; - platformLaunchStoryboardImages.forEach(function (item) { + const platformLaunchStoryboardImages = mapLaunchStoryboardContents(splashScreens, launchStoryboardImagesDir); + const pathMap = {}; + platformLaunchStoryboardImages.forEach(item => { if (item.target) { pathMap[item.target] = item.src; } @@ -654,6 +751,7 @@ function mapLaunchStoryboardResources (splashScreens, launchStoryboardImagesDir) * scale: '1x|2x|3x', * width-class: undefined|'compact', * height-class: undefined|'compact' + * ... * }, ... * ], * info: { @@ -670,17 +768,16 @@ function mapLaunchStoryboardResources (splashScreens, launchStoryboardImagesDir) * @return {Object} */ function getLaunchStoryboardContentsJSON (splashScreens, launchStoryboardImagesDir) { - - var platformLaunchStoryboardImages = mapLaunchStoryboardContents(splashScreens, launchStoryboardImagesDir); - var contentsJSON = { + const platformLaunchStoryboardImages = mapLaunchStoryboardContents(splashScreens, launchStoryboardImagesDir); + const contentsJSON = { images: [], info: { author: 'Xcode', version: 1 } }; - contentsJSON.images = platformLaunchStoryboardImages.map(function (item) { - var newItem = { + contentsJSON.images = platformLaunchStoryboardImages.map(item => { + const newItem = { idiom: item.idiom, scale: item.scale }; @@ -694,6 +791,10 @@ function getLaunchStoryboardContentsJSON (splashScreens, launchStoryboardImagesD newItem['height-class'] = IMAGESET_COMPACT_SIZE_CLASS; } + if (item.appearence) { + newItem.appearances = [{ appearance: 'luminosity', value: item.appearence }]; + } + // Xcode doesn't want a filename property if there's no image for these traits if (item.filename) { newItem.filename = item.filename; @@ -704,107 +805,6 @@ function getLaunchStoryboardContentsJSON (splashScreens, launchStoryboardImagesD } /** - * 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. * @@ -812,8 +812,8 @@ function updateProjectPlistForLaunchStoryboard (platformConfig, infoPlist) { * @param {string} platformProjDir The platform's project directory */ function getLaunchStoryboardImagesDir (projectRoot, platformProjDir) { - var launchStoryboardImagesDir; - var xcassetsExists = folderExists(path.join(projectRoot, platformProjDir, 'Images.xcassets/')); + let launchStoryboardImagesDir; + const xcassetsExists = folderExists(path.join(projectRoot, platformProjDir, 'Images.xcassets/')); if (xcassetsExists) { launchStoryboardImagesDir = path.join(platformProjDir, 'Images.xcassets/LaunchStoryboard.imageset/'); @@ -832,15 +832,15 @@ function getLaunchStoryboardImagesDir (projectRoot, platformProjDir) { * @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); + const splashScreens = cordovaProject.projectConfig.getSplashScreens('ios'); + const platformProjDir = path.relative(cordovaProject.root, locations.xcodeCordovaProj); + const launchStoryboardImagesDir = getLaunchStoryboardImagesDir(cordovaProject.root, platformProjDir); if (launchStoryboardImagesDir) { - var resourceMap = mapLaunchStoryboardResources(splashScreens, launchStoryboardImagesDir); - var contentsJSON = getLaunchStoryboardContentsJSON(splashScreens, launchStoryboardImagesDir); + const resourceMap = mapLaunchStoryboardResources(splashScreens, launchStoryboardImagesDir); + const contentsJSON = getLaunchStoryboardContentsJSON(splashScreens, launchStoryboardImagesDir); - events.emit('verbose', 'Updating launch storyboard images at ' + launchStoryboardImagesDir); + events.emit('verbose', `Updating launch storyboard images at ${launchStoryboardImagesDir}`); FileUpdater.updatePaths( resourceMap, { rootDir: cordovaProject.root }, logFileOp); @@ -859,24 +859,24 @@ function updateLaunchStoryboardImages (cordovaProject, locations) { * @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); + const splashScreens = projectConfig.getSplashScreens('ios'); + const platformProjDir = path.relative(projectRoot, locations.xcodeCordovaProj); + const launchStoryboardImagesDir = getLaunchStoryboardImagesDir(projectRoot, platformProjDir); if (launchStoryboardImagesDir) { - var resourceMap = mapLaunchStoryboardResources(splashScreens, launchStoryboardImagesDir); - var contentsJSON = getLaunchStoryboardContentsJSON(splashScreens, launchStoryboardImagesDir); + const resourceMap = mapLaunchStoryboardResources(splashScreens, launchStoryboardImagesDir); + const contentsJSON = getLaunchStoryboardContentsJSON(splashScreens, launchStoryboardImagesDir); - Object.keys(resourceMap).forEach(function (targetPath) { + Object.keys(resourceMap).forEach(targetPath => { resourceMap[targetPath] = null; }); - events.emit('verbose', 'Cleaning storyboard image set at ' + launchStoryboardImagesDir); + 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) { + contentsJSON.images.forEach(image => { image.filename = undefined; }); @@ -896,10 +896,9 @@ function cleanLaunchStoryboardImages (projectRoot, projectConfig, locations) { * (or empty string if both are undefined). */ function getOrientationValue (platformConfig) { + const ORIENTATION_DEFAULT = 'default'; - var ORIENTATION_DEFAULT = 'default'; - - var orientation = platformConfig.getPreference('orientation'); + let orientation = platformConfig.getPreference('orientation'); if (!orientation) { return ''; } @@ -911,8 +910,7 @@ function getOrientationValue (platformConfig) { return orientation; } - events.emit('warn', 'Unrecognized value for Orientation preference: ' + orientation + - '. Defaulting to value: ' + ORIENTATION_DEFAULT + '.'); + events.emit('warn', `Unrecognized value for Orientation preference: ${orientation}. Defaulting to value: ${ORIENTATION_DEFAULT}.`); return ORIENTATION_DEFAULT; } @@ -938,20 +936,20 @@ function getOrientationValue (platformConfig) { } */ function processAccessAndAllowNavigationEntries (config) { - var accesses = config.getAccesses(); - var allow_navigations = config.getAllowNavigations(); + const accesses = config.getAccesses(); + const allow_navigations = config.getAllowNavigations(); return allow_navigations - // we concat allow_navigations and accesses, after processing accesses - .concat(accesses.map(function (obj) { + // we concat allow_navigations and accesses, after processing accesses + .concat(accesses.map(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 = { + .reduce((previousReturn, currentElement) => { + const options = { minimum_tls_version: currentElement.minimum_tls_version, requires_forward_secrecy: currentElement.requires_forward_secrecy, requires_certificate_transparency: currentElement.requires_certificate_transparency, @@ -959,16 +957,16 @@ function processAccessAndAllowNavigationEntries (config) { 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); + const obj = parseWhitelistUrlForATS(currentElement.href, options); if (obj) { // we 'union' duplicate entries - var item = previousReturn[obj.Hostname]; + let item = previousReturn[obj.Hostname]; if (!item) { item = {}; } - for (var o in obj) { - if (obj.hasOwnProperty(o)) { + for (const o in obj) { + if (Object.prototype.hasOwnProperty.call(obj, o)) { item[o] = obj[o]; } } @@ -999,15 +997,16 @@ function processAccessAndAllowNavigationEntries (config) { 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 = {}; + // @todo 'url.parse' was deprecated since v11.0.0. Use 'url.URL' constructor instead. + const href = URL.parse(url); // eslint-disable-line + const 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; + let val; val = (options.allows_arbitrary_loads_in_web_content === 'true'); if (options.allows_arbitrary_loads_in_web_content && val) { // default is false @@ -1029,10 +1028,12 @@ function parseWhitelistUrlForATS (url, options) { 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) { + const subdomain1 = '/*.'; // wildcard in hostname + const subdomain2 = '*://*.'; // wildcard in hostname and protocol + const subdomain3 = '*://'; // wildcard in protocol only + if (!href.pathname) { + return null; + } else if (href.pathname.indexOf(subdomain1) === 0) { retObj.NSIncludesSubdomains = true; retObj.Hostname = href.pathname.substring(subdomain1.length); } else if (href.pathname.indexOf(subdomain2) === 0) { @@ -1050,12 +1051,12 @@ function parseWhitelistUrlForATS (url, options) { retObj.NSExceptionMinimumTLSVersion = options.minimum_tls_version; } - var rfs = (options.requires_forward_secrecy === 'true'); + const 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'); + const rct = (options.requires_certificate_transparency === 'true'); if (options.requires_certificate_transparency && rct) { // default is false retObj.NSRequiresCertificateTransparency = true; } @@ -1075,48 +1076,48 @@ function parseWhitelistUrlForATS (url, options) { in config.xml */ function writeATSEntries (config) { - var pObj = processAccessAndAllowNavigationEntries(config); + const pObj = processAccessAndAllowNavigationEntries(config); - var ats = {}; + const ats = {}; - for (var hostname in pObj) { - if (pObj.hasOwnProperty(hostname)) { - var entry = pObj[hostname]; + for (const hostname in pObj) { + if (Object.prototype.hasOwnProperty.call(pObj, hostname)) { + const 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; + ats.NSAllowsArbitraryLoads = true; // at least one of the overriding keys is present if (entry.NSAllowsArbitraryLoadsInWebContent) { - ats['NSAllowsArbitraryLoadsInWebContent'] = true; + ats.NSAllowsArbitraryLoadsInWebContent = true; } if (entry.NSAllowsArbitraryLoadsForMedia) { - ats['NSAllowsArbitraryLoadsForMedia'] = true; + ats.NSAllowsArbitraryLoadsForMedia = true; } if (entry.NSAllowsLocalNetworking) { - ats['NSAllowsLocalNetworking'] = true; + ats.NSAllowsLocalNetworking = true; } continue; } - var exceptionDomain = {}; + const exceptionDomain = {}; - for (var key in entry) { - if (entry.hasOwnProperty(key) && key !== 'Hostname') { + for (const key in entry) { + if (Object.prototype.hasOwnProperty.call(entry, key) && key !== 'Hostname') { exceptionDomain[key] = entry[key]; } } - if (!ats['NSExceptionDomains']) { - ats['NSExceptionDomains'] = {}; + if (!ats.NSExceptionDomains) { + ats.NSExceptionDomains = {}; } - ats['NSExceptionDomains'][hostname] = exceptionDomain; + ats.NSExceptionDomains[hostname] = exceptionDomain; } } @@ -1125,7 +1126,7 @@ function writeATSEntries (config) { function folderExists (folderPath) { try { - var stat = fs.statSync(folderPath); + const stat = fs.statSync(folderPath); return stat && stat.isDirectory(); } catch (e) { return false; @@ -1141,10 +1142,10 @@ function default_CFBundleVersion (version) { // 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"'}; + const 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 + '.'); + events.emit('warn', `Unrecognized value for target-device preference: ${value}.`); return null; } diff --git a/StoneIsland/platforms/ios/cordova/lib/projectFile.js b/StoneIsland/platforms/ios/cordova/lib/projectFile.js index 8a3f7e51..3e1e8304 100755..100644 --- a/StoneIsland/platforms/ios/cordova/lib/projectFile.js +++ b/StoneIsland/platforms/ios/cordova/lib/projectFile.js @@ -17,47 +17,46 @@ under the License. */ -var xcode = require('xcode'); -var plist = require('plist'); -var _ = require('underscore'); -var path = require('path'); -var fs = require('fs'); -var shell = require('shelljs'); +const xcode = require('xcode'); +const plist = require('plist'); +const _ = require('underscore'); +const path = require('path'); +const fs = require('fs-extra'); -var pluginHandlers = require('./plugman/pluginHandlers'); -var CordovaError = require('cordova-common').CordovaError; +const pluginHandlers = require('./plugman/pluginHandlers'); +const CordovaError = require('cordova-common').CordovaError; -var cachedProjectFiles = {}; +const cachedProjectFiles = {}; function parseProjectFile (locations) { - var project_dir = locations.root; - var pbxPath = locations.pbxproj; + const project_dir = locations.root; + const pbxPath = locations.pbxproj; if (cachedProjectFiles[project_dir]) { return cachedProjectFiles[project_dir]; } - var xcodeproj = xcode.project(pbxPath); + const xcodeproj = xcode.project(pbxPath); xcodeproj.parseSync(); - var xcBuildConfiguration = xcodeproj.pbxXCBuildConfigurationSection(); - var plist_file_entry = _.find(xcBuildConfiguration, function (entry) { return entry.buildSettings && entry.buildSettings.INFOPLIST_FILE; }); - var plist_file = path.join(project_dir, plist_file_entry.buildSettings.INFOPLIST_FILE.replace(/^"(.*)"$/g, '$1').replace(/\\&/g, '&')); - var config_file = path.join(path.dirname(plist_file), 'config.xml'); + const xcBuildConfiguration = xcodeproj.pbxXCBuildConfigurationSection(); + const plist_file_entry = _.find(xcBuildConfiguration, entry => entry.buildSettings && entry.buildSettings.INFOPLIST_FILE); + const plist_file = path.join(project_dir, plist_file_entry.buildSettings.INFOPLIST_FILE.replace(/^"(.*)"$/g, '$1').replace(/\\&/g, '&')); + const config_file = path.join(path.dirname(plist_file), 'config.xml'); if (!fs.existsSync(plist_file) || !fs.existsSync(config_file)) { throw new CordovaError('Could not find *-Info.plist file, or config.xml file.'); } - var frameworks_file = path.join(project_dir, 'frameworks.json'); - var frameworks = {}; + const frameworks_file = path.join(project_dir, 'frameworks.json'); + let frameworks = {}; try { frameworks = require(frameworks_file); } catch (e) { } - var xcode_dir = path.dirname(plist_file); - var pluginsDir = path.resolve(xcode_dir, 'Plugins'); - var resourcesDir = path.resolve(xcode_dir, 'Resources'); + const xcode_dir = path.dirname(plist_file); + const pluginsDir = path.resolve(xcode_dir, 'Plugins'); + const resourcesDir = path.resolve(xcode_dir, 'Resources'); cachedProjectFiles[project_dir] = { plugins_dir: pluginsDir, @@ -72,13 +71,21 @@ function parseProjectFile (locations) { fs.writeFileSync(pbxPath, xcodeproj.writeSync()); if (Object.keys(this.frameworks).length === 0) { // If there is no framework references remain in the project, just remove this file - shell.rm('-rf', frameworks_file); + fs.removeSync(frameworks_file); return; } fs.writeFileSync(frameworks_file, JSON.stringify(this.frameworks, null, 4)); }, getPackageName: function () { - return plist.parse(fs.readFileSync(plist_file, 'utf8')).CFBundleIdentifier; + const packageName = plist.parse(fs.readFileSync(plist_file, 'utf8')).CFBundleIdentifier; + let bundleIdentifier = packageName; + + const variables = packageName.match(/\$\((\w+)\)/); // match $(VARIABLE), if any + if (variables && variables.length >= 2) { + bundleIdentifier = xcodeproj.getBuildProperty(variables[1]); + } + + return bundleIdentifier.replace(/^"/, '').replace(/"$/, ''); }, getInstaller: function (name) { return pluginHandlers.getInstaller(name); @@ -86,7 +93,7 @@ function parseProjectFile (locations) { getUninstaller: function (name) { return pluginHandlers.getUninstaller(name); }, - frameworks: frameworks + frameworks }; return cachedProjectFiles[project_dir]; } @@ -97,7 +104,7 @@ function purgeProjectFileCache (project_dir) { module.exports = { parse: parseProjectFile, - purgeProjectFileCache: purgeProjectFileCache + purgeProjectFileCache }; xcode.project.prototype.pbxEmbedFrameworksBuildPhaseObj = function (target) { @@ -105,25 +112,23 @@ xcode.project.prototype.pbxEmbedFrameworksBuildPhaseObj = function (target) { }; xcode.project.prototype.addToPbxEmbedFrameworksBuildPhase = function (file) { - var sources = this.pbxEmbedFrameworksBuildPhaseObj(file.target); + const sources = this.pbxEmbedFrameworksBuildPhaseObj(file.target); if (sources) { sources.files.push(pbxBuildPhaseObj(file)); } }; xcode.project.prototype.removeFromPbxEmbedFrameworksBuildPhase = function (file) { - var sources = this.pbxEmbedFrameworksBuildPhaseObj(file.target); + const sources = this.pbxEmbedFrameworksBuildPhaseObj(file.target); if (sources) { - sources.files = _.reject(sources.files, function (file) { - return file.comment === longComment(file); - }); + sources.files = _.reject(sources.files, file => file.comment === longComment(file)); } }; // special handlers to add frameworks to the 'Embed Frameworks' build phase, needed for custom frameworks // see CB-9517. should probably be moved to node-xcode. -var util = require('util'); +const util = require('util'); function pbxBuildPhaseObj (file) { - var obj = Object.create(null); + const obj = Object.create(null); obj.value = file.uuid; obj.comment = longComment(file); return obj; diff --git a/StoneIsland/platforms/ios/cordova/lib/run.js b/StoneIsland/platforms/ios/cordova/lib/run.js index 3a6246e1..f7fd8c13 100755..100644 --- a/StoneIsland/platforms/ios/cordova/lib/run.js +++ b/StoneIsland/platforms/ios/cordova/lib/run.js @@ -17,23 +17,23 @@ under the License. */ -var Q = require('q'); -var path = require('path'); -var iossim = require('ios-sim'); -var build = require('./build'); -var spawn = require('./spawn'); -var check_reqs = require('./check_reqs'); +const path = require('path'); +const build = require('./build'); +const { + CordovaError, + events, + superspawn: { spawn } +} = require('cordova-common'); +const check_reqs = require('./check_reqs'); +const fs = require('fs-extra'); -var events = require('cordova-common').events; - -var cordovaPath = path.join(__dirname, '..'); -var projectPath = path.join(__dirname, '..', '..'); - -module.exports.run = function (runOptions) { +const cordovaPath = path.join(__dirname, '..'); +const projectPath = path.join(__dirname, '..', '..'); +module.exports.run = runOptions => { // Validate args if (runOptions.device && runOptions.emulator) { - return Q.reject('Only one of "device"/"emulator" options should be specified'); + return Promise.reject(new CordovaError('Only one of "device"/"emulator" options should be specified')); } // support for CB-8168 `cordova/run --list` @@ -41,15 +41,13 @@ module.exports.run = function (runOptions) { if (runOptions.device) return module.exports.listDevices(); if (runOptions.emulator) return module.exports.listEmulators(); // if no --device or --emulator flag is specified, list both devices and emulators - return module.exports.listDevices().then(function () { - return module.exports.listEmulators(); - }); + return module.exports.listDevices().then(() => module.exports.listEmulators()); } - var useDevice = !!runOptions.device; + let useDevice = !!runOptions.device; - return require('./list-devices').run() - .then(function (devices) { + return require('./listDevices').run() + .then(devices => { if (devices.length > 0 && !(runOptions.emulator)) { useDevice = true; // we also explicitly set device flag in options as we pass @@ -57,58 +55,56 @@ module.exports.run = function (runOptions) { runOptions.device = true; return check_reqs.check_ios_deploy(); } - }).then(function () { + }).then(() => { if (!runOptions.nobuild) { return build.run(runOptions); } else { - return Q.resolve(); + return Promise.resolve(); } - }).then(function () { - return build.findXCodeProjectIn(projectPath); - }).then(function (projectName) { - var appPath = path.join(projectPath, 'build', 'emulator', projectName + '.app'); - var buildOutputDir = path.join(projectPath, 'build', 'device'); + }).then(() => build.findXCodeProjectIn(projectPath)) + .then(projectName => { + let appPath = path.join(projectPath, 'build', 'emulator', `${projectName}.app`); + const buildOutputDir = path.join(projectPath, 'build', 'device'); // select command to run and arguments depending whether // we're running on device/emulator if (useDevice) { return module.exports.checkDeviceConnected() - .then(function () { + .then(() => { // Unpack IPA - var ipafile = path.join(buildOutputDir, projectName + '.ipa'); + const ipafile = path.join(buildOutputDir, `${projectName}.ipa`); // unpack the existing platform/ios/build/device/appname.ipa (zipfile), will create a Payload folder - return spawn('unzip', [ '-o', '-qq', ipafile ], buildOutputDir); + return spawn('unzip', ['-o', '-qq', ipafile], { cwd: buildOutputDir, printCommand: true, stdio: 'inherit' }); }) - .then(function () { + .then(() => { // Uncompress IPA (zip file) - var appFileInflated = path.join(buildOutputDir, 'Payload', projectName + '.app'); - var appFile = path.join(buildOutputDir, projectName + '.app'); - var payloadFolder = path.join(buildOutputDir, 'Payload'); + const appFileInflated = path.join(buildOutputDir, 'Payload', `${projectName}.app`); + const appFile = path.join(buildOutputDir, `${projectName}.app`); + const payloadFolder = path.join(buildOutputDir, 'Payload'); // delete the existing platform/ios/build/device/appname.app - return spawn('rm', [ '-rf', appFile ], buildOutputDir) - .then(function () { - // move the platform/ios/build/device/Payload/appname.app to parent - return spawn('mv', [ '-f', appFileInflated, buildOutputDir ], buildOutputDir); - }) - .then(function () { - // delete the platform/ios/build/device/Payload folder - return spawn('rm', [ '-rf', payloadFolder ], buildOutputDir); - }); + fs.removeSync(appFile); + // move the platform/ios/build/device/Payload/appname.app to parent + fs.moveSync(appFileInflated, appFile); + // delete the platform/ios/build/device/Payload folder + fs.removeSync(payloadFolder); + + return null; }) - .then(function () { - appPath = path.join(projectPath, 'build', 'device', projectName + '.app'); - var extraArgs = []; - if (runOptions.argv) { - // argv.slice(2) removes node and run.js, filterSupportedArgs removes the run.js args - extraArgs = module.exports.filterSupportedArgs(runOptions.argv.slice(2)); - } - return module.exports.deployToDevice(appPath, runOptions.target, extraArgs); - }, function () { + .then( + () => { + appPath = path.join(projectPath, 'build', 'device', `${projectName}.app`); + let extraArgs = []; + if (runOptions.argv) { + // argv.slice(2) removes node and run.js, filterSupportedArgs removes the run.js args + extraArgs = module.exports.filterSupportedArgs(runOptions.argv.slice(2)); + } + return module.exports.deployToDevice(appPath, runOptions.target, extraArgs); + }, // if device connection check failed use emulator then - return module.exports.deployToSim(appPath, runOptions.target); - }); + () => module.exports.deployToSim(appPath, runOptions.target) + ); } else { return module.exports.deployToSim(appPath, runOptions.target); } @@ -129,11 +125,11 @@ module.exports.listEmulators = listEmulators; * @return {Array} array with unsupported args for the 'run' command */ function filterSupportedArgs (args) { - var filtered = []; - var sargs = ['--device', '--emulator', '--nobuild', '--list', '--target', '--debug', '--release']; - var re = new RegExp(sargs.join('|')); + const filtered = []; + const sargs = ['--device', '--emulator', '--nobuild', '--list', '--target', '--debug', '--release']; + const re = new RegExp(sargs.join('|')); - args.forEach(function (element) { + args.forEach(element => { // supported args not found, we add // we do a regex search because --target can be "--target=XXX" if (element.search(re) === -1) { @@ -149,7 +145,7 @@ function filterSupportedArgs (args) { * @return {Promise} Fullfilled when any device is connected, rejected otherwise */ function checkDeviceConnected () { - return spawn('ios-deploy', ['-c', '-t', '1']); + return spawn('ios-deploy', ['-c', '-t', '1'], { printCommand: true, stdio: 'inherit' }); } /** @@ -159,11 +155,12 @@ function checkDeviceConnected () { * @return {Promise} Resolves when deploy succeeds otherwise rejects */ function deployToDevice (appPath, target, extraArgs) { + events.emit('log', 'Deploying to device'); // Deploying to device... if (target) { - return spawn('ios-deploy', ['--justlaunch', '-d', '-b', appPath, '-i', target].concat(extraArgs)); + return spawn('ios-deploy', ['--justlaunch', '-d', '-b', appPath, '-i', target].concat(extraArgs), { printCommand: true, stdio: 'inherit' }); } else { - return spawn('ios-deploy', ['--justlaunch', '--no-wifi', '-d', '-b', appPath].concat(extraArgs)); + return spawn('ios-deploy', ['--justlaunch', '--no-wifi', '-d', '-b', appPath].concat(extraArgs), { printCommand: true, stdio: 'inherit' }); } } @@ -174,19 +171,20 @@ function deployToDevice (appPath, target, extraArgs) { * @return {Promise} Resolves when deploy succeeds otherwise rejects */ function deployToSim (appPath, target) { - // Select target device for emulator. Default is 'iPhone-6' + events.emit('log', 'Deploying to simulator'); if (!target) { - return require('./list-emulator-images').run() - .then(function (emulators) { + // Select target device for emulator + return require('./listEmulatorImages').run() + .then(emulators => { if (emulators.length > 0) { target = emulators[0]; } - emulators.forEach(function (emulator) { + emulators.forEach(emulator => { if (emulator.indexOf('iPhone') === 0) { target = emulator; } }); - events.emit('log', 'No target specified for emulator. Deploying to ' + target + ' simulator'); + events.emit('log', `No target specified for emulator. Deploying to "${target}" simulator.`); return startSim(appPath, target); }); } else { @@ -195,32 +193,50 @@ function deployToSim (appPath, target) { } function startSim (appPath, target) { - var logPath = path.join(cordovaPath, 'console.log'); + const logPath = path.join(cordovaPath, 'console.log'); - return iossim.launch(appPath, 'com.apple.CoreSimulator.SimDeviceType.' + target, logPath, '--exit'); + return iossimLaunch(appPath, `com.apple.CoreSimulator.SimDeviceType.${target}`, logPath, '--exit'); +} + +function iossimLaunch (appPath, devicetypeid, log, exit) { + return spawn( + require.resolve('ios-sim/bin/ios-sim'), + ['launch', appPath, '--devicetypeid', devicetypeid, '--log', log, exit], + { cwd: projectPath, printCommand: true } + ).progress(stdio => { + if (stdio.stderr) { + events.emit('error', `[ios-sim] ${stdio.stderr}`); + } + if (stdio.stdout) { + events.emit('log', `[ios-sim] ${stdio.stdout.trim()}`); + } + }) + .then(result => { + events.emit('log', 'Simulator successfully started via `ios-sim`.'); + }); } function listDevices () { - return require('./list-devices').run() - .then(function (devices) { + return require('./listDevices').run() + .then(devices => { events.emit('log', 'Available iOS Devices:'); - devices.forEach(function (device) { - events.emit('log', '\t' + device); + devices.forEach(device => { + events.emit('log', `\t${device}`); }); }); } function listEmulators () { - return require('./list-emulator-images').run() - .then(function (emulators) { + return require('./listEmulatorImages').run() + .then(emulators => { events.emit('log', 'Available iOS Simulators:'); - emulators.forEach(function (emulator) { - events.emit('log', '\t' + emulator); + emulators.forEach(emulator => { + events.emit('log', `\t${emulator}`); }); }); } -module.exports.help = function () { +module.exports.help = () => { console.log('\nUsage: run [ --device | [ --emulator [ --target=<id> ] ] ] [ --debug | --release | --nobuild ]'); // TODO: add support for building different archs // console.log(" [ --archs=\"<list of target architectures>\" ] "); diff --git a/StoneIsland/platforms/ios/cordova/lib/spawn.js b/StoneIsland/platforms/ios/cordova/lib/spawn.js deleted file mode 100755 index b5a56852..00000000 --- a/StoneIsland/platforms/ios/cordova/lib/spawn.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - 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. -*/ - -var Q = require('q'); -var proc = require('child_process'); - -/** - * Run specified command with arguments - * @param {String} cmd Command - * @param {Array} args Array of arguments that should be passed to command - * @param {String} opt_cwd Working directory for command - * @param {String} opt_verbosity Verbosity level for command stdout output, "verbose" by default - * @return {Promise} Promise either fullfilled or rejected with error code - */ -module.exports = function (cmd, args, opt_cwd) { - var d = Q.defer(); - try { - var child = proc.spawn(cmd, args, {cwd: opt_cwd, stdio: 'inherit'}); - - child.on('exit', function (code) { - if (code) { - d.reject('Error code ' + code + ' for command: ' + cmd + ' with args: ' + args); - } else { - d.resolve(); - } - }); - } catch (e) { - d.reject(e); - } - return d.promise; -}; diff --git a/StoneIsland/platforms/ios/cordova/lib/versions.js b/StoneIsland/platforms/ios/cordova/lib/versions.js index c6a41b83..f8914785 100755..100644 --- a/StoneIsland/platforms/ios/cordova/lib/versions.js +++ b/StoneIsland/platforms/ios/cordova/lib/versions.js @@ -1,5 +1,3 @@ -#!/usr/bin/env node - /* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file @@ -19,76 +17,43 @@ under the License. */ -var child_process = require('child_process'); -var Q = require('q'); +const { + CordovaError, + superspawn: { spawn } +} = require('cordova-common'); +const semver = require('semver'); -exports.get_apple_ios_version = function () { - var d = Q.defer(); - child_process.exec('xcodebuild -showsdks', function (error, stdout, stderr) { - if (error) { - d.reject(stderr); - } else { - d.resolve(stdout); - } - }); +function fetchSdkVersionByType (sdkType) { + return spawn('xcodebuild', ['-showsdks']) + .then(output => { + const regexSdk = new RegExp(`^${sdkType} \\d`); - return d.promise.then(function (output) { - var regex = /[0-9]*\.[0-9]*/; - var versions = []; - var regexIOS = /^iOS \d+/; - output = output.split('\n'); - for (var i = 0; i < output.length; i++) { - if (output[i].trim().match(regexIOS)) { - versions[versions.length] = parseFloat(output[i].match(regex)[0]); - } - } - versions.sort(); - console.log(versions[0]); - return Q(); - }, function (stderr) { - return Q.reject(stderr); - }); -}; + const versions = output.split('\n') + .filter(line => line.trim().match(regexSdk)) + .map(line => line.match(/\d+\.\d+/)[0]) + .sort(exports.compareVersions); -exports.get_apple_osx_version = function () { - var d = Q.defer(); - child_process.exec('xcodebuild -showsdks', function (error, stdout, stderr) { - if (error) { - d.reject(stderr); - } else { - d.resolve(stdout); - } - }); + console.log(versions[0]); + }); +} - return d.promise.then(function (output) { - var regex = /[0-9]*\.[0-9]*/; - var versions = []; - var regexOSX = /^OS X \d+/; - output = output.split('\n'); - for (var i = 0; i < output.length; i++) { - if (output[i].trim().match(regexOSX)) { - versions[versions.length] = parseFloat(output[i].match(regex)[0]); - } - } - versions.sort(); - console.log(versions[0]); - return Q(); - }, function (stderr) { - return Q.reject(stderr); - }); +exports.get_apple_ios_version = () => { + return fetchSdkVersionByType('iOS'); }; -exports.get_apple_xcode_version = function () { - var d = Q.defer(); - child_process.exec('xcodebuild -version', function (error, stdout, stderr) { - var versionMatch = /Xcode (.*)/.exec(stdout); - if (error || !versionMatch) { - d.reject(stderr); - } else { - d.resolve(versionMatch[1]); - } - }); - return d.promise; +exports.get_apple_osx_version = () => { + return fetchSdkVersionByType('macOS'); +}; + +exports.get_apple_xcode_version = () => { + return spawn('xcodebuild', ['-version']) + .then(output => { + const versionMatch = /Xcode (.*)/.exec(output); + + if (!versionMatch) return Promise.reject(output); + + return versionMatch[1]; + }); }; /** @@ -96,16 +61,8 @@ exports.get_apple_xcode_version = function () { * @return {Promise} Promise that either resolved with ios-deploy version * or rejected in case of error */ -exports.get_ios_deploy_version = function () { - var d = Q.defer(); - child_process.exec('ios-deploy --version', function (error, stdout, stderr) { - if (error) { - d.reject(stderr); - } else { - d.resolve(stdout); - } - }); - return d.promise; +exports.get_ios_deploy_version = () => { + return spawn('ios-deploy', ['--version']); }; /** @@ -113,16 +70,8 @@ exports.get_ios_deploy_version = function () { * @return {Promise} Promise that either resolved with pod version * or rejected in case of error */ -exports.get_cocoapods_version = function () { - var d = Q.defer(); - child_process.exec('pod --version', function (error, stdout, stderr) { - if (error) { - d.reject(stderr); - } else { - d.resolve(stdout); - } - }); - return d.promise; +exports.get_cocoapods_version = () => { + return spawn('pod', ['--version']); }; /** @@ -130,16 +79,8 @@ exports.get_cocoapods_version = function () { * @return {Promise} Promise that either resolved with ios-sim version * or rejected in case of error */ -exports.get_ios_sim_version = function () { - var d = Q.defer(); - child_process.exec('ios-sim --version', function (error, stdout, stderr) { - if (error) { - d.reject(stderr); - } else { - d.resolve(stdout); - } - }); - return d.promise; +exports.get_ios_sim_version = () => { + return spawn('ios-sim', ['--version']); }; /** @@ -148,47 +89,31 @@ exports.get_ios_sim_version = function () { * @return {Promise} Promise that either resolved with tool version * or rejected in case of error */ -exports.get_tool_version = function (toolName) { +exports.get_tool_version = toolName => { switch (toolName) { case 'xcodebuild': return exports.get_apple_xcode_version(); case 'ios-sim': return exports.get_ios_sim_version(); case 'ios-deploy': return exports.get_ios_deploy_version(); case 'pod': return exports.get_cocoapods_version(); - default: return Q.reject(toolName + ' is not valid tool name. Valid names are: \'xcodebuild\', \'ios-sim\', \'ios-deploy\', and \'pod\''); + default: return Promise.reject(new CordovaError(`${toolName} is not valid tool name. Valid names are: 'xcodebuild', 'ios-sim', 'ios-deploy', and 'pod'`)); } }; /** - * Compares two semver-notated version strings. Returns number - * that indicates equality of provided version strings. + * Compares two version strings that can be coerced to semver. + * * @param {String} version1 Version to compare * @param {String} version2 Another version to compare * @return {Number} Negative number if first version is lower than the second, * positive otherwise and 0 if versions are equal. */ -exports.compareVersions = function (version1, version2) { - function parseVer (version) { - return version.split('.').map(function (value) { - // try to convert version segment to Number - var parsed = Number(value); - // Number constructor is strict enough and will return NaN - // if conversion fails. In this case we won't be able to compare versions properly - if (isNaN(parsed)) { - throw 'Version should contain only numbers and dots'; - } - return parsed; - }); - } - var parsedVer1 = parseVer(version1); - var parsedVer2 = parseVer(version2); +exports.compareVersions = (...args) => { + const coerceToSemverIfInvalid = v => { + const semverVersion = semver.parse(v) || semver.coerce(v); + if (!semverVersion) throw new TypeError(`Invalid Version: ${v}`); + return semverVersion; + }; - // Compare corresponding segments of each version - for (var i = 0; i < Math.max(parsedVer1.length, parsedVer2.length); i++) { - // if segment is not specified, assume that it is 0 - // E.g. 3.1 is equal to 3.1.0 - var ret = (parsedVer1[i] || 0) - (parsedVer2[i] || 0); - // if segments are not equal, we're finished - if (ret !== 0) return ret; - } - return 0; + const semverVersions = args.map(coerceToSemverIfInvalid); + return semver.compare(...semverVersions); }; |
