cordova-android/bin/lib/create.js

325 lines
15 KiB
JavaScript
Raw Normal View History

2015-10-21 07:15:57 +08:00
/*
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.
*/
2017-06-14 02:42:20 +08:00
var path = require('path');
var fs = require('fs-extra');
var utils = require('../templates/cordova/lib/utils');
2017-06-14 02:42:20 +08:00
var check_reqs = require('./../templates/cordova/lib/check_reqs');
var ROOT = path.join(__dirname, '..', '..');
feat!: unify & fix gradle library/tooling overrides (#1212) * enhancement: Control SDK versions and other default projects in one place * fix: target/compile sdk usage * refactor: cleanup gradle process * chore: cleanup and remove unused changes * chore: remove more unneeded FILE_PATH * chore: fix lint error * revert change intended to be part of a different PR * chore: apply changes to revert to fit new changes * fix: Ensure proper types * breaking: Removed TempateFile class * Replaced the one and only usage of it with the properties-parser editor. * Breaking change because we are converting a method into an asynchronous method. * refactor: Use the sync version of properties editor * Gh 1178 fix sdk use gradlearg fix (#2) * fix: readd gradleArg support * fix: variable name * refactor: remove unused mock variables * Update bin/templates/cordova/lib/builders/ProjectBuilder.js * Update bin/lib/create.js * fix: const naming (review suggestion) * fix: use defaults for framework building * chore: apply review suggestion * chore: rename config.json & defaults.json (review suggestions) * refactor: updateUserProjectGradleConfig method * refactor: minor changes in updateUserProjectGradleConfig * refactor: major changes in updateUserProjectGradleConfig * fix: wrong handling of missing preferences * fix: usage of undefined this * fix(create.spec): mocking of getPreference * test(check_reqs): reduce diff size * refactor: add wrapper to load gradle config defaults * fix(check_reqs): get_target * Reads default SDK from default gradle config now * fix(check_reqs.spec): return correct types from mocks * revert to using get_target in create * fix: e2e test Co-authored-by: Erisu <ellis.bryan@gmail.com> Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>
2021-07-06 14:38:28 +08:00
const { createEditor } = require('properties-parser');
2015-10-21 07:15:57 +08:00
var CordovaError = require('cordova-common').CordovaError;
var AndroidManifest = require('../templates/cordova/lib/AndroidManifest');
// Export all helper functions, and make sure internally within this module, we
// reference these methods via the `exports` object - this helps with testing
// (since we can then mock and control behaviour of all of these functions)
exports.validatePackageName = validatePackageName;
exports.validateProjectName = validateProjectName;
exports.copyJsAndLibrary = copyJsAndLibrary;
exports.copyScripts = copyScripts;
exports.copyBuildRules = copyBuildRules;
exports.writeProjectProperties = writeProjectProperties;
exports.prepBuildFiles = prepBuildFiles;
exports.writeNameForAndroidStudio = writeNameForAndroidStudio;
2017-06-14 02:42:20 +08:00
function getFrameworkDir (projectPath, shared) {
2015-10-21 07:15:57 +08:00
return shared ? path.join(ROOT, 'framework') : path.join(projectPath, 'CordovaLib');
}
feat!: unify & fix gradle library/tooling overrides (#1212) * enhancement: Control SDK versions and other default projects in one place * fix: target/compile sdk usage * refactor: cleanup gradle process * chore: cleanup and remove unused changes * chore: remove more unneeded FILE_PATH * chore: fix lint error * revert change intended to be part of a different PR * chore: apply changes to revert to fit new changes * fix: Ensure proper types * breaking: Removed TempateFile class * Replaced the one and only usage of it with the properties-parser editor. * Breaking change because we are converting a method into an asynchronous method. * refactor: Use the sync version of properties editor * Gh 1178 fix sdk use gradlearg fix (#2) * fix: readd gradleArg support * fix: variable name * refactor: remove unused mock variables * Update bin/templates/cordova/lib/builders/ProjectBuilder.js * Update bin/lib/create.js * fix: const naming (review suggestion) * fix: use defaults for framework building * chore: apply review suggestion * chore: rename config.json & defaults.json (review suggestions) * refactor: updateUserProjectGradleConfig method * refactor: minor changes in updateUserProjectGradleConfig * refactor: major changes in updateUserProjectGradleConfig * fix: wrong handling of missing preferences * fix: usage of undefined this * fix(create.spec): mocking of getPreference * test(check_reqs): reduce diff size * refactor: add wrapper to load gradle config defaults * fix(check_reqs): get_target * Reads default SDK from default gradle config now * fix(check_reqs.spec): return correct types from mocks * revert to using get_target in create * fix: e2e test Co-authored-by: Erisu <ellis.bryan@gmail.com> Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>
2021-07-06 14:38:28 +08:00
function copyJsAndLibrary (projectPath, shared, projectName, targetAPI) {
2015-10-21 07:15:57 +08:00
var nestedCordovaLibPath = getFrameworkDir(projectPath, false);
var srcCordovaJsPath = path.join(ROOT, 'bin', 'templates', 'project', 'assets', 'www', 'cordova.js');
var app_path = path.join(projectPath, 'app', 'src', 'main');
const platform_www = path.join(projectPath, 'platform_www');
fs.copySync(srcCordovaJsPath, path.join(app_path, 'assets', 'www', 'cordova.js'));
2015-10-21 07:15:57 +08:00
// Copy the cordova.js file to platforms/<platform>/platform_www/
// The www dir is nuked on each prepare so we keep cordova.js in platform_www
fs.ensureDirSync(platform_www);
fs.copySync(srcCordovaJsPath, path.join(platform_www, 'cordova.js'));
2015-10-21 07:15:57 +08:00
// Copy cordova-js-src directory into platform_www directory.
// We need these files to build cordova.js if using browserify method.
fs.copySync(path.join(ROOT, 'cordova-js-src'), path.join(platform_www, 'cordova-js-src'));
2015-10-21 07:15:57 +08:00
if (shared) {
var relativeFrameworkPath = path.relative(projectPath, getFrameworkDir(projectPath, true));
fs.symlinkSync(relativeFrameworkPath, nestedCordovaLibPath, 'dir');
} else {
fs.ensureDirSync(nestedCordovaLibPath);
fs.copySync(path.join(ROOT, 'framework', 'AndroidManifest.xml'), path.join(nestedCordovaLibPath, 'AndroidManifest.xml'));
feat!: unify & fix gradle library/tooling overrides (#1212) * enhancement: Control SDK versions and other default projects in one place * fix: target/compile sdk usage * refactor: cleanup gradle process * chore: cleanup and remove unused changes * chore: remove more unneeded FILE_PATH * chore: fix lint error * revert change intended to be part of a different PR * chore: apply changes to revert to fit new changes * fix: Ensure proper types * breaking: Removed TempateFile class * Replaced the one and only usage of it with the properties-parser editor. * Breaking change because we are converting a method into an asynchronous method. * refactor: Use the sync version of properties editor * Gh 1178 fix sdk use gradlearg fix (#2) * fix: readd gradleArg support * fix: variable name * refactor: remove unused mock variables * Update bin/templates/cordova/lib/builders/ProjectBuilder.js * Update bin/lib/create.js * fix: const naming (review suggestion) * fix: use defaults for framework building * chore: apply review suggestion * chore: rename config.json & defaults.json (review suggestions) * refactor: updateUserProjectGradleConfig method * refactor: minor changes in updateUserProjectGradleConfig * refactor: major changes in updateUserProjectGradleConfig * fix: wrong handling of missing preferences * fix: usage of undefined this * fix(create.spec): mocking of getPreference * test(check_reqs): reduce diff size * refactor: add wrapper to load gradle config defaults * fix(check_reqs): get_target * Reads default SDK from default gradle config now * fix(check_reqs.spec): return correct types from mocks * revert to using get_target in create * fix: e2e test Co-authored-by: Erisu <ellis.bryan@gmail.com> Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>
2021-07-06 14:38:28 +08:00
const propertiesEditor = createEditor(path.join(ROOT, 'framework', 'project.properties'));
propertiesEditor.set('target', targetAPI);
propertiesEditor.save(path.join(nestedCordovaLibPath, 'project.properties'));
fs.copySync(path.join(ROOT, 'framework', 'build.gradle'), path.join(nestedCordovaLibPath, 'build.gradle'));
fs.copySync(path.join(ROOT, 'framework', 'cordova.gradle'), path.join(nestedCordovaLibPath, 'cordova.gradle'));
fs.copySync(path.join(ROOT, 'framework', 'repositories.gradle'), path.join(nestedCordovaLibPath, 'repositories.gradle'));
fs.copySync(path.join(ROOT, 'framework', 'src'), path.join(nestedCordovaLibPath, 'src'));
feat!: unify & fix gradle library/tooling overrides (#1212) * enhancement: Control SDK versions and other default projects in one place * fix: target/compile sdk usage * refactor: cleanup gradle process * chore: cleanup and remove unused changes * chore: remove more unneeded FILE_PATH * chore: fix lint error * revert change intended to be part of a different PR * chore: apply changes to revert to fit new changes * fix: Ensure proper types * breaking: Removed TempateFile class * Replaced the one and only usage of it with the properties-parser editor. * Breaking change because we are converting a method into an asynchronous method. * refactor: Use the sync version of properties editor * Gh 1178 fix sdk use gradlearg fix (#2) * fix: readd gradleArg support * fix: variable name * refactor: remove unused mock variables * Update bin/templates/cordova/lib/builders/ProjectBuilder.js * Update bin/lib/create.js * fix: const naming (review suggestion) * fix: use defaults for framework building * chore: apply review suggestion * chore: rename config.json & defaults.json (review suggestions) * refactor: updateUserProjectGradleConfig method * refactor: minor changes in updateUserProjectGradleConfig * refactor: major changes in updateUserProjectGradleConfig * fix: wrong handling of missing preferences * fix: usage of undefined this * fix(create.spec): mocking of getPreference * test(check_reqs): reduce diff size * refactor: add wrapper to load gradle config defaults * fix(check_reqs): get_target * Reads default SDK from default gradle config now * fix(check_reqs.spec): return correct types from mocks * revert to using get_target in create * fix: e2e test Co-authored-by: Erisu <ellis.bryan@gmail.com> Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>
2021-07-06 14:38:28 +08:00
fs.copySync(path.join(ROOT, 'framework', 'cdv-gradle-config-defaults.json'), path.join(projectPath, 'cdv-gradle-config.json'));
2015-10-21 07:15:57 +08:00
}
}
2017-06-14 02:42:20 +08:00
function extractSubProjectPaths (data) {
2015-10-21 07:15:57 +08:00
var ret = {};
var r = /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg;
var m;
while ((m = r.exec(data))) {
ret[m[1]] = 1;
}
return Object.keys(ret);
}
2017-06-14 02:42:20 +08:00
function writeProjectProperties (projectPath, target_api) {
2015-10-21 07:15:57 +08:00
var dstPath = path.join(projectPath, 'project.properties');
var templatePath = path.join(ROOT, 'bin', 'templates', 'project', 'project.properties');
var srcPath = fs.existsSync(dstPath) ? dstPath : templatePath;
var data = fs.readFileSync(srcPath, 'utf8');
data = data.replace(/^target=.*/m, 'target=' + target_api);
var subProjects = extractSubProjectPaths(data);
2017-06-14 02:42:20 +08:00
subProjects = subProjects.filter(function (p) {
2015-10-21 07:15:57 +08:00
return !(/^CordovaLib$/m.exec(p) ||
2017-06-14 02:42:20 +08:00
/[\\/]cordova-android[\\/]framework$/m.exec(p) ||
/^(\.\.[\\/])+framework$/m.exec(p));
2015-10-21 07:15:57 +08:00
});
subProjects.unshift('CordovaLib');
data = data.replace(/^\s*android\.library\.reference\.\d+=.*\n/mg, '');
if (!/\n$/.exec(data)) {
data += '\n';
}
for (var i = 0; i < subProjects.length; ++i) {
2017-06-14 02:42:20 +08:00
data += 'android.library.reference.' + (i + 1) + '=' + subProjects[i] + '\n';
2015-10-21 07:15:57 +08:00
}
fs.writeFileSync(dstPath, data);
}
// This makes no sense, what if you're building with a different build system?
function prepBuildFiles (projectPath) {
var buildModule = require('../templates/cordova/lib/builders/builders');
buildModule.getBuilder(projectPath).prepBuildFiles();
2015-10-21 07:15:57 +08:00
}
2017-06-28 04:15:04 +08:00
function copyBuildRules (projectPath, isLegacy) {
2015-10-21 07:15:57 +08:00
var srcDir = path.join(ROOT, 'bin', 'templates', 'project');
2017-06-28 04:15:04 +08:00
if (isLegacy) {
// The project's build.gradle is identical to the earlier build.gradle, so it should still work
fs.copySync(path.join(srcDir, 'legacy', 'build.gradle'), path.join(projectPath, 'legacy', 'build.gradle'));
fs.copySync(path.join(srcDir, 'wrapper.gradle'), path.join(projectPath, 'wrapper.gradle'));
} else {
fs.copySync(path.join(srcDir, 'build.gradle'), path.join(projectPath, 'build.gradle'));
fs.copySync(path.join(srcDir, 'app', 'build.gradle'), path.join(projectPath, 'app', 'build.gradle'));
fs.copySync(path.join(srcDir, 'app', 'repositories.gradle'), path.join(projectPath, 'app', 'repositories.gradle'));
fs.copySync(path.join(srcDir, 'repositories.gradle'), path.join(projectPath, 'repositories.gradle'));
fs.copySync(path.join(srcDir, 'wrapper.gradle'), path.join(projectPath, 'wrapper.gradle'));
}
2015-10-21 07:15:57 +08:00
}
function copyScripts (projectPath) {
var bin = path.join(ROOT, 'bin');
var srcScriptsDir = path.join(bin, 'templates', 'cordova');
2015-10-21 07:15:57 +08:00
var destScriptsDir = path.join(projectPath, 'cordova');
// Delete old scripts directory if this is an update.
fs.removeSync(destScriptsDir);
2015-10-21 07:15:57 +08:00
// Copy in the new ones.
fs.copySync(srcScriptsDir, destScriptsDir);
const nodeModulesDir = path.join(ROOT, 'node_modules');
if (fs.existsSync(nodeModulesDir)) fs.copySync(nodeModulesDir, path.join(destScriptsDir, 'node_modules'));
2015-10-21 07:15:57 +08:00
}
/**
* Test whether a package name is acceptable for use as an android project.
* Returns a promise, fulfilled if the package name is acceptable; rejected
* otherwise.
*/
2017-06-14 02:42:20 +08:00
function validatePackageName (package_name) {
// Make the package conform to Java package types
// http://developer.android.com/guide/topics/manifest/manifest-element.html#package
// Enforce underscore limitation
2015-10-21 07:15:57 +08:00
var msg = 'Error validating package name. ';
2015-10-21 07:15:57 +08:00
if (!/^[a-zA-Z][a-zA-Z0-9_]+(\.[a-zA-Z][a-zA-Z0-9_]*)+$/.test(package_name)) {
return Promise.reject(new CordovaError(msg + 'Must look like: `com.company.Name`. Currently is: `' + package_name + '`'));
2015-10-21 07:15:57 +08:00
}
2017-06-14 02:42:20 +08:00
// Class is a reserved word
if (/\b[Cc]lass\b/.test(package_name)) {
return Promise.reject(new CordovaError(msg + '"class" is a reserved word'));
2015-10-21 07:15:57 +08:00
}
return Promise.resolve();
2015-10-21 07:15:57 +08:00
}
/**
* Test whether given string is acceptable for use as a project name
2015-10-21 07:15:57 +08:00
* Returns a promise, fulfilled if the project name is acceptable; rejected
* otherwise.
*/
2017-06-14 02:42:20 +08:00
function validateProjectName (project_name) {
2015-10-21 07:15:57 +08:00
var msg = 'Error validating project name. ';
2017-06-14 02:42:20 +08:00
// Make sure there's something there
2015-10-21 07:15:57 +08:00
if (project_name === '') {
return Promise.reject(new CordovaError(msg + 'Project name cannot be empty'));
2015-10-21 07:15:57 +08:00
}
return Promise.resolve();
2015-10-21 07:15:57 +08:00
}
/**
* Write the name of the app in "platforms/android/.idea/.name" so that Android Studio can show that name in the
* project listing. This is helpful to quickly look in the Android Studio listing if there are so many projects in
* Android Studio.
*
* https://github.com/apache/cordova-android/issues/1172
*/
function writeNameForAndroidStudio (project_path, project_name) {
const ideaPath = path.join(project_path, '.idea');
fs.ensureDirSync(ideaPath);
fs.writeFileSync(path.join(ideaPath, '.name'), project_name);
}
2015-10-21 07:15:57 +08:00
/**
* Creates an android application with the given options.
*
* @param {String} project_path Path to the new Cordova android project.
* @param {ConfigParser} config Instance of ConfigParser to retrieve basic
* project properties.
* @param {Object} [options={}] Various options
* @param {String} [options.activityName='MainActivity'] Name for the
* activity
* @param {Boolean} [options.link=false] Specifies whether javascript files
* and CordovaLib framework will be symlinked to created application.
* @param {String} [options.customTemplate] Path to project template
* (override)
* @param {EventEmitter} [events] An EventEmitter instance for logging
* events
*
* @return {Promise<String>} Directory where application has been created
*/
2017-06-14 02:42:20 +08:00
exports.create = function (project_path, config, options, events) {
2015-10-21 07:15:57 +08:00
options = options || {};
// Set default values for path, package and name
project_path = path.relative(process.cwd(), (project_path || 'CordovaExample'));
// Check if project already exists
2017-06-14 02:42:20 +08:00
if (fs.existsSync(project_path)) {
return Promise.reject(new CordovaError('Project already exists! Delete and recreate'));
2015-10-21 07:15:57 +08:00
}
var package_name = config.android_packageName() || config.packageName() || 'my.cordova.project';
var project_name = config.name()
? config.name().replace(/[^\w.]/g, '_') : 'CordovaExample';
2015-10-21 07:15:57 +08:00
var safe_activity_name = config.android_activityName() || options.activityName || 'MainActivity';
2017-06-14 02:42:20 +08:00
var target_api = check_reqs.get_target();
2015-10-21 07:15:57 +08:00
2017-06-14 02:42:20 +08:00
// Make the package conform to Java package types
return exports.validatePackageName(package_name)
2017-06-14 02:42:20 +08:00
.then(function () {
return exports.validateProjectName(project_name);
2017-06-14 02:42:20 +08:00
}).then(function () {
2015-10-21 07:15:57 +08:00
// Log the given values for the project
2017-06-28 04:15:04 +08:00
events.emit('log', 'Creating Cordova project for the Android platform:');
events.emit('log', '\tPath: ' + project_path);
events.emit('log', '\tPackage: ' + package_name);
events.emit('log', '\tName: ' + project_name);
events.emit('log', '\tActivity: ' + safe_activity_name);
events.emit('log', '\tAndroid target: ' + target_api);
events.emit('verbose', 'Copying android template project to ' + project_path);
var project_template_dir = options.customTemplate || path.join(ROOT, 'bin', 'templates', 'project');
var app_path = path.join(project_path, 'app', 'src', 'main');
// copy project template
fs.ensureDirSync(app_path);
fs.copySync(path.join(project_template_dir, 'assets'), path.join(app_path, 'assets'));
fs.copySync(path.join(project_template_dir, 'res'), path.join(app_path, 'res'));
fs.copySync(path.join(project_template_dir, 'gitignore'), path.join(project_path, '.gitignore'));
// Manually create directories that would be empty within the template (since git doesn't track directories).
fs.ensureDirSync(path.join(app_path, 'libs'));
// copy cordova.js, cordova.jar
feat!: unify & fix gradle library/tooling overrides (#1212) * enhancement: Control SDK versions and other default projects in one place * fix: target/compile sdk usage * refactor: cleanup gradle process * chore: cleanup and remove unused changes * chore: remove more unneeded FILE_PATH * chore: fix lint error * revert change intended to be part of a different PR * chore: apply changes to revert to fit new changes * fix: Ensure proper types * breaking: Removed TempateFile class * Replaced the one and only usage of it with the properties-parser editor. * Breaking change because we are converting a method into an asynchronous method. * refactor: Use the sync version of properties editor * Gh 1178 fix sdk use gradlearg fix (#2) * fix: readd gradleArg support * fix: variable name * refactor: remove unused mock variables * Update bin/templates/cordova/lib/builders/ProjectBuilder.js * Update bin/lib/create.js * fix: const naming (review suggestion) * fix: use defaults for framework building * chore: apply review suggestion * chore: rename config.json & defaults.json (review suggestions) * refactor: updateUserProjectGradleConfig method * refactor: minor changes in updateUserProjectGradleConfig * refactor: major changes in updateUserProjectGradleConfig * fix: wrong handling of missing preferences * fix: usage of undefined this * fix(create.spec): mocking of getPreference * test(check_reqs): reduce diff size * refactor: add wrapper to load gradle config defaults * fix(check_reqs): get_target * Reads default SDK from default gradle config now * fix(check_reqs.spec): return correct types from mocks * revert to using get_target in create * fix: e2e test Co-authored-by: Erisu <ellis.bryan@gmail.com> Co-authored-by: Raphael von der Grün <raphinesse@gmail.com>
2021-07-06 14:38:28 +08:00
exports.copyJsAndLibrary(project_path, options.link, safe_activity_name, target_api);
// Set up ther Android Studio paths
var java_path = path.join(app_path, 'java');
var assets_path = path.join(app_path, 'assets');
var resource_path = path.join(app_path, 'res');
fs.ensureDirSync(java_path);
fs.ensureDirSync(assets_path);
fs.ensureDirSync(resource_path);
// interpolate the activity name and package
var packagePath = package_name.replace(/\./g, path.sep);
var activity_dir = path.join(java_path, packagePath);
var activity_path = path.join(activity_dir, safe_activity_name + '.java');
fs.ensureDirSync(activity_dir);
fs.copySync(path.join(project_template_dir, 'Activity.java'), activity_path);
utils.replaceFileContents(activity_path, /__ACTIVITY__/, safe_activity_name);
utils.replaceFileContents(path.join(app_path, 'res', 'values', 'strings.xml'), /__NAME__/, project_name);
utils.replaceFileContents(activity_path, /__ID__/, package_name);
var manifest = new AndroidManifest(path.join(project_template_dir, 'AndroidManifest.xml'));
manifest.setPackageId(package_name)
.getActivity().setName(safe_activity_name);
var manifest_path = path.join(app_path, 'AndroidManifest.xml');
manifest.write(manifest_path);
exports.copyScripts(project_path);
exports.copyBuildRules(project_path);
2017-06-28 04:15:04 +08:00
// Link it to local android install.
exports.writeProjectProperties(project_path, target_api);
exports.prepBuildFiles(project_path);
exports.writeNameForAndroidStudio(project_path, project_name);
2017-06-28 04:15:04 +08:00
events.emit('log', generateDoneMessage('create', options.link));
}).then(() => project_path);
2015-10-21 07:15:57 +08:00
};
2017-06-14 02:42:20 +08:00
function generateDoneMessage (type, link) {
2015-10-21 07:15:57 +08:00
var pkg = require('../../package');
2017-06-14 02:42:20 +08:00
var msg = 'Android project ' + (type === 'update' ? 'updated ' : 'created ') + 'with ' + pkg.name + '@' + pkg.version;
2015-10-21 07:15:57 +08:00
if (link) {
msg += ' and has a linked CordovaLib';
}
return msg;
}
// Returns a promise.
2017-06-14 02:42:20 +08:00
exports.update = function (projectPath, options, events) {
2017-12-01 03:08:39 +08:00
var errorString =
'An in-place platform update is not supported. \n' +
'The `platforms` folder is always treated as a build artifact in the CLI workflow.\n' +
'To update your platform, you have to remove, then add your android platform again.\n' +
'Make sure you save your plugins beforehand using `cordova plugin save`, and save \n' + 'a copy of the platform first if you had manual changes in it.\n' +
'\tcordova plugin save\n' +
'\tcordova platform rm android\n' +
'\tcordova platform add android\n'
;
return Promise.reject(errorString);
2015-10-21 07:15:57 +08:00
};