diff --git a/cordova-lib/spec-cordova/create.spec.js b/cordova-lib/spec-cordova/create.spec.js index dd1ffa2b6..8d57e10c1 100644 --- a/cordova-lib/spec-cordova/create.spec.js +++ b/cordova-lib/spec-cordova/create.spec.js @@ -19,7 +19,6 @@ var helpers = require('./helpers'), path = require('path'), - fs = require('fs'), shell = require('shelljs'), Q = require('q'), events = require('cordova-common').events, @@ -31,22 +30,25 @@ var appName = 'TestBase'; var appId = 'org.testing'; var project = path.join(tmpDir, appName); -var configNormal = { - lib: { +var configSubDirPkgJson = { + lib: { www: { - url: path.join(__dirname, 'fixtures', 'base', 'www'), - version: 'testCordovaCreate' + template: true, + url: path.join(__dirname, 'fixtures', 'templates', 'withsubdirectory_package_json'), + version: '' } - } - }; -var configSymlink = { - lib: { + } +}; + +var configConfigInWww = { + lib: { www: { - url: path.join(__dirname, 'fixtures', 'base'), // "create" should copy or link the www child of this dir and not the dir itself. - link: true + template: true, + url: path.join(__dirname, 'fixtures', 'templates', 'config_in_www'), + version: '' } - } - }; + } +}; var configGit = { lib: { @@ -110,10 +112,10 @@ describe('create end-to-end', function() { expect(path.join(project, 'hooks', 'README.md')).toExist(); - // Check if config files exist. + // Check if www files exist. expect(path.join(project, 'www', 'index.html')).toExist(); - // Check that www/config.xml was updated. + // Check that config.xml was updated. var configXml = new ConfigParser(path.join(project, 'config.xml')); expect(configXml.packageName()).toEqual(appId); @@ -121,47 +123,68 @@ describe('create end-to-end', function() { // expect(configXml.name()).toEqual('TestBase'); } - var results; - events.on('results', function(res) { results = res; }); + function checkConfigXml() { + // Check if top level dirs exist. + var dirs = ['hooks', 'platforms', 'plugins', 'www']; + dirs.forEach(function(d) { + expect(path.join(project, d)).toExist(); + }); + expect(path.join(project, 'hooks', 'README.md')).toExist(); + + //index.js and template subdir folder should not exist (inner files should be copied to the project folder) + expect(path.join(project, 'index.js')).not.toExist(); + expect(path.join(project, 'template')).not.toExist(); - 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); - }); + // Check if www files exist. + expect(path.join(project, 'www', 'index.html')).toExist(); + var configXml = new ConfigParser(path.join(project, 'www', 'config.xml')); + expect(configXml.packageName()).toEqual(appId); + expect(configXml.version()).toEqual('1.0.0'); - 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); - } - expect(err).toBeUndefined(); - } - }) - .fin(done); - }); + // Check that config.xml does not exist outside of www + expect(path.join(project, 'config.xml')).not.toExist(); + + // Check that we got no package.json + expect(path.join(project, 'package.json')).not.toExist(); + + // Check that we got the right config.xml from the template and not stock + expect(configXml.description()).toEqual('this is the correct config.xml'); + } - it('should successfully run with Git URL', function(done) { + function checkSubDir() { + // Check if top level dirs exist. + var dirs = ['hooks', 'platforms', 'plugins', 'www']; + dirs.forEach(function(d) { + expect(path.join(project, d)).toExist(); + }); + expect(path.join(project, 'hooks', 'README.md')).toExist(); + + //index.js and template subdir folder should not exist (inner files should be copied to the project folder) + expect(path.join(project, 'index.js')).not.toExist(); + expect(path.join(project, 'template')).not.toExist(); + + // Check if config files exist. + expect(path.join(project, 'www', 'index.html')).toExist(); + + // Check that config.xml was updated. + var configXml = new ConfigParser(path.join(project, 'config.xml')); + expect(configXml.packageName()).toEqual(appId); + expect(configXml.version()).toEqual('1.0.0'); + + + // Check that we got package.json (the correct one) + var pkjson = require(path.join(project, 'package.json')); + expect(pkjson.name).toEqual(appName.toLowerCase()); + expect(pkjson.valid).toEqual('true'); + + // Check that we got the right config.xml + expect(configXml.description()).toEqual('this is the correct config.xml'); + } + + var results; + events.on('results', function(res) { results = res; }); + + it('should successfully run with Git URL', function(done) { // Call cordova create with no args, should return help. Q() .then(function() { @@ -208,6 +231,11 @@ describe('create end-to-end', function() { return cordova.raw.create(project, appId, appName, config); }) .then(checkProject) + .then(function(){ + // Check that we got the right config.xml + var configXml = new ConfigParser(path.join(project, 'config.xml')); + expect(configXml.description()).toEqual('this is the very correct config.xml'); + }) .fail(function(err) { console.log(err && err.stack); expect(err).toBeUndefined(); @@ -262,24 +290,18 @@ describe('create end-to-end', function() { }) .fin(done); }); - + + it('should successfully run with template having package.json, and subdirectory, and package.json in subdirectory', function(done) { // Call cordova create with no args, should return help. - var config = { - lib: { - www: { - template: true, - url: path.join(__dirname, 'fixtures', 'templates', 'withsubdirectory_package_json'), - version: '' - } - } - }; + var config = configSubDirPkgJson; Q() .then(function() { // Create a real project + project = project + '1'; return cordova.raw.create(project, appId, appName, config); }) - .then(checkProject) + .then(checkSubDir) .fail(function(err) { console.log(err && err.stack); expect(err).toBeUndefined(); @@ -287,6 +309,22 @@ describe('create end-to-end', function() { .fin(done); }); + it('should successfully run config.xml in the www folder', function(done) { + // Call cordova create with no args, should return help. + var config = configConfigInWww; + Q() + .then(function() { + // Create a real project + project = project + '2'; + return cordova.raw.create(project, appId, appName, config); + }) + .then(checkConfigXml) + .fail(function(err) { + console.log(err && err.stack); + expect(err).toBeUndefined(); + }) + .fin(done); + }); }); diff --git a/cordova-lib/spec-cordova/fixtures/templates/config_in_www/www/config.xml b/cordova-lib/spec-cordova/fixtures/templates/config_in_www/www/config.xml new file mode 100644 index 000000000..0efe8c6db --- /dev/null +++ b/cordova-lib/spec-cordova/fixtures/templates/config_in_www/www/config.xml @@ -0,0 +1,43 @@ + + + + HelloCordova + this is the correct config.xml + + Apache Cordova Team + + + + + + + + + + + + + + + + + + + diff --git a/cordova-lib/spec-cordova/fixtures/templates/config_in_www/www/index.html b/cordova-lib/spec-cordova/fixtures/templates/config_in_www/www/index.html new file mode 100644 index 000000000..646f9cb25 --- /dev/null +++ b/cordova-lib/spec-cordova/fixtures/templates/config_in_www/www/index.html @@ -0,0 +1,49 @@ + + + + + + + + + + + Hello World + + +
+

Apache Cordova

+ +
+ + + + diff --git a/cordova-lib/spec-cordova/fixtures/templates/nopackage_json/config.xml b/cordova-lib/spec-cordova/fixtures/templates/nopackage_json/config.xml index 02e616c88..3a9cb3c32 100644 --- a/cordova-lib/spec-cordova/fixtures/templates/nopackage_json/config.xml +++ b/cordova-lib/spec-cordova/fixtures/templates/nopackage_json/config.xml @@ -19,9 +19,7 @@ --> HelloCordova - - A sample Apache Cordova application that responds to the deviceready event. - + this is the very correct config.xml Apache Cordova Team diff --git a/cordova-lib/spec-cordova/fixtures/templates/withsubdirectory_package_json/package.json b/cordova-lib/spec-cordova/fixtures/templates/withsubdirectory_package_json/package.json index 51cbb55e3..80f8575d6 100644 --- a/cordova-lib/spec-cordova/fixtures/templates/withsubdirectory_package_json/package.json +++ b/cordova-lib/spec-cordova/fixtures/templates/withsubdirectory_package_json/package.json @@ -7,5 +7,6 @@ "type": "git", "url": "https://github.com/apache/cordova-app-hello-world.git" }, - "license": "Apache-2.0" + "license": "Apache-2.0", + "test": "false" } \ No newline at end of file diff --git a/cordova-lib/spec-cordova/fixtures/templates/withsubdirectory_package_json/template/config.xml b/cordova-lib/spec-cordova/fixtures/templates/withsubdirectory_package_json/template/config.xml index 02e616c88..0efe8c6db 100644 --- a/cordova-lib/spec-cordova/fixtures/templates/withsubdirectory_package_json/template/config.xml +++ b/cordova-lib/spec-cordova/fixtures/templates/withsubdirectory_package_json/template/config.xml @@ -19,9 +19,7 @@ --> HelloCordova - - A sample Apache Cordova application that responds to the deviceready event. - + this is the correct config.xml Apache Cordova Team diff --git a/cordova-lib/spec-cordova/fixtures/templates/withsubdirectory_package_json/template/package.json b/cordova-lib/spec-cordova/fixtures/templates/withsubdirectory_package_json/template/package.json index 30a86ba4e..c2479caf7 100644 --- a/cordova-lib/spec-cordova/fixtures/templates/withsubdirectory_package_json/template/package.json +++ b/cordova-lib/spec-cordova/fixtures/templates/withsubdirectory_package_json/template/package.json @@ -6,5 +6,6 @@ "type": "git", "url": "https://github.com/apache/cordova-app-hello-world.git" }, - "license": "Apache-2.0" + "license": "Apache-1.0", + "valid": "true" } \ No newline at end of file diff --git a/cordova-lib/src/cordova/create.js b/cordova-lib/src/cordova/create.js index c98ab906b..da22755b7 100644 --- a/cordova-lib/src/cordova/create.js +++ b/cordova-lib/src/cordova/create.js @@ -174,72 +174,69 @@ function create(dir, optionalId, optionalName, cfg, fetchOpt) { 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."'); - 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 --fetch flag is passed, use cordova fetch to obtain the npm or git template - if((isGit || isNPM) && fetchOpt) { - //Saved to .Cordova folder (ToDo: Delete installed template after using) - var tempDest = cordova_util.globalConfig; - events.emit('log', 'Using cordova-fetch for '+ cfg.lib.www.url); - return fetch(cfg.lib.www.url, tempDest, {}); - //Otherwise use remote-load to get git template - } else if (isGit) { - - parseArr = cfg.lib.www.url.split('#'); - gitURL = parseArr[0]; - branch = parseArr[1]; - - events.emit('log', 'Retrieving ' + cfg.lib.www.url + ' using git...'); - - return remoteLoad.gitClone(gitURL, branch).fail( - function(err) { - return Q.reject(new CordovaError('Failed to retrieve '+ cfg.lib.www.url + ' using git: ' + err.message)); - } - ); - //Otherwise use remote-load to get npm template - } else if (isNPM) { - events.emit('log', 'Retrieving ' + cfg.lib.www.url + ' using 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 = 'latest'; + events.emit('verbose', 'Copying assets."'); + 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 --fetch flag is passed, use cordova fetch to obtain the npm or git template + if((isGit || isNPM) && fetchOpt) { + //Saved to .Cordova folder (ToDo: Delete installed template after using) + var tempDest = cordova_util.globalConfig; + events.emit('log', 'Using cordova-fetch for '+ cfg.lib.www.url); + return fetch(cfg.lib.www.url, tempDest, {}); + //Otherwise use remote-load to get git template + } else if (isGit) { + + parseArr = cfg.lib.www.url.split('#'); + gitURL = parseArr[0]; + branch = parseArr[1]; + + events.emit('log', 'Retrieving ' + cfg.lib.www.url + ' using git...'); + + return remoteLoad.gitClone(gitURL, branch).fail( + function(err) { + return Q.reject(new CordovaError('Failed to retrieve '+ cfg.lib.www.url + ' using git: ' + err.message)); } - - return remoteLoad.npmFetch(packageName, packageVersion).fail( - function(err) { - events.emit('warn', err.message); - return Q.reject(new CordovaError('Failed to retrieve '+ cfg.lib.www.url + ' using npm: ' + err.message)); - } - ); - //If assets are not online, resolve as a relative path on local computer + ); + //Otherwise use remote-load to get npm template + } else if (isNPM) { + events.emit('log', 'Retrieving ' + cfg.lib.www.url + ' using 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 { - cfg.lib.www.url = path.resolve(cfg.lib.www.url); - - return Q(cfg.lib.www.url); + packageName = cfg.lib.www.url; + packageVersion = 'latest'; } + + return remoteLoad.npmFetch(packageName, packageVersion).fail( + function(err) { + events.emit('warn', err.message); + return Q.reject(new CordovaError('Failed to retrieve '+ cfg.lib.www.url + ' using npm: ' + err.message)); + } + ); + //If assets are not online, resolve as a relative path on local computer + } else { + cfg.lib.www.url = path.resolve(cfg.lib.www.url); + return Q(cfg.lib.www.url); } }).then(function(input_directory) { var import_from_path = input_directory; - //handle when input wants to specify sub-directory (specified in index.js); - // + + //handle when input wants to specify sub-directory (specified in index.js as "dirname" export); + var isSubDir = false; try { var templatePkg = require(input_directory); if (templatePkg && templatePkg.dirname){ import_from_path = templatePkg.dirname; + isSubDir = true; } } catch (e) { - events.emit('verbose', 'index.js does not specific valid sub-directory: ' + input_directory); + events.emit('verbose', 'index.js does not specify valid sub-directory: ' + input_directory); + isSubDir = false; } if (!fs.existsSync(import_from_path)) { @@ -247,126 +244,53 @@ function create(dir, optionalId, optionalName, cfg, fetchOpt) { import_from_path); } - var paths = { - root: import_from_path, - www: import_from_path - }; + var paths = {}; - // Keep going into child "www" folder if exists in stock app package. - while (fs.existsSync(path.join(paths.www, 'www'))) { - paths.root = paths.www; - paths.www = path.join(paths.root, 'www'); - } + // get stock config.xml, used if template does not contain config.xml + paths.configXml = path.join(require('cordova-app-hello-world').dirname, 'config.xml'); - // 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'); - } 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'); - } - } - if (fs.existsSync(path.join(paths.root, 'merges'))) { - paths.merges = path.join(paths.root, 'merges'); - } else { - // No merges by default - } - if (fs.existsSync(path.join(paths.root, 'hooks'))) { - paths.hooks = path.join(paths.root, 'hooks'); - paths.hooksLinkable = true; - } else { - try { - 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'); - } - } + // get stock www; used if template does not contain www + paths.www = path.join(require('cordova-app-hello-world').dirname, 'www'); + + // get stock hooks; used if template does not contain hooks + paths.hooks = path.join(require('cordova-app-hello-world').dirname, 'hooks'); + + // ToDo: get stock package.json if template does not contain package.json; var dirAlreadyExisted = fs.existsSync(dir); if (!dirAlreadyExisted) { fs.mkdirSync(dir); } - - var tryToLink = !!cfg.lib.www.link; - function copyOrLink(src, dst, linkable) { - if (src) { - if (tryToLink && linkable) { - fs.symlinkSync(src, dst, 'dir'); - } else { - shell.mkdir(dst); - shell.cp('-R', path.join(src, '*'), dst); - } - } - } - - /* - 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); - + // Copy files from template to project 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')); - } else { - shell.cp(paths.configXml, path.join(dir, 'config.xml')); - } + copyTemplateFiles(import_from_path, dir, isSubDir); + + // If following were not copied from template, copy from stock app hello world + ifNotCopied(paths.www, path.join(dir, 'www')); + ifNotCopied(paths.hooks, path.join(dir, 'hooks')); + var configXmlExists = cordova_util.projectConfig(dir); + if (paths.configXml && !configXmlExists) { + shell.cp(paths.configXml, path.join(dir, 'config.xml')); } } catch (e) { if (!dirAlreadyExisted) { shell.rm('-rf', dir); } - if (process.platform.slice(0, 3) == 'win' && e.code == 'EPERM') { - throw new CordovaError('Symlinks on Windows require Administrator privileges'); - } throw e; } + + // Update package.json name and version fields. + if (fs.existsSync(path.join(dir, 'package.json'))) { + var pkgjson = require(path.resolve(dir, 'package.json')); + if (cfg.name) { + pkgjson.name = cfg.name.toLowerCase(); + } + pkgjson.version = '1.0.0'; + fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify(pkgjson, null, 4), 'utf8'); + } + // Create basic project structure. if (!fs.existsSync(path.join(dir, 'platforms'))) shell.mkdir(path.join(dir, 'platforms')); @@ -374,11 +298,53 @@ function create(dir, optionalId, optionalName, cfg, fetchOpt) { if (!fs.existsSync(path.join(dir, 'plugins'))) shell.mkdir(path.join(dir, 'plugins')); - // Write out id and name to config.xml + // Write out id and name to config.xml; set version to 1.0.0 (to match package.json default version) var configPath = cordova_util.projectConfig(dir); var conf = new ConfigParser(configPath); if (cfg.id) conf.setPackageName(cfg.id); if (cfg.name) conf.setName(cfg.name); + conf.setVersion('1.0.0'); conf.write(); }); } + +/** + * Recursively copies folder to destination if folder is not found in destination. + * @param {string} src for copying + * @param {string} dst for copying + * @return No return value + */ +function ifNotCopied(src, dst) { + if (!fs.existsSync(dst) && src) { + shell.mkdir(dst); + shell.cp('-R', path.join(src, '*'), dst); + } +} + +/** + * Copies template files, and directories into a Cordova project directory. + * If the template exists in a subdirectory everything is copied. + * Otherwise package.json, RELEASENOTES.md, .git, NOTICE, LICENSE, COPYRIGHT, and .npmignore are not copied over. + * A template directory, and project directory must be passed. + * templateDir - Template directory + * projectDir - Project directory + * isSubDir - boolean is true if template has subdirectory structure (see code around line 229) + */ +function copyTemplateFiles(templateDir, projectDir, isSubDir) { + var templateFiles; // Current file + templateFiles = fs.readdirSync(templateDir); + // Remove directories, and files that are unwanted + if (!isSubDir) { + templateFiles = templateFiles.filter( + function (value) { + return !(value === 'package.json' || value === 'RELEASENOTES.md' || value === '.git' || value === 'NOTICE'|| + value === 'LICENSE' || value === 'COPYRIGHT' || value === '.npmignore'); + } + ); + } + // Copy each template file after filter + for (var i = 0; i < templateFiles.length; i++) { + var copyPath = path.resolve(templateDir, templateFiles[i]); + shell.cp('-R', copyPath, projectDir); + } +} diff --git a/cordova-lib/src/cordova/util.js b/cordova-lib/src/cordova/util.js index 75ad1d634..1bc227e26 100644 --- a/cordova-lib/src/cordova/util.js +++ b/cordova-lib/src/cordova/util.js @@ -238,7 +238,7 @@ function projectConfig(projectDir) { } else if (fs.existsSync(wwwPath)) { return wwwPath; } - return rootPath; + return false; } function preProcessOptions (inputOptions) {