refactor: remove shelljs dependency (#842)

Co-authored-by: エリス <erisu@users.noreply.github.com>
This commit is contained in:
Norman Breau 2020-01-28 21:12:55 -04:00 committed by GitHub
parent dee1e77d0b
commit 3712619f5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 702 additions and 511 deletions

30
LICENSE
View File

@ -226,36 +226,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
================================================================================
bin/node_modules/shelljs
================================================================================
Copyright (c) 2012, Artur Adib <aadib@mozilla.com>
All rights reserved.
You may use this project under the terms of the New BSD license as follows:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Artur Adib nor the
names of the contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL ARTUR ADIB BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================================================
bin/node_modules/nopt
================================================================================

View File

@ -19,9 +19,9 @@
under the License.
*/
var shell = require('shelljs');
var path = require('path');
var fs = require('fs');
var fs = require('fs-extra');
var utils = require('../../bin/lib/utils');
var check_reqs = require('./../templates/cordova/lib/check_reqs');
var ROOT = path.join(__dirname, '..', '..');
@ -33,20 +33,12 @@ var AndroidManifest = require('../templates/cordova/lib/AndroidManifest');
// (since we can then mock and control behaviour of all of these functions)
exports.validatePackageName = validatePackageName;
exports.validateProjectName = validateProjectName;
exports.setShellFatal = setShellFatal;
exports.copyJsAndLibrary = copyJsAndLibrary;
exports.copyScripts = copyScripts;
exports.copyBuildRules = copyBuildRules;
exports.writeProjectProperties = writeProjectProperties;
exports.prepBuildFiles = prepBuildFiles;
function setShellFatal (value, func) {
var oldVal = shell.config.fatal;
shell.config.fatal = value;
func();
shell.config.fatal = oldVal;
}
function getFrameworkDir (projectPath, shared) {
return shared ? path.join(ROOT, 'framework') : path.join(projectPath, 'CordovaLib');
}
@ -55,53 +47,33 @@ function copyJsAndLibrary (projectPath, shared, projectName, isLegacy) {
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');
if (isLegacy) {
app_path = projectPath;
}
shell.cp('-f', srcCordovaJsPath, path.join(app_path, 'assets', 'www', 'cordova.js'));
fs.copySync(srcCordovaJsPath, path.join(app_path, 'assets', 'www', 'cordova.js'));
// 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
shell.mkdir('-p', path.join(projectPath, 'platform_www'));
shell.cp('-f', srcCordovaJsPath, path.join(projectPath, 'platform_www'));
fs.ensureDirSync(platform_www);
fs.copySync(srcCordovaJsPath, path.join(platform_www, 'cordova.js'));
// Copy cordova-js-src directory into platform_www directory.
// We need these files to build cordova.js if using browserify method.
shell.cp('-rf', path.join(ROOT, 'cordova-js-src'), path.join(projectPath, 'platform_www'));
fs.copySync(path.join(ROOT, 'cordova-js-src'), path.join(platform_www, 'cordova-js-src'));
// Don't fail if there are no old jars.
exports.setShellFatal(false, function () {
shell.ls(path.join(app_path, 'libs', 'cordova-*.jar')).forEach(function (oldJar) {
console.log('Deleting ' + oldJar);
shell.rm('-f', oldJar);
});
var wasSymlink = true;
try {
// Delete the symlink if it was one.
fs.unlinkSync(nestedCordovaLibPath);
} catch (e) {
wasSymlink = false;
}
// Delete old library project if it existed.
if (shared) {
shell.rm('-rf', nestedCordovaLibPath);
} else if (!wasSymlink) {
// Delete only the src, since Eclipse / Android Studio can't handle their project files being deleted.
shell.rm('-rf', path.join(nestedCordovaLibPath, 'src'));
}
});
if (shared) {
var relativeFrameworkPath = path.relative(projectPath, getFrameworkDir(projectPath, true));
fs.symlinkSync(relativeFrameworkPath, nestedCordovaLibPath, 'dir');
} else {
shell.mkdir('-p', nestedCordovaLibPath);
shell.cp('-f', path.join(ROOT, 'framework', 'AndroidManifest.xml'), nestedCordovaLibPath);
shell.cp('-f', path.join(ROOT, 'framework', 'project.properties'), nestedCordovaLibPath);
shell.cp('-f', path.join(ROOT, 'framework', 'build.gradle'), nestedCordovaLibPath);
shell.cp('-f', path.join(ROOT, 'framework', 'cordova.gradle'), nestedCordovaLibPath);
shell.cp('-r', path.join(ROOT, 'framework', 'src'), nestedCordovaLibPath);
fs.ensureDirSync(nestedCordovaLibPath);
fs.copySync(path.join(ROOT, 'framework', 'AndroidManifest.xml'), path.join(nestedCordovaLibPath, 'AndroidManifest.xml'));
fs.copySync(path.join(ROOT, 'framework', 'project.properties'), 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', 'src'), path.join(nestedCordovaLibPath, 'src'));
}
}
@ -150,12 +122,12 @@ function copyBuildRules (projectPath, isLegacy) {
if (isLegacy) {
// The project's build.gradle is identical to the earlier build.gradle, so it should still work
shell.cp('-f', path.join(srcDir, 'legacy', 'build.gradle'), projectPath);
shell.cp('-f', path.join(srcDir, 'wrapper.gradle'), projectPath);
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 {
shell.cp('-f', path.join(srcDir, 'build.gradle'), projectPath);
shell.cp('-f', path.join(srcDir, 'app', 'build.gradle'), path.join(projectPath, 'app'));
shell.cp('-f', path.join(srcDir, 'wrapper.gradle'), projectPath);
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, 'wrapper.gradle'), path.join(projectPath, 'wrapper.gradle'));
}
}
@ -164,24 +136,29 @@ function copyScripts (projectPath) {
var srcScriptsDir = path.join(bin, 'templates', 'cordova');
var destScriptsDir = path.join(projectPath, 'cordova');
// Delete old scripts directory if this is an update.
shell.rm('-rf', destScriptsDir);
fs.removeSync(destScriptsDir);
// Copy in the new ones.
shell.cp('-r', srcScriptsDir, projectPath);
fs.copySync(srcScriptsDir, destScriptsDir);
let nodeModulesDir = path.join(ROOT, 'node_modules');
if (fs.existsSync(nodeModulesDir)) shell.cp('-r', nodeModulesDir, destScriptsDir);
if (fs.existsSync(nodeModulesDir)) fs.copySync(nodeModulesDir, path.join(destScriptsDir, 'node_modules'));
fs.copySync(path.join(bin, 'check_reqs'), path.join(destScriptsDir, 'check_reqs'));
fs.copySync(path.join(bin, 'check_reqs.bat'), path.join(destScriptsDir, 'check_reqs.bat'));
fs.copySync(path.join(bin, 'android_sdk_version'), path.join(destScriptsDir, 'android_sdk_version'));
fs.copySync(path.join(bin, 'android_sdk_version.bat'), path.join(destScriptsDir, 'android_sdk_version.bat'));
shell.cp(path.join(bin, 'check_reqs*'), destScriptsDir);
shell.cp(path.join(bin, 'android_sdk_version*'), destScriptsDir);
var check_reqs = path.join(destScriptsDir, 'check_reqs');
var android_sdk_version = path.join(destScriptsDir, 'android_sdk_version');
// TODO: the two files being edited on-the-fly here are shared between
// platform and project-level commands. the below `sed` is updating the
// platform and project-level commands. the below is updating the
// `require` path for the two libraries. if there's a better way to share
// modules across both the repo and generated projects, we should make sure
// to remove/update this.
shell.sed('-i', /templates\/cordova\//, '', android_sdk_version);
shell.sed('-i', /templates\/cordova\//, '', check_reqs);
let templatesCordovaRegex = /templates\/cordova\//;
utils.replaceFileContents(android_sdk_version, templatesCordovaRegex, '');
utils.replaceFileContents(check_reqs, templatesCordovaRegex, '');
}
/**
@ -273,51 +250,50 @@ exports.create = function (project_path, config, options, events) {
events.emit('verbose', 'Copying android template project to ' + project_path);
exports.setShellFatal(true, function () {
var project_template_dir = options.customTemplate || path.join(ROOT, 'bin', 'templates', 'project');
var app_path = path.join(project_path, 'app', 'src', 'main');
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
shell.mkdir('-p', app_path);
shell.cp('-r', path.join(project_template_dir, 'assets'), app_path);
shell.cp('-r', path.join(project_template_dir, 'res'), app_path);
shell.cp(path.join(project_template_dir, 'gitignore'), path.join(project_path, '.gitignore'));
// 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).
shell.mkdir(path.join(app_path, 'libs'));
// 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
exports.copyJsAndLibrary(project_path, options.link, safe_activity_name);
// copy cordova.js, cordova.jar
exports.copyJsAndLibrary(project_path, options.link, safe_activity_name);
// 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');
shell.mkdir('-p', java_path);
shell.mkdir('-p', assets_path);
shell.mkdir('-p', resource_path);
// 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');
// 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');
shell.mkdir('-p', activity_dir);
shell.cp('-f', path.join(project_template_dir, 'Activity.java'), activity_path);
shell.sed('-i', /__ACTIVITY__/, safe_activity_name, activity_path);
shell.sed('-i', /__NAME__/, project_name, path.join(app_path, 'res', 'values', 'strings.xml'));
shell.sed('-i', /__ID__/, package_name, activity_path);
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 = 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);
var manifest_path = path.join(app_path, 'AndroidManifest.xml');
manifest.write(manifest_path);
exports.copyScripts(project_path);
exports.copyBuildRules(project_path);
exports.copyScripts(project_path);
exports.copyBuildRules(project_path);
});
// Link it to local android install.
exports.writeProjectProperties(project_path, target_api);
exports.prepBuildFiles(project_path);

47
bin/lib/utils.js Normal file
View File

@ -0,0 +1,47 @@
/*
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.
*/
/*
Provides a set of utility methods, which can also be spied on during unit tests.
*/
// TODO: Perhaps this should live in cordova-common?
const fs = require('fs-extra');
/**
* Reads, searches, and replaces the found occurences with replacementString and then writes the file back out.
* A backup is not made.
*
* @param {string} file A file path to a readable & writable file
* @param {RegExp} searchRegex The search regex
* @param {string} replacementString The string to replace the found occurences
* @returns {void}
*/
exports.replaceFileContents = function (file, searchRegex, replacementString) {
// let contents;
try {
var contents = fs.readFileSync(file).toString();
} catch (ex) {
console.log('TRYING TO READ: ', file);
throw ex;
}
contents = contents.replace(searchRegex, replacementString);
fs.writeFileSync(file, contents);
};

View File

@ -264,45 +264,23 @@ module.exports.findBestApkForArchitecture = function (buildResults, arch) {
};
function PackageInfo (keystore, alias, storePassword, password, keystoreType) {
this.keystore = {
'name': 'key.store',
'value': keystore
};
this.alias = {
'name': 'key.alias',
'value': alias
};
if (storePassword) {
this.storePassword = {
'name': 'key.store.password',
'value': storePassword
};
}
if (password) {
this.password = {
'name': 'key.alias.password',
'value': password
};
}
if (keystoreType) {
this.keystoreType = {
'name': 'key.store.type',
'value': keystoreType
};
}
const createNameKeyObject = (name, value) => ({ name, value: value.replace(/\\/g, '\\\\') });
this.data = [
createNameKeyObject('key.store', keystore),
createNameKeyObject('key.alias', alias)
];
if (storePassword) this.data.push(createNameKeyObject('key.store.password', storePassword));
if (password) this.data.push(createNameKeyObject('key.alias.password', password));
if (keystoreType) this.data.push(createNameKeyObject('key.store.type', keystoreType));
}
PackageInfo.prototype = {
toProperties: function () {
var self = this;
var result = '';
Object.keys(self).forEach(function (key) {
result += self[key].name;
result += '=';
result += self[key].value.replace(/\\/g, '\\\\');
result += '\n';
});
return result;
appendToProperties: function (propertiesParser) {
for (const { name, value } of this.data) propertiesParser.set(name, value);
propertiesParser.save();
}
};

View File

@ -17,15 +17,15 @@
under the License.
*/
var fs = require('fs');
var fs = require('fs-extra');
var path = require('path');
var shell = require('shelljs');
const execa = require('execa');
var events = require('cordova-common').events;
var CordovaError = require('cordova-common').CordovaError;
var check_reqs = require('../check_reqs');
var PackageType = require('../PackageType');
const compareFunc = require('compare-func');
const { createEditor } = require('properties-parser');
const MARKER = 'YOUR CHANGES WILL BE ERASED!';
const SIGNING_PROPERTIES = '-signing.properties';
@ -33,6 +33,67 @@ const TEMPLATE =
'# This file is automatically generated.\n' +
'# Do not modify this file -- ' + MARKER + '\n';
const fileSorter = compareFunc([
// Sort arch specific builds after generic ones
filePath => /-x86|-arm/.test(filePath),
// Sort unsigned builds after signed ones
filePath => /-unsigned/.test(filePath),
// Sort by file modification time, latest first
filePath => -fs.statSync(filePath).mtime.getTime(),
// Sort by file name length, ascending
'length'
]);
/**
* If the provided directory does not exist or extension is missing, return an empty array.
* If the director exists, loop the directories and collect list of files matching the extension.
*
* @param {String} dir Directory to scan
* @param {String} extension
*/
function recursivelyFindFiles (dir, extension) {
if (!fs.existsSync(dir) || !extension) return [];
const files = fs.readdirSync(dir, { withFileTypes: true })
.map(entry => {
const item = path.resolve(dir, entry.name);
if (entry.isDirectory()) return recursivelyFindFiles(item, extension);
if (path.extname(entry.name) === `.${extension}`) return item;
return false;
});
return Array.prototype.concat(...files)
.filter(file => file !== false);
}
/**
* @param {String} dir
* @param {String} build_type
* @param {String} arch
* @param {String} extension
*/
function findOutputFilesHelper (dir, build_type, arch, extension) {
let files = recursivelyFindFiles(path.resolve(dir, build_type), extension);
if (files.length === 0) return files;
// Assume arch-specific build if newest apk has -x86 or -arm.
let archSpecific = !!/-x86|-arm/.exec(path.basename(files[0]));
// And show only arch-specific ones (or non-arch-specific)
files = files.filter(p => !!/-x86|-arm/.exec(path.basename(p)) === archSpecific);
if (archSpecific && files.length > 1 && arch) {
files = files.filter(p => path.basename(p).indexOf('-' + arch) !== -1);
}
return files;
}
class ProjectBuilder {
constructor (rootDirectory) {
this.root = rootDirectory || path.resolve(__dirname, '../../..');
@ -131,7 +192,7 @@ class ProjectBuilder {
try {
fs.accessSync(subProjectGradle, fs.F_OK);
} catch (e) {
shell.cp('-f', pluginBuildGradle, subProjectGradle);
fs.copySync(pluginBuildGradle, subProjectGradle);
}
};
@ -221,22 +282,25 @@ class ProjectBuilder {
return self.runGradleWrapper(gradlePath);
}).then(function () {
return self.prepBuildFiles();
}).then(function () {
// If the gradle distribution URL is set, make sure it points to version we want.
// If it's not set, do nothing, assuming that we're using a future version of gradle that we don't want to mess with.
// For some reason, using ^ and $ don't work. This does the job, though.
var distributionUrlRegex = /distributionUrl.*zip/;
var distributionUrl = process.env['CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL'] || 'https\\://services.gradle.org/distributions/gradle-6.1-all.zip';
var gradleWrapperPropertiesPath = path.join(self.root, 'gradle', 'wrapper', 'gradle-wrapper.properties');
shell.chmod('u+w', gradleWrapperPropertiesPath);
shell.sed('-i', distributionUrlRegex, 'distributionUrl=' + distributionUrl, gradleWrapperPropertiesPath);
}).then(() => {
// update/set the distributionUrl in the gradle-wrapper.properties
const gradleWrapperPropertiesPath = path.join(self.root, 'gradle/wrapper/gradle-wrapper.properties');
const gradleWrapperProperties = createEditor(gradleWrapperPropertiesPath);
const distributionUrl = process.env['CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL'] || 'https://services.gradle.org/distributions/gradle-6.1-all.zip';
gradleWrapperProperties.set('distributionUrl', distributionUrl);
gradleWrapperProperties.save();
var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
var propertiesFilePath = path.join(self.root, propertiesFile);
events.emit('verbose', `Gradle Distribution URL: ${distributionUrl}`);
})
.then(() => {
const signingPropertiesPath = path.join(self.root, `${opts.buildType}${SIGNING_PROPERTIES}`);
if (fs.existsSync(signingPropertiesPath)) fs.removeSync(signingPropertiesPath);
if (opts.packageInfo) {
fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
} else if (isAutoGenerated(propertiesFilePath)) {
shell.rm('-f', propertiesFilePath);
fs.ensureFileSync(signingPropertiesPath);
const signingProperties = createEditor(signingPropertiesPath);
signingProperties.addHeadComment(TEMPLATE);
opts.packageInfo.appendToProperties(signingProperties);
}
});
}
@ -263,28 +327,29 @@ class ProjectBuilder {
}
clean (opts) {
var builder = this;
var wrapper = path.join(this.root, 'gradlew');
var args = builder.getArgs('clean', opts);
const wrapper = path.join(this.root, 'gradlew');
const args = this.getArgs('clean', opts);
return execa(wrapper, args, { stdio: 'inherit' })
.then(function () {
shell.rm('-rf', path.join(builder.root, 'out'));
.then(() => {
fs.removeSync(path.join(this.root, 'out'));
['debug', 'release'].forEach(function (config) {
var propertiesFilePath = path.join(builder.root, config + SIGNING_PROPERTIES);
if (isAutoGenerated(propertiesFilePath)) {
shell.rm('-f', propertiesFilePath);
}
});
['debug', 'release'].map(config => path.join(this.root, `${config}${SIGNING_PROPERTIES}`))
.forEach(file => {
const hasFile = fs.existsSync(file);
const hasMarker = hasFile && fs.readFileSync(file, 'utf8')
.includes(MARKER);
if (hasFile && hasMarker) fs.removeSync(file);
});
});
}
findOutputApks (build_type, arch) {
return findOutputApksHelper(this.apkDir, build_type, arch).sort(apkSorter);
return findOutputFilesHelper(this.apkDir, build_type, arch, 'apk').sort(fileSorter);
}
findOutputBundles (build_type) {
return findOutputBundlesHelper(this.aabDir, build_type);
return findOutputFilesHelper(this.aabDir, build_type, false, 'aab').sort(fileSorter);
}
fetchBuildResults (build_type, arch) {
@ -296,93 +361,3 @@ class ProjectBuilder {
}
module.exports = ProjectBuilder;
const apkSorter = compareFunc([
// Sort arch specific builds after generic ones
apkPath => /-x86|-arm/.test(apkPath),
// Sort unsigned builds after signed ones
apkPath => /-unsigned/.test(apkPath),
// Sort by file modification time, latest first
apkPath => -fs.statSync(apkPath).mtime.getTime(),
// Sort by file name length, ascending
'length'
]);
function findOutputApksHelper (dir, build_type, arch) {
var shellSilent = shell.config.silent;
shell.config.silent = true;
// list directory recursively
var ret = shell.ls('-R', dir).map(function (file) {
// ls does not include base directory
return path.join(dir, file);
}).filter(function (file) {
// find all APKs
return file.match(/\.apk?$/i);
}).filter(function (candidate) {
var apkName = path.basename(candidate);
// Need to choose between release and debug .apk.
if (build_type === 'debug') {
return /-debug/.exec(apkName) && !/-unaligned|-unsigned/.exec(apkName);
}
if (build_type === 'release') {
return /-release/.exec(apkName) && !/-unaligned/.exec(apkName);
}
return true;
}).sort(apkSorter);
shell.config.silent = shellSilent;
if (ret.length === 0) {
return ret;
}
// Assume arch-specific build if newest apk has -x86 or -arm.
var archSpecific = !!/-x86|-arm/.exec(path.basename(ret[0]));
// And show only arch-specific ones (or non-arch-specific)
ret = ret.filter(function (p) {
return !!/-x86|-arm/.exec(path.basename(p)) === archSpecific;
});
if (archSpecific && ret.length > 1 && arch) {
ret = ret.filter(function (p) {
return path.basename(p).indexOf('-' + arch) !== -1;
});
}
return ret;
}
// This method was a copy of findOutputApksHelper and modified to look for bundles
// While replacing shell with fs-extra, it might be a good idea to see if we can
// generalise these findOutput methods.
function findOutputBundlesHelper (dir, build_type) {
const shellSilent = shell.config.silent;
shell.config.silent = true;
// list directory recursively
const ret = shell.ls('-R', dir).map(function (file) {
return path.join(dir, file); // ls does not include base directory
}).filter(function (file) {
return file.match(/\.aab?$/i); // find all bundles
}).filter(function (candidate) {
// Need to choose between release and debug bundle.
if (build_type === 'debug') {
return /debug/.exec(candidate);
}
if (build_type === 'release') {
return /release/.exec(candidate);
}
return true;
});
shell.config.silent = shellSilent;
return ret;
}
function isAutoGenerated (file) {
return fs.existsSync(file) && fs.readFileSync(file, 'utf8').indexOf(MARKER) > 0;
}

View File

@ -20,10 +20,10 @@
*/
const execa = require('execa');
var shelljs = require('shelljs');
var path = require('path');
var fs = require('fs');
var fs = require('fs-extra');
var os = require('os');
var which = require('which');
var REPO_ROOT = path.join(__dirname, '..', '..', '..', '..');
var PROJECT_ROOT = path.join(__dirname, '..', '..');
const { CordovaError, ConfigParser, events } = require('cordova-common');
@ -31,11 +31,25 @@ var android_sdk = require('./android_sdk');
const { createEditor } = require('properties-parser');
function forgivingWhichSync (cmd) {
try {
return fs.realpathSync(shelljs.which(cmd));
} catch (e) {
return '';
let whichResult = which.sync(cmd, { nothrow: true });
// On null, returns empty string to maintain backwards compatibility
// realpathSync follows symlinks
return whichResult === null ? '' : fs.realpathSync(whichResult);
}
function getJDKDirectory (directory) {
let p = path.resolve(directory, 'java');
if (fs.existsSync(p)) {
let directories = fs.readdirSync(p);
for (let i = 0; i < directories.length; i++) {
let dir = directories[i];
if (/^(jdk)+./.test(dir)) {
return path.resolve(directory, 'java', dir);
}
}
}
return null;
}
module.exports.isWindows = function () {
@ -182,7 +196,6 @@ module.exports.check_java = function () {
});
} else {
// See if we can derive it from javac's location.
// fs.realpathSync is require on Ubuntu, which symplinks from /usr/bin -> JDK
var maybeJavaHome = path.dirname(path.dirname(javacPath));
if (fs.existsSync(path.join(maybeJavaHome, 'lib', 'tools.jar'))) {
process.env['JAVA_HOME'] = maybeJavaHome;
@ -191,16 +204,16 @@ module.exports.check_java = function () {
}
}
} else if (module.exports.isWindows()) {
// Try to auto-detect java in the default install paths.
var oldSilent = shelljs.config.silent;
shelljs.config.silent = true;
var firstJdkDir =
shelljs.ls(process.env['ProgramFiles'] + '\\java\\jdk*')[0] ||
shelljs.ls('C:\\Program Files\\java\\jdk*')[0] ||
shelljs.ls('C:\\Program Files (x86)\\java\\jdk*')[0];
shelljs.config.silent = oldSilent;
const programFilesEnv = path.resolve(process.env['ProgramFiles']);
const programFiles = 'C:\\Program Files\\';
const programFilesx86 = 'C:\\Program Files (x86)\\';
let firstJdkDir =
getJDKDirectory(programFilesEnv) ||
getJDKDirectory(programFiles) ||
getJDKDirectory(programFilesx86);
if (firstJdkDir) {
// shelljs always uses / in paths.
firstJdkDir = firstJdkDir.replace(/\//g, path.sep);
if (!javacPath) {
process.env['PATH'] += path.delimiter + path.join(firstJdkDir, 'bin');

View File

@ -20,6 +20,7 @@
*/
const execa = require('execa');
const fs = require('fs-extra');
var android_versions = require('android-versions');
var retry = require('./retry');
var build = require('./build');
@ -28,27 +29,25 @@ var Adb = require('./Adb');
var AndroidManifest = require('./AndroidManifest');
var events = require('cordova-common').events;
var CordovaError = require('cordova-common').CordovaError;
var shelljs = require('shelljs');
var android_sdk = require('./android_sdk');
var check_reqs = require('./check_reqs');
var which = require('which');
var os = require('os');
var fs = require('fs');
// constants
var ONE_SECOND = 1000; // in milliseconds
var ONE_MINUTE = 60 * ONE_SECOND; // in milliseconds
var INSTALL_COMMAND_TIMEOUT = 5 * ONE_MINUTE; // in milliseconds
var NUM_INSTALL_RETRIES = 3;
var CHECK_BOOTED_INTERVAL = 3 * ONE_SECOND; // in milliseconds
var EXEC_KILL_SIGNAL = 'SIGKILL';
const ONE_SECOND = 1000; // in milliseconds
const ONE_MINUTE = 60 * ONE_SECOND; // in milliseconds
const INSTALL_COMMAND_TIMEOUT = 5 * ONE_MINUTE; // in milliseconds
const NUM_INSTALL_RETRIES = 3;
const CHECK_BOOTED_INTERVAL = 3 * ONE_SECOND; // in milliseconds
const EXEC_KILL_SIGNAL = 'SIGKILL';
function forgivingWhichSync (cmd) {
try {
return fs.realpathSync(shelljs.which(cmd));
} catch (e) {
return '';
}
let whichResult = which.sync(cmd, { nothrow: true });
// On null, returns empty string to maintain backwards compatibility
// realpathSync follows symlinks
return whichResult === null ? '' : fs.realpathSync(whichResult);
}
module.exports.list_images_using_avdmanager = function () {
@ -290,7 +289,7 @@ module.exports.start = function (emulator_ID, boot_timeout) {
return self.get_available_port().then(function (port) {
// Figure out the directory the emulator binary runs in, and set the cwd to that directory.
// Workaround for https://code.google.com/p/android/issues/detail?id=235461
var emulator_dir = path.dirname(shelljs.which('emulator'));
var emulator_dir = path.dirname(which.sync('emulator'));
var args = ['-avd', emulatorId, '-port', port];
// Don't wait for it to finish, since the emulator will probably keep running for a long time.
execa('emulator', args, { stdio: 'inherit', detached: true, cwd: emulator_dir })

View File

@ -14,9 +14,8 @@
*
*/
var fs = require('fs');
var fs = require('fs-extra');
var path = require('path');
var shell = require('shelljs');
var events = require('cordova-common').events;
var CordovaError = require('cordova-common').CordovaError;
@ -42,7 +41,7 @@ var handlers = {
deleteJava(project.projectDir, dest);
} else {
// Just remove the file, not the whole parent directory
removeFile(project.projectDir, dest);
removeFile(path.resolve(project.projectDir, dest));
}
}
},
@ -53,7 +52,7 @@ var handlers = {
},
uninstall: function (obj, plugin, project, options) {
var dest = path.join('app/libs', path.basename(obj.src));
removeFile(project.projectDir, dest);
removeFile(path.resolve(project.projectDir, dest));
}
},
'resource-file': {
@ -63,7 +62,7 @@ var handlers = {
},
uninstall: function (obj, plugin, project, options) {
var dest = path.join('app', 'src', 'main', obj.target);
removeFile(project.projectDir, dest);
removeFile(path.resolve(project.projectDir, dest));
}
},
'framework': {
@ -102,7 +101,7 @@ var handlers = {
if (obj.custom) {
var subRelativeDir = project.getCustomSubprojectRelativeDir(plugin.id, src);
removeFile(project.projectDir, subRelativeDir);
removeFile(path.resolve(project.projectDir, subRelativeDir));
subDir = path.resolve(project.projectDir, subRelativeDir);
// If it's the last framework in the plugin, remove the parent directory.
var parDir = path.dirname(subDir);
@ -143,12 +142,12 @@ var handlers = {
if (!target) throw new CordovaError(generateAttributeError('target', 'asset', plugin.id));
removeFileF(path.resolve(project.www, target));
removeFileF(path.resolve(project.www, 'plugins', plugin.id));
removeFile(path.resolve(project.www, target));
removeFile(path.resolve(project.www, 'plugins', plugin.id));
if (options && options.usePlatformWww) {
// CB-11022 remove file from both directories if usePlatformWww is specified
removeFileF(path.resolve(project.platformWww, target));
removeFileF(path.resolve(project.platformWww, 'plugins', plugin.id));
removeFile(path.resolve(project.platformWww, target));
removeFile(path.resolve(project.platformWww, 'plugins', plugin.id));
}
}
},
@ -166,13 +165,13 @@ var handlers = {
scriptContent = 'cordova.define("' + moduleName + '", function(require, exports, module) {\n' + scriptContent + '\n});\n';
var wwwDest = path.resolve(project.www, 'plugins', plugin.id, obj.src);
shell.mkdir('-p', path.dirname(wwwDest));
fs.ensureDirSync(path.dirname(wwwDest));
fs.writeFileSync(wwwDest, scriptContent, 'utf-8');
if (options && options.usePlatformWww) {
// CB-11022 copy file to both directories if usePlatformWww is specified
var platformWwwDest = path.resolve(project.platformWww, 'plugins', plugin.id, obj.src);
shell.mkdir('-p', path.dirname(platformWwwDest));
fs.ensureDirSync(path.dirname(platformWwwDest));
fs.writeFileSync(platformWwwDest, scriptContent, 'utf-8');
}
},
@ -217,14 +216,11 @@ function copyFile (plugin_dir, src, project_dir, dest, link) {
// 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'); }
shell.mkdir('-p', path.dirname(dest));
fs.ensureDirSync(path.dirname(dest));
if (link) {
symlinkFileOrDirTree(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', src + '/*', dest);
} else {
shell.cp('-f', src, dest);
fs.copySync(src, dest);
}
}
@ -238,11 +234,11 @@ function copyNewFile (plugin_dir, src, project_dir, dest, link) {
function symlinkFileOrDirTree (src, dest) {
if (fs.existsSync(dest)) {
shell.rm('-Rf', dest);
fs.removeSync(dest);
}
if (fs.statSync(src).isDirectory()) {
shell.mkdir('-p', dest);
fs.ensureDirSync(path.dirname(dest));
fs.readdirSync(src).forEach(function (entry) {
symlinkFileOrDirTree(path.join(src, entry), path.join(dest, entry));
});
@ -251,15 +247,8 @@ function symlinkFileOrDirTree (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);
}
// deletes file/directory without checking
function removeFileF (file) {
shell.rm('-Rf', file);
function removeFile (file) {
fs.removeSync(file);
}
// Sometimes we want to remove some java, and prune any unnecessary empty directories
@ -272,7 +261,7 @@ function removeFileAndParents (baseDir, destFile, stopper) {
var file = path.resolve(baseDir, destFile);
if (!fs.existsSync(file)) return;
removeFileF(file);
removeFile(file);
// check if directory is empty
var curDir = path.dirname(file);

View File

@ -17,9 +17,8 @@
under the License.
*/
var fs = require('fs');
var fs = require('fs-extra');
var path = require('path');
var shell = require('shelljs');
var events = require('cordova-common').events;
var AndroidManifest = require('./AndroidManifest');
var checkReqs = require('./check_reqs');
@ -30,6 +29,7 @@ var FileUpdater = require('cordova-common').FileUpdater;
var PlatformJson = require('cordova-common').PlatformJson;
var PlatformMunger = require('cordova-common').ConfigChanges.PlatformMunger;
var PluginInfoProvider = require('cordova-common').PluginInfoProvider;
let utils = require('./utils');
const GradlePropertiesParser = require('./config/GradlePropertiesParser');
@ -120,7 +120,7 @@ function updateConfigFilesFrom (sourceConfig, configMunger, locations) {
// 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)
@ -222,9 +222,10 @@ function updateProjectAccordingTo (platformConfig, locations) {
.write();
// Java file paths shouldn't be hard coded
var javaPattern = path.join(locations.javaSrc, manifestId.replace(/\./g, '/'), '*.java');
var java_files = shell.ls(javaPattern).filter(function (f) {
return shell.grep(/extends\s+CordovaActivity/g, f);
let javaDirectory = path.join(locations.javaSrc, manifestId.replace(/\./g, '/'));
let javaPattern = /\.java$/;
let java_files = utils.scanDirectory(javaDirectory, javaPattern, true).filter(function (f) {
return utils.grep(f, /extends\s+CordovaActivity/g) !== null;
});
if (java_files.length === 0) {
@ -233,9 +234,15 @@ function updateProjectAccordingTo (platformConfig, locations) {
events.emit('log', 'Multiple candidate Java files that extend CordovaActivity found. Guessing at the first one, ' + java_files[0]);
}
var destFile = path.join(locations.root, 'app', 'src', 'main', 'java', androidPkgName.replace(/\./g, '/'), path.basename(java_files[0]));
shell.mkdir('-p', path.dirname(destFile));
shell.sed(/package [\w.]*;/, 'package ' + androidPkgName + ';', java_files[0]).to(destFile);
let destFile = java_files[0];
// var destFile = path.join(locations.root, 'app', 'src', 'main', 'java', androidPkgName.replace(/\./g, '/'), path.basename(java_files[0]));
// fs.ensureDirSync(path.dirname(destFile));
// events.emit('verbose', java_files[0]);
// events.emit('verbose', destFile);
// console.log(locations);
// fs.copySync(java_files[0], destFile);
utils.replaceFileContents(destFile, /package [\w.]*;/, 'package ' + androidPkgName + ';');
events.emit('verbose', 'Wrote out Android package name "' + androidPkgName + '" to ' + destFile);
var removeOrigPkg = checkReqs.isWindows() || checkReqs.isDarwin() ?
@ -244,7 +251,7 @@ function updateProjectAccordingTo (platformConfig, locations) {
if (removeOrigPkg) {
// If package was name changed we need to remove old java with main activity
shell.rm('-Rf', java_files[0]);
fs.removeSync(java_files[0]);
// remove any empty directories
var currentDir = path.dirname(java_files[0]);
var sourcesRoot = path.resolve(locations.root, 'src');
@ -637,9 +644,10 @@ function cleanIcons (projectRoot, projectConfig, platformResourcesDir) {
* Gets a map containing resources of a specified name from all drawable folders in a directory.
*/
function mapImageResources (rootDir, subDir, type, resourceName) {
var pathMap = {};
shell.ls(path.join(rootDir, subDir, type + '-*')).forEach(function (drawableFolder) {
var imagePath = path.join(subDir, path.basename(drawableFolder), resourceName);
let pathMap = {};
let pattern = new RegExp(type + '+-.+');
utils.scanDirectory(path.join(rootDir, subDir), pattern).forEach(function (drawableFolder) {
let imagePath = path.join(subDir, path.basename(drawableFolder), resourceName);
pathMap[imagePath] = null;
});
return pathMap;

97
bin/templates/cordova/lib/utils.js vendored Normal file
View File

@ -0,0 +1,97 @@
/*
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.
*/
/*
Provides a set of utility methods, which can also be spied on during unit tests.
*/
// TODO: Perhaps this should live in cordova-common?
const fs = require('fs-extra');
const path = require('path');
/**
* Reads, searches, and replaces the found occurences with replacementString and then writes the file back out.
* A backup is not made.
*
* @param {string} file A file path to a readable & writable file
* @param {RegExp} searchRegex The search regex
* @param {string} replacementString The string to replace the found occurences
* @returns void
*/
exports.replaceFileContents = function (file, searchRegex, replacementString) {
let contents = fs.readFileSync(file).toString();
contents = contents.replace(searchRegex, replacementString);
fs.writeFileSync(file, contents);
};
/**
* Reads a file and scans for regex. Returns the line of the first occurence or null if no occurences are found.
*
* @param {string} file A file path
* @param {RegExp} regex A search regex
* @returns string|null
*/
exports.grep = function (file, regex) {
let contents = fs.readFileSync(file).toString().replace(/\\r/g, '').split('\n');
for (let i = 0; i < contents.length; i++) {
let line = contents[i];
if (regex.test(line)) {
return line;
}
}
return null;
};
/**
* Scans directories and outputs a list of found paths that matches the regex
*
* @param {string} directory The starting directory
* @param {RegExp} regex The search regex
* @param {boolean} recursive Enables recursion
* @returns Array<string>
*/
exports.scanDirectory = function (directory, regex, recursive) {
let output = [];
if (fs.existsSync(directory)) {
let items = fs.readdirSync(directory);
for (let i = 0; i < items.length; i++) {
let item = items[i];
let itemPath = path.join(directory, item);
let stats = fs.statSync(itemPath);
if (regex.test(itemPath)) {
output.push(itemPath);
}
if (stats.isDirectory()) {
if (recursive) {
output = output.concat(exports.scanDirectory(itemPath, regex, recursive));
} else {
// Move onto the next item
continue;
}
}
}
}
return output;
};

View File

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="output" path="bin/classes"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>

View File

@ -34,9 +34,10 @@
"compare-func": "^1.3.2",
"cordova-common": "^3.2.0",
"execa": "^3.2.0",
"fs-extra": "^8.1.0",
"nopt": "^4.0.1",
"properties-parser": "^0.3.1",
"shelljs": "^0.5.3"
"which": "^1.3.1"
},
"devDependencies": {
"eslint": "^5.12.0",

View File

@ -0,0 +1,150 @@
/*
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 PluginInfoProvider = require('cordova-common').PluginInfoProvider;
let fs = require('fs-extra');
var cp = require('child_process');
var path = require('path');
var util = require('util');
var cordova_bin = path.join(__dirname, '../../../bin');
/**
* Creates a project using platform create script with given parameters
* @param {string} projectname - name of the project
* @param {string} projectid - id of the project
* @param {string} platformpath - path to the platform
* @param {function} callback - function which is called (without arguments) when the project is created or (with error object) when error occurs
*/
module.exports.createProject = function (projectname, projectid, platformpath, callback) {
// platformpath is optional
if (!callback && typeof platformpath === 'function') {
callback = platformpath;
platformpath = null;
}
var projectDirName = getDirName(projectid);
var createScriptPath = platformpath ? path.join(platformpath, 'bin/create') : path.join(cordova_bin, 'create');
// remove existing folder
module.exports.removeProject(projectid);
// create the project
var command = util.format('"%s" %s %s "%s"', createScriptPath, projectDirName, projectid, projectname);
cp.exec(command, function (error, stdout, stderr) {
if (error) {
console.log(stdout);
console.error(stderr);
}
callback(error);
});
};
/**
* Updates a project using platform update script with given parameters
* @param {string} projectid - id of the project
* @param {string} platformpath - path to the platform
* @param {function} callback - function which is called (without arguments) when the project is updated or (with error object) when error occurs
*/
module.exports.updateProject = function (projectid, platformpath, callback) {
// platformpath is optional
if (!callback && typeof platformpath === 'function') {
callback = platformpath;
platformpath = null;
}
var projectDirName = getDirName(projectid);
var updateScriptPath = platformpath ? path.join(platformpath, 'bin/update') : path.join(cordova_bin, 'update');
var command = util.format('"%s" %s', updateScriptPath, projectDirName);
cp.exec(command, function (error, stdout, stderr) {
if (error) {
console.log(stdout);
console.error(stderr);
}
callback(error);
});
};
/**
* Builds a project using platform build script with given parameters
* @param {string} projectid - id of the project
* @param {function} callback - function which is called (without arguments) when the project is built or (with error object) when error occurs
*/
module.exports.buildProject = function (projectid, callback) {
var projectDirName = getDirName(projectid);
var command = path.join(projectDirName, 'cordova/build');
cp.exec(command, function (error, stdout, stderr) {
if (error) {
console.log(stdout);
console.error(stderr);
}
callback(error);
});
};
/**
* Removes a project
* @param {string} projectid - id of the project
*/
module.exports.removeProject = function (projectid) {
var projectDirName = getDirName(projectid);
fs.removeSync(projectDirName);
};
/**
* Add a plugin to a project using platform api
* @param {string} projectid - id of the project
* @param {string} plugindir - path to a plugin
* @param {function} callback - function which is called (without arguments) when the plugin is added or (with error object) when error occurs
*/
module.exports.addPlugin = function (projectid, plugindir, callback) {
var projectDirName = getDirName(projectid);
var pip = new PluginInfoProvider();
var pluginInfo = pip.get(plugindir);
var Api = require(path.join(__dirname, '../../..', projectDirName, 'cordova', 'Api.js'));
var api = new Api('android', projectDirName);
api.addPlugin(pluginInfo).then(function () {
callback(null);
}, function (error) {
console.error(error);
callback(error);
});
};
/**
* Gets a version number from project using platform script
* @param {string} projectid - id of the project
* @param {function} callback - function which is called with platform version as an argument
*/
module.exports.getPlatformVersion = function (projectid, callback) {
var command = path.join(getDirName(projectid), 'cordova/version');
cp.exec(command, function (error, stdout, stderr) {
if (error) {
console.log(stdout);
console.error(stderr);
}
callback(stdout.trim());
});
};
function getDirName (projectid) {
return 'test-' + projectid;
}

View File

@ -18,9 +18,8 @@
*/
const os = require('os');
const fs = require('fs');
const fs = require('fs-extra');
const path = require('path');
const shell = require('shelljs');
const execa = require('execa');
const { PluginInfoProvider } = require('cordova-common');
@ -34,7 +33,7 @@ describe('plugin add', function () {
tmpDir = fs.realpathSync(fs.mkdtempSync(tmpDirTemplate));
});
afterEach(() => {
shell.rm('-rf', tmpDir);
fs.removeSync(tmpDir);
});
it('Test#001 : create project and add a plugin with framework', function () {

View File

@ -17,7 +17,7 @@
under the License.
*/
const fs = require('fs');
const fs = require('fs-extra');
const path = require('path');
const rewire = require('rewire');
@ -215,20 +215,20 @@ describe('ProjectBuilder', () => {
});
describe('clean', () => {
let shellSpy;
beforeEach(() => {
shellSpy = jasmine.createSpyObj('shell', ['rm']);
ProjectBuilder.__set__('shell', shellSpy);
const marker = ProjectBuilder.__get__('MARKER');
spyOn(fs, 'readFileSync').and.returnValue(`Some Header Here: ${marker}`);
spyOn(fs, 'removeSync');
spyOn(builder, 'getArgs');
execaSpy.and.returnValue(Promise.resolve());
});
it('should get arguments for cleaning', () => {
const opts = {};
builder.clean(opts);
expect(builder.getArgs).toHaveBeenCalledWith('clean', opts);
return builder.clean(opts).then(() => {
expect(builder.getArgs).toHaveBeenCalledWith('clean', opts);
});
});
it('should spawn gradle', () => {
@ -243,7 +243,7 @@ describe('ProjectBuilder', () => {
it('should remove "out" folder', () => {
return builder.clean({}).then(() => {
expect(shellSpy.rm).toHaveBeenCalledWith('-rf', path.join(rootDir, 'out'));
expect(fs.removeSync).toHaveBeenCalledWith(path.join(rootDir, 'out'));
});
});
@ -251,13 +251,11 @@ describe('ProjectBuilder', () => {
const debugSigningFile = path.join(rootDir, 'debug-signing.properties');
const releaseSigningFile = path.join(rootDir, 'release-signing.properties');
const isAutoGeneratedSpy = jasmine.createSpy('isAutoGenerated');
ProjectBuilder.__set__('isAutoGenerated', isAutoGeneratedSpy);
isAutoGeneratedSpy.and.returnValue(true);
spyOn(fs, 'existsSync').and.returnValue(true);
return builder.clean({}).then(() => {
expect(shellSpy.rm).toHaveBeenCalledWith(jasmine.any(String), debugSigningFile);
expect(shellSpy.rm).toHaveBeenCalledWith(jasmine.any(String), releaseSigningFile);
expect(fs.removeSync).toHaveBeenCalledWith(debugSigningFile);
expect(fs.removeSync).toHaveBeenCalledWith(releaseSigningFile);
});
});
@ -265,18 +263,16 @@ describe('ProjectBuilder', () => {
const debugSigningFile = path.join(rootDir, 'debug-signing.properties');
const releaseSigningFile = path.join(rootDir, 'release-signing.properties');
const isAutoGeneratedSpy = jasmine.createSpy('isAutoGenerated');
ProjectBuilder.__set__('isAutoGenerated', isAutoGeneratedSpy);
isAutoGeneratedSpy.and.returnValue(false);
spyOn(fs, 'existsSync').and.returnValue(false);
return builder.clean({}).then(() => {
expect(shellSpy.rm).not.toHaveBeenCalledWith(jasmine.any(String), debugSigningFile);
expect(shellSpy.rm).not.toHaveBeenCalledWith(jasmine.any(String), releaseSigningFile);
expect(fs.removeSync).not.toHaveBeenCalledWith(debugSigningFile);
expect(fs.removeSync).not.toHaveBeenCalledWith(releaseSigningFile);
});
});
});
describe('apkSorter', () => {
describe('fileSorter', () => {
it('should sort APKs from most recent to oldest, deprioritising unsigned arch-specific builds', () => {
const APKs = {
'app-debug.apk': new Date('2018-04-20'),
@ -291,40 +287,14 @@ describe('ProjectBuilder', () => {
const expectedResult = ['app-release.apk', 'app-debug.apk', 'app-release-unsigned.apk',
'app-release-arm.apk', 'app-release-x86.apk', 'app-debug-x86.apk', 'app-debug-arm.apk'];
const fsSpy = jasmine.createSpyObj('fs', ['statSync']);
fsSpy.statSync.and.callFake(filename => {
spyOn(fs, 'statSync').and.callFake(filename => {
return { mtime: APKs[filename] };
});
ProjectBuilder.__set__('fs', fsSpy);
const apkArray = Object.keys(APKs);
const sortedApks = apkArray.sort(ProjectBuilder.__get__('apkSorter'));
const sortedApks = apkArray.sort(ProjectBuilder.__get__('fileSorter'));
expect(sortedApks).toEqual(expectedResult);
});
});
describe('isAutoGenerated', () => {
let fsSpy;
beforeEach(() => {
fsSpy = jasmine.createSpyObj('fs', ['existsSync', 'readFileSync']);
fsSpy.existsSync.and.returnValue(true);
ProjectBuilder.__set__('fs', fsSpy);
});
it('should return true if the file contains the autogenerated marker', () => {
const fileContents = `# DO NOT MODIFY - YOUR CHANGES WILL BE ERASED!`;
fsSpy.readFileSync.and.returnValue(fileContents);
expect(ProjectBuilder.__get__('isAutoGenerated')()).toBe(true);
});
it('should return false if the file does not contain the autogenerated marker', () => {
const fileContents = `# My modified file`;
fsSpy.readFileSync.and.returnValue(fileContents);
expect(ProjectBuilder.__get__('isAutoGenerated')()).toBe(false);
});
});
});

View File

@ -20,10 +20,10 @@
var rewire = require('rewire');
var check_reqs = rewire('../../bin/templates/cordova/lib/check_reqs');
var android_sdk = require('../../bin/templates/cordova/lib/android_sdk');
var shelljs = require('shelljs');
var fs = require('fs');
var fs = require('fs-extra');
var path = require('path');
var events = require('cordova-common').events;
var which = require('which');
// This should match /bin/templates/project/build.gradle
const DEFAULT_TARGET_API = 29;
@ -46,7 +46,7 @@ describe('check_reqs', function () {
});
describe('even if no Android binaries are on the PATH', function () {
beforeEach(function () {
spyOn(shelljs, 'which').and.returnValue(null);
spyOn(which, 'sync').and.returnValue(null);
spyOn(fs, 'existsSync').and.returnValue(true);
});
it('it should set ANDROID_HOME on Windows', () => {
@ -79,7 +79,7 @@ describe('check_reqs', function () {
});
it('should set ANDROID_HOME based on `android` command if command exists in a SDK-like directory structure', () => {
spyOn(fs, 'existsSync').and.returnValue(true);
spyOn(shelljs, 'which').and.callFake(function (cmd) {
spyOn(which, 'sync').and.callFake(function (cmd) {
if (cmd === 'android') {
return '/android/sdk/tools/android';
} else {
@ -91,7 +91,7 @@ describe('check_reqs', function () {
});
});
it('should error out if `android` command exists in a non-SDK-like directory structure', () => {
spyOn(shelljs, 'which').and.callFake(function (cmd) {
spyOn(which, 'sync').and.callFake(function (cmd) {
if (cmd === 'android') {
return '/just/some/random/path/android';
} else {
@ -107,7 +107,7 @@ describe('check_reqs', function () {
});
it('should set ANDROID_HOME based on `adb` command if command exists in a SDK-like directory structure', () => {
spyOn(fs, 'existsSync').and.returnValue(true);
spyOn(shelljs, 'which').and.callFake(function (cmd) {
spyOn(which, 'sync').and.callFake(function (cmd) {
if (cmd === 'adb') {
return '/android/sdk/platform-tools/adb';
} else {
@ -119,7 +119,7 @@ describe('check_reqs', function () {
});
});
it('should error out if `adb` command exists in a non-SDK-like directory structure', () => {
spyOn(shelljs, 'which').and.callFake(function (cmd) {
spyOn(which, 'sync').and.callFake(function (cmd) {
if (cmd === 'adb') {
return '/just/some/random/path/adb';
} else {
@ -135,7 +135,7 @@ describe('check_reqs', function () {
});
it('should set ANDROID_HOME based on `avdmanager` command if command exists in a SDK-like directory structure', () => {
spyOn(fs, 'existsSync').and.returnValue(true);
spyOn(shelljs, 'which').and.callFake(function (cmd) {
spyOn(which, 'sync').and.callFake(function (cmd) {
if (cmd === 'avdmanager') {
return '/android/sdk/tools/bin/avdmanager';
} else {
@ -147,7 +147,7 @@ describe('check_reqs', function () {
});
});
it('should error out if `avdmanager` command exists in a non-SDK-like directory structure', () => {
spyOn(shelljs, 'which').and.callFake(function (cmd) {
spyOn(which, 'sync').and.callFake(function (cmd) {
if (cmd === 'avdmanager') {
return '/just/some/random/path/avdmanager';
} else {
@ -165,7 +165,7 @@ describe('check_reqs', function () {
});
describe('set PATH for various Android binaries if not available', function () {
beforeEach(function () {
spyOn(shelljs, 'which').and.returnValue(null);
spyOn(which, 'sync').and.returnValue(null);
process.env.ANDROID_HOME = 'let the children play';
spyOn(fs, 'existsSync').and.returnValue(true);
});

View File

@ -18,11 +18,11 @@
*/
var rewire = require('rewire');
var utils = require('../../bin/lib/utils');
var create = rewire('../../bin/lib/create');
var check_reqs = require('../../bin/templates/cordova/lib/check_reqs');
var fs = require('fs');
var fs = require('fs-extra');
var path = require('path');
var shell = require('shelljs');
describe('create', function () {
describe('validatePackageName helper method', function () {
@ -53,26 +53,33 @@ describe('create', function () {
it('should reject empty package names', () => {
return expectPackageNameToBeRejected('');
});
it('should reject package names containing "class"', () => {
return expectPackageNameToBeRejected('com.class.is.bad');
});
it('should reject package names that do not start with a latin letter', () => {
return expectPackageNameToBeRejected('_un.der.score');
});
it('should reject package names with terms that do not start with a latin letter', () => {
return expectPackageNameToBeRejected('un._der.score');
});
it('should reject package names containing non-alphanumeric or underscore characters', () => {
return expectPackageNameToBeRejected('th!$.!$.b@d');
});
it('should reject package names that do not contain enough dots', () => {
return expectPackageNameToBeRejected('therearenodotshere');
});
it('should reject package names that end with a dot', () => {
return expectPackageNameToBeRejected('this.is.a.complete.sentence.');
});
});
});
describe('validateProjectName helper method', function () {
describe('happy path (valid project names)', function () {
var valid = [
@ -90,6 +97,7 @@ describe('create', function () {
});
});
});
describe('failure cases (invalid project names)', function () {
it('should reject empty project names', () => {
return create.validateProjectName('').then(() => {
@ -101,6 +109,7 @@ describe('create', function () {
});
});
});
describe('main method', function () {
var config_mock;
var events_mock;
@ -110,6 +119,7 @@ describe('create', function () {
var app_path = path.join(project_path, 'app', 'src', 'main');
var default_templates = path.join(__dirname, '..', '..', 'bin', 'templates', 'project');
var fake_android_target = 'android-1337';
beforeEach(function () {
Manifest_mock.prototype = jasmine.createSpyObj('AndroidManifest instance mock', ['setPackageId', 'getActivity', 'setName', 'write']);
Manifest_mock.prototype.setPackageId.and.returnValue(new Manifest_mock());
@ -117,7 +127,6 @@ describe('create', function () {
Manifest_mock.prototype.setName.and.returnValue(new Manifest_mock());
spyOn(create, 'validatePackageName').and.resolveTo();
spyOn(create, 'validateProjectName').and.resolveTo();
spyOn(create, 'setShellFatal').and.callFake(function (noop, cb) { cb(); });
spyOn(create, 'copyJsAndLibrary');
spyOn(create, 'copyScripts');
spyOn(create, 'copyBuildRules');
@ -125,16 +134,18 @@ describe('create', function () {
spyOn(create, 'prepBuildFiles');
revert_manifest_mock = create.__set__('AndroidManifest', Manifest_mock);
spyOn(fs, 'existsSync').and.returnValue(false);
spyOn(shell, 'cp');
spyOn(shell, 'mkdir');
spyOn(shell, 'sed');
spyOn(fs, 'copySync');
spyOn(fs, 'ensureDirSync');
spyOn(utils, 'replaceFileContents');
config_mock = jasmine.createSpyObj('ConfigParser mock instance', ['packageName', 'android_packageName', 'name', 'android_activityName']);
events_mock = jasmine.createSpyObj('EventEmitter mock instance', ['emit']);
spyOn(check_reqs, 'get_target').and.returnValue(fake_android_target);
});
afterEach(function () {
revert_manifest_mock();
});
describe('parameter values and defaults', function () {
it('should have a default package name of my.cordova.project', () => {
config_mock.packageName.and.returnValue(undefined);
@ -142,42 +153,49 @@ describe('create', function () {
expect(create.validatePackageName).toHaveBeenCalledWith('my.cordova.project');
});
});
it('should use the ConfigParser-provided package name, if exists', () => {
config_mock.packageName.and.returnValue('org.apache.cordova');
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(create.validatePackageName).toHaveBeenCalledWith('org.apache.cordova');
});
});
it('should have a default project name of CordovaExample', () => {
config_mock.name.and.returnValue(undefined);
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(create.validateProjectName).toHaveBeenCalledWith('CordovaExample');
});
});
it('should use the ConfigParser-provided project name, if exists', () => {
config_mock.name.and.returnValue('MySweetAppName');
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(create.validateProjectName).toHaveBeenCalledWith('MySweetAppName');
});
});
it('should replace any non-word characters (including unicode and spaces) in the ConfigParser-provided project name with underscores', () => {
config_mock.name.and.returnValue('応応応応 hello 用用用用');
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(create.validateProjectName).toHaveBeenCalledWith('_____hello_____');
});
});
it('should have a default activity name of MainActivity', () => {
config_mock.android_activityName.and.returnValue(undefined);
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(Manifest_mock.prototype.setName).toHaveBeenCalledWith('MainActivity');
});
});
it('should use the activityName provided via options parameter, if exists', () => {
config_mock.android_activityName.and.returnValue(undefined);
return create.create(project_path, config_mock, { activityName: 'AwesomeActivity' }, events_mock).then(() => {
expect(Manifest_mock.prototype.setName).toHaveBeenCalledWith('AwesomeActivity');
});
});
it('should use the ConfigParser-provided activity name, if exists', () => {
config_mock.android_activityName.and.returnValue('AmazingActivity');
return create.create(project_path, config_mock, {}, events_mock).then(() => {
@ -185,6 +203,7 @@ describe('create', function () {
});
});
});
describe('failure', function () {
it('should fail if the target path already exists', () => {
fs.existsSync.and.returnValue(true);
@ -195,6 +214,7 @@ describe('create', function () {
expect(err.message).toContain('Project already exists!');
});
});
it('should fail if validateProjectName rejects', () => {
const fakeError = new Error();
create.validateProjectName.and.callFake(() => Promise.reject(fakeError));
@ -207,63 +227,73 @@ describe('create', function () {
});
});
describe('happy path', function () {
it('should copy project templates from a specified custom template', () => {
return create.create(project_path, config_mock, { customTemplate: '/template/path' }, events_mock).then(() => {
expect(shell.cp).toHaveBeenCalledWith('-r', path.join('/template/path', 'assets'), app_path);
expect(shell.cp).toHaveBeenCalledWith('-r', path.join('/template/path', 'res'), app_path);
expect(shell.cp).toHaveBeenCalledWith(path.join('/template/path', 'gitignore'), path.join(project_path, '.gitignore'));
expect(fs.copySync).toHaveBeenCalledWith(path.join('/template/path', 'assets'), path.join(app_path, 'assets'));
expect(fs.copySync).toHaveBeenCalledWith(path.join('/template/path', 'res'), path.join(app_path, 'res'));
expect(fs.copySync).toHaveBeenCalledWith(path.join('/template/path', 'gitignore'), path.join(project_path, '.gitignore'));
});
});
it('should copy project templates from the default templates location if no custom template is provided', () => {
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(shell.cp).toHaveBeenCalledWith('-r', path.join(default_templates, 'assets'), app_path);
expect(shell.cp).toHaveBeenCalledWith('-r', path.join(default_templates, 'res'), app_path);
expect(shell.cp).toHaveBeenCalledWith(path.join(default_templates, 'gitignore'), path.join(project_path, '.gitignore'));
expect(fs.copySync).toHaveBeenCalledWith(path.join(default_templates, 'assets'), path.join(app_path, 'assets'));
expect(fs.copySync).toHaveBeenCalledWith(path.join(default_templates, 'res'), path.join(app_path, 'res'));
expect(fs.copySync).toHaveBeenCalledWith(path.join(default_templates, 'gitignore'), path.join(project_path, '.gitignore'));
});
});
it('should copy JS and library assets', () => {
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(create.copyJsAndLibrary).toHaveBeenCalled();
});
});
it('should create a java src directory based on the provided project package name', () => {
config_mock.packageName.and.returnValue('org.apache.cordova');
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(shell.mkdir).toHaveBeenCalledWith('-p', path.join(app_path, 'java', 'org', 'apache', 'cordova'));
expect(fs.ensureDirSync).toHaveBeenCalledWith(path.join(app_path, 'java', 'org', 'apache', 'cordova'));
});
});
it('should copy, rename and interpolate the template Activity java class with the project-specific activity name and package name', () => {
config_mock.packageName.and.returnValue('org.apache.cordova');
config_mock.android_activityName.and.returnValue('CEEDEEVEE');
var activity_path = path.join(app_path, 'java', 'org', 'apache', 'cordova', 'CEEDEEVEE.java');
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(shell.cp).toHaveBeenCalledWith('-f', path.join(default_templates, 'Activity.java'), activity_path);
expect(shell.sed).toHaveBeenCalledWith('-i', /__ACTIVITY__/, 'CEEDEEVEE', activity_path);
expect(shell.sed).toHaveBeenCalledWith('-i', /__ID__/, 'org.apache.cordova', activity_path);
expect(fs.copySync).toHaveBeenCalledWith(path.join(default_templates, 'Activity.java'), activity_path);
expect(utils.replaceFileContents).toHaveBeenCalledWith(activity_path, /__ACTIVITY__/, 'CEEDEEVEE');
expect(utils.replaceFileContents).toHaveBeenCalledWith(activity_path, /__ID__/, 'org.apache.cordova');
});
});
it('should interpolate the project name into strings.xml', () => {
config_mock.name.and.returnValue('IncredibleApp');
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(shell.sed).toHaveBeenCalledWith('-i', /__NAME__/, 'IncredibleApp', path.join(app_path, 'res', 'values', 'strings.xml'));
expect(utils.replaceFileContents).toHaveBeenCalledWith(path.join(app_path, 'res', 'values', 'strings.xml'), /__NAME__/, 'IncredibleApp');
});
});
it('should copy template scripts into generated project', () => {
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(create.copyScripts).toHaveBeenCalledWith(project_path);
});
});
it('should copy build rules / gradle files into generated project', () => {
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(create.copyBuildRules).toHaveBeenCalledWith(project_path);
});
});
it('should write project.properties file with project details and target API', () => {
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(create.writeProjectProperties).toHaveBeenCalledWith(project_path, fake_android_target);
});
});
it('should prepare build files', () => {
return create.create(project_path, config_mock, {}, events_mock).then(() => {
expect(create.prepBuildFiles).toHaveBeenCalledWith(project_path);

View File

@ -17,10 +17,10 @@
under the License.
*/
const fs = require('fs');
const fs = require('fs-extra');
const path = require('path');
const rewire = require('rewire');
const shelljs = require('shelljs');
const which = require('which');
const CordovaError = require('cordova-common').CordovaError;
const check_reqs = require('../../bin/templates/cordova/lib/check_reqs');
@ -83,7 +83,7 @@ describe('emulator', () => {
});
it('should try to parse AVD information using `avdmanager` first', () => {
spyOn(shelljs, 'which').and.callFake(cmd => cmd === 'avdmanager');
spyOn(which, 'sync').and.callFake(cmd => cmd === 'avdmanager');
const avdmanager_spy = spyOn(emu, 'list_images_using_avdmanager').and.returnValue(Promise.resolve([]));
@ -93,7 +93,7 @@ describe('emulator', () => {
});
it('should delegate to `android` if `avdmanager` cant be found and `android` can', () => {
spyOn(shelljs, 'which').and.callFake(cmd => cmd !== 'avdmanager');
spyOn(which, 'sync').and.callFake(cmd => cmd !== 'avdmanager');
const android_spy = spyOn(emu, 'list_images_using_android').and.returnValue(Promise.resolve([]));
@ -103,7 +103,7 @@ describe('emulator', () => {
});
it('should correct api level information and fill in the blanks about api level if exists', () => {
spyOn(shelljs, 'which').and.callFake(cmd => cmd === 'avdmanager');
spyOn(which, 'sync').and.callFake(cmd => cmd === 'avdmanager');
spyOn(emu, 'list_images_using_avdmanager').and.returnValue(Promise.resolve([
{
name: 'Pixel_7.0',
@ -127,7 +127,7 @@ describe('emulator', () => {
});
it('should throw an error if neither `avdmanager` nor `android` are able to be found', () => {
spyOn(shelljs, 'which').and.returnValue(false);
spyOn(which, 'sync').and.returnValue(false);
return emu.list_images().then(
() => fail('Unexpectedly resolved'),
@ -255,7 +255,7 @@ describe('emulator', () => {
let AdbSpy;
let checkReqsSpy;
let execaSpy;
let shellJsSpy;
let whichSpy;
beforeEach(() => {
emulator = {
@ -286,9 +286,9 @@ describe('emulator', () => {
const proc = emu.__get__('process');
spyOn(proc.stdout, 'write').and.stub();
shellJsSpy = jasmine.createSpyObj('shelljs', ['which']);
shellJsSpy.which.and.returnValue('/dev/android-sdk/tools');
emu.__set__('shelljs', shellJsSpy);
whichSpy = jasmine.createSpyObj('which', ['sync']);
whichSpy.sync.and.returnValue('/dev/android-sdk/tools');
emu.__set__('which', whichSpy);
});
it('should find an emulator if an id is not specified', () => {

View File

@ -19,9 +19,8 @@
var rewire = require('rewire');
var common = rewire('../../../bin/templates/cordova/lib/pluginHandlers');
var path = require('path');
var fs = require('fs');
var fs = require('fs-extra');
var osenv = require('os');
var shell = require('shelljs');
var test_dir = path.join(osenv.tmpdir(), 'test_plugman');
var project_dir = path.join(test_dir, 'project');
var src = path.join(project_dir, 'src');
@ -39,22 +38,22 @@ describe('common platform handler', function () {
describe('copyFile', function () {
it('Test#001 : should throw if source path not found', function () {
shell.rm('-rf', src);
fs.removeSync(src);
expect(function () { copyFile(test_dir, src, project_dir, dest); })
.toThrow(new Error('"' + src + '" not found!'));
});
it('Test#002 : should throw if src not in plugin directory', function () {
shell.mkdir('-p', project_dir);
fs.ensureDirSync(project_dir);
fs.writeFileSync(non_plugin_file, 'contents', 'utf-8');
var outside_file = '../non_plugin_file';
expect(function () { copyFile(test_dir, outside_file, project_dir, dest); })
.toThrow(new Error('File "' + path.resolve(test_dir, outside_file) + '" is located outside the plugin directory "' + test_dir + '"'));
shell.rm('-rf', test_dir);
fs.removeSync(test_dir);
});
it('Test#003 : should allow symlink src, if inside plugin', function () {
shell.mkdir('-p', java_dir);
fs.ensureDirSync(java_dir);
fs.writeFileSync(java_file, 'contents', 'utf-8');
// This will fail on windows if not admin - ignore the error in that case.
@ -63,11 +62,11 @@ describe('common platform handler', function () {
}
copyFile(test_dir, symlink_file, project_dir, dest);
shell.rm('-rf', project_dir);
fs.removeSync(project_dir);
});
it('Test#004 : should throw if symlink is linked to a file outside the plugin', function () {
shell.mkdir('-p', java_dir);
fs.ensureDirSync(java_dir);
fs.writeFileSync(non_plugin_file, 'contents', 'utf-8');
// This will fail on windows if not admin - ignore the error in that case.
@ -77,68 +76,68 @@ describe('common platform handler', function () {
expect(function () { copyFile(test_dir, symlink_file, project_dir, dest); })
.toThrow(new Error('File "' + path.resolve(test_dir, symlink_file) + '" is located outside the plugin directory "' + test_dir + '"'));
shell.rm('-rf', project_dir);
fs.removeSync(project_dir);
});
it('Test#005 : should throw if dest is outside the project directory', function () {
shell.mkdir('-p', java_dir);
fs.ensureDirSync(java_dir);
fs.writeFileSync(java_file, 'contents', 'utf-8');
expect(function () { copyFile(test_dir, java_file, project_dir, non_plugin_file); })
.toThrow(new Error('Destination "' + path.resolve(project_dir, non_plugin_file) + '" for source file "' + path.resolve(test_dir, java_file) + '" is located outside the project'));
shell.rm('-rf', project_dir);
fs.removeSync(project_dir);
});
it('Test#006 : should call mkdir -p on target path', function () {
shell.mkdir('-p', java_dir);
fs.ensureDirSync(java_dir);
fs.writeFileSync(java_file, 'contents', 'utf-8');
var s = spyOn(shell, 'mkdir').and.callThrough();
var s = spyOn(fs, 'ensureDirSync').and.callThrough();
var resolvedDest = path.resolve(project_dir, dest);
copyFile(test_dir, java_file, project_dir, dest);
expect(s).toHaveBeenCalled();
expect(s).toHaveBeenCalledWith('-p', path.dirname(resolvedDest));
shell.rm('-rf', project_dir);
expect(s).toHaveBeenCalledWith(path.dirname(resolvedDest));
fs.removeSync(project_dir);
});
it('Test#007 : should call cp source/dest paths', function () {
shell.mkdir('-p', java_dir);
fs.ensureDirSync(java_dir);
fs.writeFileSync(java_file, 'contents', 'utf-8');
var s = spyOn(shell, 'cp').and.callThrough();
var s = spyOn(fs, 'copySync').and.callThrough();
var resolvedDest = path.resolve(project_dir, dest);
copyFile(test_dir, java_file, project_dir, dest);
expect(s).toHaveBeenCalled();
expect(s).toHaveBeenCalledWith('-f', java_file, resolvedDest);
expect(s).toHaveBeenCalledWith(java_file, resolvedDest);
shell.rm('-rf', project_dir);
fs.removeSync(project_dir);
});
});
describe('copyNewFile', function () {
it('Test#008 : should throw if target path exists', function () {
shell.mkdir('-p', dest);
fs.ensureDirSync(dest);
expect(function () { copyNewFile(test_dir, src, project_dir, dest); })
.toThrow(new Error('"' + dest + '" already exists!'));
shell.rm('-rf', dest);
fs.removeSync(dest);
});
});
describe('deleteJava', function () {
beforeEach(function () {
shell.mkdir('-p', java_dir);
fs.ensureDirSync(java_dir);
fs.writeFileSync(java_file, 'contents', 'utf-8');
});
afterEach(function () {
shell.rm('-rf', java_dir);
fs.removeSync(java_dir);
});
it('Test#009 : should call fs.unlinkSync on the provided paths', function () {
var s = spyOn(fs, 'unlinkSync').and.callThrough();
var s = spyOn(fs, 'removeSync').and.callThrough();
deleteJava(project_dir, java_file);
expect(s).toHaveBeenCalled();
expect(s).toHaveBeenCalledWith(path.resolve(project_dir, java_file));

View File

@ -21,14 +21,13 @@ var rewire = require('rewire');
var common = rewire('../../../bin/templates/cordova/lib/pluginHandlers');
var android = common.__get__('handlers');
var path = require('path');
var fs = require('fs');
var shell = require('shelljs');
var fs = require('fs-extra');
var os = require('os');
var temp = path.join(os.tmpdir(), 'plugman');
var plugins_dir = path.join(temp, 'cordova/plugins');
var dummyplugin = path.join(__dirname, '../../fixtures/org.test.plugins.dummyplugin');
var faultyplugin = path.join(__dirname, '../../fixtures/org.test.plugins.faultyplugin');
var android_studio_project = path.join(__dirname, '../../fixtures/android_studio_project/*');
var android_studio_project = path.join(__dirname, '../../fixtures/android_studio_project');
var PluginInfo = require('cordova-common').PluginInfo;
var AndroidProject = require('../../../bin/templates/cordova/lib/AndroidProject');
@ -48,14 +47,14 @@ describe('android project handler', function () {
var dummyProject;
beforeEach(function () {
shell.mkdir('-p', temp);
fs.ensureDirSync(temp);
dummyProject = AndroidProject.getProjectFile(temp);
copyFileSpy.calls.reset();
common.__set__('copyFile', copyFileSpy);
});
afterEach(function () {
shell.rm('-rf', temp);
fs.removeSync(temp);
common.__set__('copyFile', copyFileOrig);
});
@ -75,7 +74,7 @@ describe('android project handler', function () {
describe('of <source-file> elements', function () {
beforeEach(function () {
shell.cp('-rf', android_studio_project, temp);
fs.copySync(android_studio_project, temp);
});
it('Test#003 : should copy stuff from one location to another by calling common.copyFile', function () {
@ -94,7 +93,7 @@ describe('android project handler', function () {
it('Test#006 : should throw if target file already exists', function () {
// write out a file
let target = path.resolve(temp, 'app', 'src', 'main', 'java', 'com', 'phonegap', 'plugins', 'dummyplugin');
shell.mkdir('-p', target);
fs.ensureDirSync(target);
target = path.join(target, 'DummyPlugin.java');
fs.writeFileSync(target, 'some bs', 'utf-8');
@ -185,7 +184,7 @@ describe('android project handler', function () {
var copyNewFileSpy = jasmine.createSpy('copyNewFile');
beforeEach(function () {
shell.cp('-rf', android_studio_project, temp);
fs.copySync(android_studio_project, temp);
spyOn(dummyProject, 'addSystemLibrary');
spyOn(dummyProject, 'addSubProject');
@ -215,7 +214,7 @@ describe('android project handler', function () {
it('Test#010 : should not copy anything if "custom" attribute is not set', function () {
var framework = { src: 'plugin-lib' };
var cpSpy = spyOn(shell, 'cp');
var cpSpy = spyOn(fs, 'copySync');
android.framework.install(framework, dummyPluginInfo, dummyProject);
expect(dummyProject.addSystemLibrary).toHaveBeenCalledWith(someString, framework.src);
expect(cpSpy).not.toHaveBeenCalled();
@ -282,26 +281,24 @@ describe('android project handler', function () {
describe('uninstallation', function () {
var removeFileOrig = common.__get__('removeFile');
var deleteJavaOrig = common.__get__('deleteJava');
var removeFileSpy = jasmine.createSpy('removeFile');
let originalRemoveSync = fs.removeSync;
var deleteJavaSpy = jasmine.createSpy('deleteJava');
var dummyProject;
let removeSyncSpy;
beforeEach(function () {
shell.mkdir('-p', temp);
shell.mkdir('-p', plugins_dir);
shell.cp('-rf', android_studio_project, temp);
fs.ensureDirSync(temp);
fs.ensureDirSync(plugins_dir);
fs.copySync(android_studio_project, temp);
AndroidProject.purgeCache();
dummyProject = AndroidProject.getProjectFile(temp);
common.__set__('removeFile', removeFileSpy);
removeSyncSpy = spyOn(fs, 'removeSync');
common.__set__('deleteJava', deleteJavaSpy);
});
afterEach(function () {
shell.rm('-rf', temp);
common.__set__('removeFile', removeFileOrig);
originalRemoveSync.call(fs, temp);
common.__set__('deleteJava', deleteJavaOrig);
});
@ -309,7 +306,7 @@ describe('android project handler', function () {
it('Test#017 : should remove jar files for Android Studio projects', function () {
android['lib-file'].install(valid_libs[0], dummyPluginInfo, dummyProject);
android['lib-file'].uninstall(valid_libs[0], dummyPluginInfo, dummyProject);
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app/libs/TestLib.jar'));
expect(removeSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/TestLib.jar'));
});
});
@ -317,7 +314,7 @@ describe('android project handler', function () {
it('Test#018 : should remove files for Android Studio projects', function () {
android['resource-file'].install(valid_resources[0], dummyPluginInfo, dummyProject);
android['resource-file'].uninstall(valid_resources[0], dummyPluginInfo, dummyProject);
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app', 'src', 'main', 'res', 'xml', 'dummy.xml'));
expect(removeSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app', 'src', 'main', 'res', 'xml', 'dummy.xml'));
});
});
@ -337,55 +334,55 @@ describe('android project handler', function () {
it('Test#019b : should remove stuff by calling common.removeFile for Android Studio projects, of jar with new app target-dir scheme', function () {
android['source-file'].install(valid_source[2], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[2], dummyPluginInfo, dummyProject, { android_studio: true });
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app/libs/TestLib.jar'));
expect(removeSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/TestLib.jar'));
});
it('Test#019c : should remove stuff by calling common.removeFile for Android Studio projects, of aar with new app target-dir scheme', function () {
android['source-file'].install(valid_source[3], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[3], dummyPluginInfo, dummyProject, { android_studio: true });
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app/libs/TestAar.aar'));
expect(removeSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/TestAar.aar'));
});
it('Test#019d : should remove stuff by calling common.removeFile for Android Studio projects, of xml with old target-dir scheme', function () {
android['source-file'].install(valid_source[4], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[4], dummyPluginInfo, dummyProject, { android_studio: true });
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app/src/main/res/xml/mysettings.xml'));
expect(removeSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/src/main/res/xml/mysettings.xml'));
});
it('Test#019e : should remove stuff by calling common.removeFile for Android Studio projects, of file with other extension with old target-dir scheme', function () {
android['source-file'].install(valid_source[5], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[5], dummyPluginInfo, dummyProject, { android_studio: true });
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app/src/main/res/values/other.extension'));
expect(removeSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/src/main/res/values/other.extension'));
});
it('Test#019f : should remove stuff by calling common.removeFile for Android Studio projects, of aidl with old target-dir scheme (GH-547)', function () {
android['source-file'].install(valid_source[6], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[6], dummyPluginInfo, dummyProject, { android_studio: true });
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app/src/main/aidl/com/mytest/myapi.aidl'));
expect(removeSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/src/main/aidl/com/mytest/myapi.aidl'));
});
it('Test#019g : should remove stuff by calling common.removeFile for Android Studio projects, of aar with old target-dir scheme (GH-547)', function () {
android['source-file'].install(valid_source[7], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[7], dummyPluginInfo, dummyProject, { android_studio: true });
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app/libs/testaar2.aar'));
expect(removeSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/testaar2.aar'));
});
it('Test#019h : should remove stuff by calling common.removeFile for Android Studio projects, of jar with old target-dir scheme (GH-547)', function () {
android['source-file'].install(valid_source[8], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[8], dummyPluginInfo, dummyProject, { android_studio: true });
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app/libs/testjar2.jar'));
expect(removeSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/libs/testjar2.jar'));
});
it('Test#019i : should remove stuff by calling common.removeFile for Android Studio projects, of .so lib file with old target-dir scheme (GH-547)', function () {
android['source-file'].install(valid_source[9], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[9], dummyPluginInfo, dummyProject, { android_studio: true });
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app/src/main/jniLibs/x86/libnative.so'));
expect(removeSyncSpy).toHaveBeenCalledWith(path.join(dummyProject.projectDir, 'app/src/main/jniLibs/x86/libnative.so'));
});
it('Test#019j : should remove stuff by calling common.deleteJava for Android Studio projects, with target-dir that includes "app"', function () {
android['source-file'].install(valid_source[10], dummyPluginInfo, dummyProject, { android_studio: true });
android['source-file'].uninstall(valid_source[10], dummyPluginInfo, dummyProject, { android_studio: true });
expect(deleteJavaSpy).toHaveBeenCalledWith(temp, path.join('app/src/main/java/com/appco/DummyPlugin2.java'));
expect(deleteJavaSpy).toHaveBeenCalledWith(dummyProject.projectDir, path.join('app/src/main/java/com/appco/DummyPlugin2.java'));
});
});
@ -394,7 +391,7 @@ describe('android project handler', function () {
var someString = jasmine.any(String);
beforeEach(function () {
shell.mkdir(path.join(dummyProject.projectDir, dummyPluginInfo.id));
fs.ensureDirSync(path.join(dummyProject.projectDir, dummyPluginInfo.id));
spyOn(dummyProject, 'removeSystemLibrary');
spyOn(dummyProject, 'removeSubProject');
@ -421,13 +418,13 @@ describe('android project handler', function () {
var framework = { src: 'plugin-lib', custom: true };
android.framework.uninstall(framework, dummyPluginInfo, dummyProject);
expect(dummyProject.removeSubProject).toHaveBeenCalledWith(dummyProject.projectDir, someString);
expect(removeFileSpy).toHaveBeenCalledWith(dummyProject.projectDir, someString);
expect(removeSyncSpy).toHaveBeenCalledWith(someString);
});
it('Test#24 : should install gradleReference using project.removeGradleReference', function () {
var framework = { src: 'plugin-lib', custom: true, type: 'gradleReference' };
android.framework.uninstall(framework, dummyPluginInfo, dummyProject);
expect(removeFileSpy).toHaveBeenCalledWith(dummyProject.projectDir, someString);
expect(removeSyncSpy).toHaveBeenCalledWith(someString);
expect(dummyProject.removeGradleReference).toHaveBeenCalledWith(dummyProject.projectDir, someString);
});
});
@ -441,8 +438,6 @@ describe('android project handler', function () {
wwwDest = path.resolve(dummyProject.www, 'plugins', dummyPluginInfo.id, jsModule.src);
platformWwwDest = path.resolve(dummyProject.platformWww, 'plugins', dummyPluginInfo.id, jsModule.src);
spyOn(shell, 'rm');
var existsSyncOrig = fs.existsSync;
spyOn(fs, 'existsSync').and.callFake(function (file) {
if ([wwwDest, platformWwwDest].indexOf(file) >= 0) return true;
@ -452,14 +447,14 @@ describe('android project handler', function () {
it('Test#025 : should put module to both www and platform_www when options.usePlatformWww flag is specified', function () {
android['js-module'].uninstall(jsModule, dummyPluginInfo, dummyProject, { usePlatformWww: true });
expect(shell.rm).toHaveBeenCalledWith('-Rf', wwwDest);
expect(shell.rm).toHaveBeenCalledWith('-Rf', platformWwwDest);
expect(removeSyncSpy).toHaveBeenCalledWith(wwwDest);
expect(removeSyncSpy).toHaveBeenCalledWith(platformWwwDest);
});
it('Test#026 : should put module to www only when options.usePlatformWww flag is not specified', function () {
android['js-module'].uninstall(jsModule, dummyPluginInfo, dummyProject);
expect(shell.rm).toHaveBeenCalledWith('-Rf', wwwDest);
expect(shell.rm).not.toHaveBeenCalledWith('-Rf', platformWwwDest);
expect(removeSyncSpy).toHaveBeenCalledWith(wwwDest);
expect(removeSyncSpy).not.toHaveBeenCalledWith(platformWwwDest);
});
});
@ -471,8 +466,6 @@ describe('android project handler', function () {
wwwDest = path.resolve(dummyProject.www, asset.target);
platformWwwDest = path.resolve(dummyProject.platformWww, asset.target);
spyOn(shell, 'rm');
var existsSyncOrig = fs.existsSync;
spyOn(fs, 'existsSync').and.callFake(function (file) {
if ([wwwDest, platformWwwDest].indexOf(file) >= 0) return true;
@ -482,14 +475,14 @@ describe('android project handler', function () {
it('Test#027 : should put module to both www and platform_www when options.usePlatformWww flag is specified', function () {
android.asset.uninstall(asset, dummyPluginInfo, dummyProject, { usePlatformWww: true });
expect(shell.rm).toHaveBeenCalledWith(jasmine.any(String), wwwDest);
expect(shell.rm).toHaveBeenCalledWith(jasmine.any(String), platformWwwDest);
expect(removeSyncSpy).toHaveBeenCalledWith(wwwDest);
expect(removeSyncSpy).toHaveBeenCalledWith(platformWwwDest);
});
it('Test#028 : should put module to www only when options.usePlatformWww flag is not specified', function () {
android.asset.uninstall(asset, dummyPluginInfo, dummyProject);
expect(shell.rm).toHaveBeenCalledWith(jasmine.any(String), wwwDest);
expect(shell.rm).not.toHaveBeenCalledWith(jasmine.any(String), platformWwwDest);
expect(removeSyncSpy).toHaveBeenCalledWith(wwwDest);
expect(removeSyncSpy).not.toHaveBeenCalledWith(platformWwwDest);
});
});
});