Repository: cordova-lib Updated Branches: refs/heads/master 7af5c5367 -> ac57bebf3
CB-9964 Added --template to Cordova Create Project: http://git-wip-us.apache.org/repos/asf/cordova-lib/repo Commit: http://git-wip-us.apache.org/repos/asf/cordova-lib/commit/ac57bebf Tree: http://git-wip-us.apache.org/repos/asf/cordova-lib/tree/ac57bebf Diff: http://git-wip-us.apache.org/repos/asf/cordova-lib/diff/ac57bebf Branch: refs/heads/master Commit: ac57bebf3b567f825fe6e586b34ddaa748a27185 Parents: 7af5c53 Author: dubeejw <[email protected]> Authored: Thu Nov 5 16:05:06 2015 -0500 Committer: Carlos Santana <[email protected]> Committed: Thu Dec 3 17:57:22 2015 -0500 ---------------------------------------------------------------------- cordova-lib/spec-cordova/create.spec.js | 113 +++++++++++++++----- cordova-lib/src/cordova/create.js | 127 +++++++++++++++++++---- cordova-lib/src/cordova/remote_load.js | 147 +++++++++++++++++++++++++++ 3 files changed, 341 insertions(+), 46 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/ac57bebf/cordova-lib/spec-cordova/create.spec.js ---------------------------------------------------------------------- diff --git a/cordova-lib/spec-cordova/create.spec.js b/cordova-lib/spec-cordova/create.spec.js index 0cbddc8..1f4be51 100644 --- a/cordova-lib/spec-cordova/create.spec.js +++ b/cordova-lib/spec-cordova/create.spec.js @@ -30,6 +30,7 @@ var tmpDir = helpers.tmpDir('create_test'); var appName = 'TestBase'; var appId = 'org.testing'; var project = path.join(tmpDir, appName); + var configNormal = { lib: { www: { @@ -48,6 +49,28 @@ var configSymlink = { } }; +var configGit = { + lib: { + www: { + url: 'https://github.com/apache/cordova-app-hello-world', + template: true, + version: 'not_versioned', + id: appName + } + } +}; + +var configNPM = { + lib: { + www: { + template: true, + url: 'cordova-app-hello-world', + version: '', + id: appName + } + } +}; + describe('cordova create checks for valid-identifier', function(done) { it('should reject reserved words from start of id', function(done) { @@ -69,11 +92,14 @@ describe('cordova create checks for valid-identifier', function(done) { describe('create end-to-end', function() { + //this.timeout(240000); beforeEach(function() { shell.rm('-rf', project); shell.mkdir('-p', tmpDir); }); + + afterEach(function() { process.chdir(path.join(__dirname, '..')); // Needed to rm the dir on Windows. shell.rm('-rf', tmpDir); @@ -92,7 +118,7 @@ describe('create end-to-end', function() { expect(path.join(project, 'www', 'index.html')).toExist(); // Check that www/config.xml was updated. - var configXml = new ConfigParser(path.join(project, 'www', 'config.xml')); + var configXml = new ConfigParser(path.join(project, 'config.xml')); expect(configXml.packageName()).toEqual(appId); // TODO (kamrik): check somehow that we got the right config.xml from the fixture and not some place else. @@ -105,37 +131,70 @@ describe('create end-to-end', function() { it('should successfully run with regular config', function(done) { // Call cordova create with no args, should return help. Q() - .then(function() { - // Create a real project - return cordova.raw.create(project, appId, appName, configNormal); - }) - .then(checkProject) - .fail(function(err) { - console.log(err && err.stack); - expect(err).toBeUndefined(); - }) - .fin(done); + .then(function() { + // Create a real project + return cordova.raw.create(project, appId, appName, configNormal); + }) + .then(checkProject) + .fail(function(err) { + console.log(err && err.stack); + expect(err).toBeUndefined(); + }) + .fin(done); }); it('should successfully run with symlinked www', function(done) { // Call cordova create with no args, should return help. cordova.raw.create(project, appId, appName, configSymlink) - .then(checkProject) - .then(function() { - // Check that www is really a symlink - expect(fs.lstatSync(path.join(project, 'www')).isSymbolicLink()).toBe(true); - }) - .fail(function(err) { - if(process.platform.slice(0, 3) == 'win') { - // Allow symlink error if not in admin mode - expect(err.message).toBe('Symlinks on Windows require Administrator privileges'); - } else { - if (err) { - console.log(err.stack); + .then(checkProject) + .then(function() { + // Check that www is really a symlink + expect(fs.lstatSync(path.join(project, 'www')).isSymbolicLink()).toBe(true); + }) + .fail(function(err) { + if(process.platform.slice(0, 3) == 'win') { + // Allow symlink error if not in admin mode + expect(err.message).toBe('Symlinks on Windows require Administrator privileges'); + } else { + if (err) { + console.log(err.stack); + } + expect(err).toBeUndefined(); } + }) + .fin(done); + }); + + it('should successfully run with Git URL', function(done) { + // Call cordova create with no args, should return help. + Q() + .then(function() { + // Create a real project + return cordova.raw.create(project, appId, appName, configGit); + }) + .then(checkProject) + .fail(function(err) { + console.log(err && err.stack); expect(err).toBeUndefined(); - } - }) - .fin(done); + }) + .fin(done); + }, 60000); + + it('should successfully run with NPM package', function(done) { + // Call cordova create with no args, should return help. + Q() + .then(function() { + // Create a real project + return cordova.raw.create(project, appId, appName, configNPM); + }) + .then(checkProject) + .fail(function(err) { + console.log(err && err.stack); + expect(err).toBeUndefined(); + }) + .fin(done); }); -}); + + + +}); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/ac57bebf/cordova-lib/src/cordova/create.js ---------------------------------------------------------------------- diff --git a/cordova-lib/src/cordova/create.js b/cordova-lib/src/cordova/create.js index 1f5e010..b848a16 100644 --- a/cordova-lib/src/cordova/create.js +++ b/cordova-lib/src/cordova/create.js @@ -22,7 +22,7 @@ var path = require('path'), shell = require('shelljs'), events = require('cordova-common').events, config = require('./config'), - lazy_load = require('./lazy_load'), + remoteLoad = require('./remote_load'), Q = require('q'), CordovaError = require('cordova-common').CordovaError, ConfigParser = require('cordova-common').ConfigParser, @@ -40,7 +40,7 @@ var path = require('path'), module.exports = create; function create(dir, optionalId, optionalName, cfg) { var argumentCount = arguments.length; - + return Q.fcall(function() { // Lets check prerequisites first @@ -165,24 +165,67 @@ function create(dir, optionalId, optionalName, cfg) { config.setAutoPersist(origAutoPersist); }) .then(function() { + var gitURL; + var branch; + var parseArr; + var packageName; + var packageVersion; + var isGit; + var isNPM; + if (!!cfg.lib.www.link) { events.emit('verbose', 'Symlinking assets."'); return cfg.lib.www.url; } else { events.emit('verbose', 'Copying assets."'); - return lazy_load.custom({ 'www': cfg.lib.www }, 'www') - .fail(function (error) { - var message = 'Failed to fetch custom www assets from ' + cfg.lib.www + - '\nProbably this is either a connection problem, or assets URL is incorrect.' + - '\nCheck your connection and assets URL.' + - '\n' + error; - return Q.reject(message); - }); + + isGit = cfg.lib.www.template && cordova_util.isUrl(cfg.lib.www.url); + isNPM = cfg.lib.www.template && (cfg.lib.www.url.indexOf('@') > -1 || !fs.existsSync(path.resolve(cfg.lib.www.url))); + + if (isGit) { + parseArr = cfg.lib.www.url.split('#'); + gitURL = parseArr[0]; + branch = parseArr[1]; + + events.emit('log', 'Retrieving ' + cfg.lib.www.url + ' from GitHub...'); + + return remoteLoad.gitClone(gitURL, branch).fail( + function(err) { + events.emit('verbose', err); + return Q.reject('Failed to retrieve '+ cfg.lib.www.url + ' from Git.'); + } + ); + } else if (isNPM) { + events.emit('log', 'Retrieving ' + cfg.lib.www.url + ' from NPM...'); + + // Determine package name, and version + if (cfg.lib.www.url.indexOf('@') !== -1) { + parseArr = cfg.lib.www.url.split('@'); + packageName = parseArr[0]; + packageVersion = parseArr[1]; + } else { + packageName = cfg.lib.www.url; + packageVersion = ''; + } + + return remoteLoad.npmFetch(packageName, packageVersion).fail( + function(err) { + events.emit('verbose', err); + return Q.reject('Failed to retrieve '+ cfg.lib.www.url + ' from NPM.'); + } + ); + } else { + cfg.lib.www.url = path.resolve(cfg.lib.www.url); + + return Q(cfg.lib.www.url); + } } }) .then(function(import_from_path) { + if (!fs.existsSync(import_from_path)) { - throw new CordovaError('Could not find directory: ' + import_from_path); + throw new CordovaError('Could not find directory: ' + + import_from_path); } var paths = { @@ -191,21 +234,27 @@ function create(dir, optionalId, optionalName, cfg) { }; // Keep going into child "www" folder if exists in stock app package. + // why? while (fs.existsSync(path.join(paths.www, 'www'))) { paths.root = paths.www; paths.www = path.join(paths.root, 'www'); } + // find config.xml if (fs.existsSync(path.join(paths.root, 'config.xml'))) { paths.configXml = path.join(paths.root, 'config.xml'); paths.configXmlLinkable = true; } else { try { - paths.configXml = path.join(require('cordova-app-hello-world').dirname, 'config.xml'); + paths.configXml = + path.join(require('cordova-app-hello-world').dirname, + 'config.xml'); } catch (e) { // Falling back on npm@2 path hierarchy // TODO: Remove fallback after cordova-app-hello-world release - paths.configXml = path.join(__dirname, '..', '..', 'node_modules', 'cordova-app-hello-world', 'config.xml'); + paths.configXml = + path.join(__dirname, '..', '..', 'node_modules', + 'cordova-app-hello-world', 'config.xml'); } } if (fs.existsSync(path.join(paths.root, 'merges'))) { @@ -218,11 +267,15 @@ function create(dir, optionalId, optionalName, cfg) { paths.hooksLinkable = true; } else { try { - paths.hooks = path.join(require('cordova-app-hello-world').dirname, 'hooks'); + paths.hooks = + path.join(require('cordova-app-hello-world').dirname, + 'hooks'); } catch (e) { // Falling back on npm@2 path hierarchy // TODO: Remove fallback after cordova-app-hello-world release - paths.hooks = path.join(__dirname, '..', '..', 'node_modules', 'cordova-app-hello-world', 'hooks'); + paths.hooks = + path.join(__dirname, '..', '..', 'node_modules', + 'cordova-app-hello-world', 'hooks'); } } @@ -231,6 +284,7 @@ function create(dir, optionalId, optionalName, cfg) { fs.mkdirSync(dir); } + var tryToLink = !!cfg.lib.www.link; function copyOrLink(src, dst, linkable) { if (src) { @@ -242,10 +296,43 @@ function create(dir, optionalId, optionalName, cfg) { } } } + + /* + Copies template files, and directories into a Cordova project directory. + Files, and directories not copied include: www, mergers,platforms, + plugins, hooks, and config.xml. A template directory, and platform + directory must be passed. + + templateDir - Template directory + projectDir - Project directory + */ + function copyTemplateFiles(templateDir, projectDir) { + var templateFiles; // Current file + + templateFiles = fs.readdirSync(templateDir); + + // Remove directories, and files that are automatically copied + templateFiles = templateFiles.filter( + function (value) { + return !(value === 'www' || value === 'mergers' || + value === 'config.xml' || value === 'hooks'); + } + ); + + // Copy each template file + for (var i = 0; i < templateFiles.length; i++) + shell.cp('-R', path.resolve(templateDir, templateFiles[i]), projectDir); + } + try { copyOrLink(paths.www, path.join(dir, 'www'), true); copyOrLink(paths.merges, path.join(dir, 'merges'), true); - copyOrLink(paths.hooks, path.join(dir, 'hooks'), paths.hooksLinkable); + copyOrLink(paths.hooks, path.join(dir, 'hooks'), + paths.hooksLinkable); + + if (cfg.lib.www.template) + copyTemplateFiles(import_from_path, dir); + if (paths.configXml) { if (tryToLink && paths.configXmlLinkable) { fs.symlinkSync(paths.configXml, path.join(dir, 'config.xml')); @@ -262,10 +349,12 @@ function create(dir, optionalId, optionalName, cfg) { } throw e; } - // Create basic project structure. - shell.mkdir(path.join(dir, 'platforms')); - shell.mkdir(path.join(dir, 'plugins')); + if (!fs.existsSync(path.join(dir, 'platforms'))) + shell.mkdir(path.join(dir, 'platforms')); + + if (!fs.existsSync(path.join(dir, 'plugins'))) + shell.mkdir(path.join(dir, 'plugins')); // Write out id and name to config.xml var configPath = cordova_util.projectConfig(dir); http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/ac57bebf/cordova-lib/src/cordova/remote_load.js ---------------------------------------------------------------------- diff --git a/cordova-lib/src/cordova/remote_load.js b/cordova-lib/src/cordova/remote_load.js new file mode 100644 index 0000000..ac56786 --- /dev/null +++ b/cordova-lib/src/cordova/remote_load.js @@ -0,0 +1,147 @@ +/** + 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 path = require('path'); +var shell = require('shelljs'); +var Q = require('q'); +var npm = require('npm'); +var npmHelper = require('../util/npm-helper'); +var unpack = require('../util/unpack'); +var util = require('./util'); +var git = require('../gitclone'); + +/* +Fetches the latests version of a package from NPM. A promise is returned that +resolves to the directory the NPM package is located in. Package options must be +passed containing a packageName, name, and version. + +options - Package options + */ +function npmFetch(packageName, packageVersion) { + var versionCallback; // Resultant callback + var downloadDir; // Download directory + var npmPackage; // NPM package information + + // Get the latest matching version from NPM if a version range is specified + versionCallback = util.getLatestMatchingNpmVersion(packageName, packageVersion).then( + function (latestVersion) { + downloadDir = path.join(util.libDirectory, packageName, 'cordova', latestVersion); + npmPackage = packageName + '@' + latestVersion; + + return exports.npmCacheAdd(npmPackage); + }, + function (err) { + return Q.reject(err); + } + ); + + return versionCallback; +} + +/* +Invokes "npm cache add," and then returns a promise that resolves to a +directory containing the downloaded, or cached package. NPM package information +must be passed in the form of package@version. + +npmPackage - NPM package details + */ +function npmCacheAdd(npmPackage) { + var loadCallback; // Resultant callback + var cacheAddCallback; // Callback for cache + var cacheDir; // Cache directory + var npmConfig; // NPM Configuration + var packageDir; // Downloaded package directory + var packageTGZ; // Downloaded TGZ directory + + cacheDir = path.join(util.libDirectory, 'npm_cache'); + + npmConfig = { + 'cache-min': 3600 * 24, + 'cache': cacheDir + }; + + // Load with NPM configuration + loadCallback = npmHelper.loadWithSettingsThenRestore(npmConfig, + function () { + + // Invoke NPM Cache Add + cacheAddCallback = Q.ninvoke(npm.commands, 'cache', ['add', npmPackage]).then( + function (info) { + packageDir = path.resolve(npm.cache, info.name, info.version, 'package'); + packageTGZ = path.resolve(npm.cache, info.name, info.version, 'package.tgz'); + + return unpack.unpackTgz(packageTGZ, packageDir); + }, + function (err) { + return Q.reject(err); + } + ); + + return cacheAddCallback; + }, + function (err) { + return Q.reject(err); + } + ); + + return loadCallback; +} + +/* +Performs a Git clone an a Git URL, and branch. If the clone was successful, +the path to the cloned directory will be returned. Otherwise, a error is +returned. A gitURL, must be passed, and a branch to checkout at is optionally +passed. + +gitURL - URL to Git repository +branch - Branch to checkout at + */ +function gitClone(gitURL, branch) { + var cloneCallback; // Resultant callback + var tmpSubDir; // Temporary sub-directory + var tmpDir; // Temporary directory + var checkoutBranch; // Branch to checkout + + checkoutBranch = branch || 'master'; + tmpSubDir = 'tmp_cordova_git_' + process.pid + '_' + (new Date()).valueOf(); + tmpDir = path.join(util.libDirectory, 'tmp', tmpSubDir); + + shell.rm('-rf', tmpDir); + shell.mkdir('-p', tmpDir); + + cloneCallback = git.clone(gitURL, checkoutBranch, tmpDir); + + // Callback for Git clone + cloneCallback.then( + function() { + return tmpDir; + }, + function (err) { + shell.rm('-rf', tmpDir); + + return Q.reject(err); + } + ); + + return cloneCallback; +} + +exports.gitClone = gitClone; +exports.npmFetch = npmFetch; +exports.npmCacheAdd = npmCacheAdd; \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
