You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ca...@apache.org on 2016/08/04 22:43:34 UTC

cordova-create git commit: CB-11623 added symlinking option

Repository: cordova-create
Updated Branches:
  refs/heads/master da2e8567a -> fa36b9415


CB-11623 added symlinking option


Project: http://git-wip-us.apache.org/repos/asf/cordova-create/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-create/commit/fa36b941
Tree: http://git-wip-us.apache.org/repos/asf/cordova-create/tree/fa36b941
Diff: http://git-wip-us.apache.org/repos/asf/cordova-create/diff/fa36b941

Branch: refs/heads/master
Commit: fa36b94155956d95f6a9cdbf6b342385eae4aeda
Parents: da2e856
Author: carynbear <ca...@berkeley.edu>
Authored: Thu Aug 4 11:28:36 2016 -0700
Committer: carynbear <ca...@berkeley.edu>
Committed: Thu Aug 4 15:06:17 2016 -0700

----------------------------------------------------------------------
 index.js                                   |  98 ++++++++++---
 spec/create.spec.js                        | 183 ++++++++++++++++++++++++
 spec/templates/noconfig/hooks/hooks.file   |   0
 spec/templates/noconfig/merges/merges.file |   0
 spec/templates/noconfig/www/index.html     |  49 +++++++
 5 files changed, 312 insertions(+), 18 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-create/blob/fa36b941/index.js
----------------------------------------------------------------------
diff --git a/index.js b/index.js
index b0b3950..ee73b51 100644
--- a/index.js
+++ b/index.js
@@ -189,6 +189,12 @@ module.exports = function(dir, optionalId, optionalName, cfg, extEvents) {
         var isGit;
         var isNPM;
 
+        //If symlink, don't fetch
+        if (!!cfg.lib.www.link) {
+            events.emit('verbose', 'Symlinking assets.');
+            return Q(cfg.lib.www.url);
+        }
+
         events.emit('verbose', 'Copying assets."');
         isGit = cfg.lib.www.template && isUrl(cfg.lib.www.url);
         isNPM = cfg.lib.www.template && (cfg.lib.www.url.indexOf('@') > -1 || !fs.existsSync(path.resolve(cfg.lib.www.url)));
@@ -247,14 +253,19 @@ module.exports = function(dir, optionalId, optionalName, cfg, extEvents) {
         }
 
         try {
+
             // Copy files from template to project
             if (cfg.lib.www.template)
                 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 = projectConfig(dir);
+            // If --link, link merges, hooks, www, and config.xml (and/or copy to root)
+            if (!!cfg.lib.www.link)
+                linkFromTemplate(import_from_path, dir);
+
+            // If following were not copied/linked from template, copy from stock app hello world
+            copyIfNotExists(paths.www, path.join(dir, 'www'));
+            copyIfNotExists(paths.hooks, path.join(dir, 'hooks'));
+            var configXmlExists = projectConfig(dir); //moves config to root if in www
             if (paths.configXml && !configXmlExists) {
                 shell.cp(paths.configXml, path.join(dir, 'config.xml'));
             }
@@ -262,17 +273,21 @@ module.exports = function(dir, optionalId, optionalName, cfg, extEvents) {
             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'));
+        var pkgjsonPath = path.join(dir, 'package.json');
+        // Update package.json name and version fields
+        if (fs.existsSync(pkgjsonPath)) {
+            var pkgjson = require(pkgjsonPath);
             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');
+            fs.writeFileSync(pkgjsonPath, JSON.stringify(pkgjson, null, 4), 'utf8');
         }
 
         // Create basic project structure.
@@ -282,26 +297,29 @@ module.exports = function(dir, optionalId, optionalName, cfg, extEvents) {
         if (!fs.existsSync(path.join(dir, 'plugins')))
             shell.mkdir(path.join(dir, 'plugins'));
 
-        // Write out id and name to config.xml; set version to 1.0.0 (to match package.json default version)
-        var configPath = 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();
+        var configPath = path.join(dir, 'config.xml');
+        // only update config.xml if not a symlink
+        if(!fs.lstatSync(configPath).isSymbolicLink()) {
+            // Write out id and name to config.xml; set version to 1.0.0 (to match package.json default version)
+            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();
+        }  
     }).then(function(){
         cleanupEvents();
     });
 };
 
 /**
- * Recursively copies folder to destination if folder is not found in destination.
+ * Recursively copies folder to destination if folder is not found in destination (including symlinks).
  * @param  {string} src for copying
  * @param  {string} dst for copying
  * @return No return value
  */
-function ifNotCopied(src, dst) {
-    if (!fs.existsSync(dst) && src) {
+function copyIfNotExists(src, dst) {
+    if (!fs.existsSync(dst) && !fs.lstatSync(dst).isSymbolicLink() && src) {
         shell.mkdir(dst);
         shell.cp('-R', path.join(src, '*'), dst);
     }
@@ -410,3 +428,47 @@ function writeToConfigJson(project_root, opts, autoPersist) {
         return json; 
     } 
 }
+
+/**
+ * Removes existing files and symlinks them if they exist.
+ * Symlinks folders: www, merges, hooks 
+ * Symlinks file: config.xml (but only if it exists outside of the www folder)
+ * If config.xml exists inside of template/www, COPY (not link) it to project/
+ * */
+ function linkFromTemplate(templateDir, projectDir) {
+    var linkSrc, linkDst, linkFolders, copySrc, copyDst;
+    function rmlinkSync(src, dst, type) {
+        if (src && dst) {
+            if (fs.existsSync(dst)) {
+                shell.rm('-rf', dst);
+            }
+            if (fs.existsSync(src)) {
+                fs.symlinkSync(src, dst, type);
+            }
+        }
+    } 
+    // if template is a www dir
+    if (path.basename(templateDir) === 'www') {
+        linkSrc = path.resolve(templateDir);
+        linkDst = path.join(projectDir, 'www');
+        rmlinkSync(linkSrc, linkDst, 'dir');
+        copySrc = path.join(templateDir, 'config.xml');
+    } else {
+        linkFolders = ['www', 'merges', 'hooks'];
+        // Link each folder
+        for (var i = 0; i < linkFolders.length; i++) {
+            linkSrc = path.join(templateDir, linkFolders[i]);
+            linkDst = path.join(projectDir, linkFolders[i]);
+            rmlinkSync(linkSrc, linkDst, 'dir');
+        }
+        linkSrc = path.join(templateDir, 'config.xml');
+        linkDst = path.join(projectDir, 'config.xml');
+        rmlinkSync(linkSrc, linkDst, 'file');
+        copySrc = path.join(templateDir, 'www', 'config.xml');
+    }
+    // if template/www/config.xml then copy to project/config.xml
+    copyDst = path.join(projectDir, 'config.xml');
+    if (!fs.existsSync(copyDst) && fs.existsSync(copySrc)) {
+        shell.cp(copySrc, projectDir);
+    }
+ }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-create/blob/fa36b941/spec/create.spec.js
----------------------------------------------------------------------
diff --git a/spec/create.spec.js b/spec/create.spec.js
index 8ad0693..d564c61 100644
--- a/spec/create.spec.js
+++ b/spec/create.spec.js
@@ -23,6 +23,7 @@ var helpers = require('./helpers'),
     events = require('cordova-common').events,
     ConfigParser = require('cordova-common').ConfigParser,
     create = require('../index'),
+    fs = require('fs'),
     CordovaLogger = require('cordova-common').CordovaLogger.get().setLevel('error');
 
 var tmpDir = helpers.tmpDir('create_test');
@@ -328,4 +329,186 @@ describe('create end-to-end', function() {
         .fin(done);
     }, 60000);
 
+    describe('when --link-to is provided', function() {
+            it('when passed www folder should not move www/config.xml, only copy and update', function(done) {
+                function checkSymWWW() {
+                    // 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();
+
+                    // Check if www files exist.
+                    expect(path.join(project, 'www', 'index.html')).toExist();
+                    
+                    // Check www/config exists 
+                    expect(path.join(project, 'www', 'config.xml')).toExist();
+                    // Check www/config.xml was not updated. 
+                    var configXml = new ConfigParser(path.join(project, 'www', 'config.xml'));
+                    expect(configXml.packageName()).toEqual('io.cordova.hellocordova');
+                    expect(configXml.version()).toEqual('0.0.1');
+                    expect(configXml.description()).toEqual('this is the correct config.xml');
+
+                    // Check that config.xml was copied to project/config.xml
+                    expect(path.join(project, 'config.xml')).toExist();
+                    configXml = new ConfigParser(path.join(project, 'config.xml'));
+                    expect(configXml.description()).toEqual('this is the correct config.xml');
+                    // Check project/config.xml was updated. 
+                    expect(configXml.packageName()).toEqual(appId);
+                    expect(configXml.version()).toEqual('1.0.0');
+
+                    // Check that we got no package.json
+                    expect(path.join(project, 'package.json')).not.toExist();
+
+                     // Check that www is really a symlink, 
+                    // and project/config.xml , hooks and merges are not
+                    expect(fs.lstatSync(path.join(project, 'www')).isSymbolicLink()).toBe(true);
+                    expect(fs.lstatSync(path.join(project, 'hooks')).isSymbolicLink()).not.toBe(true);
+                    expect(fs.lstatSync(path.join(project, 'config.xml')).isSymbolicLink()).not.toBe(true);
+                }
+                var config = {
+                    lib: {
+                        www: {
+                            template: true,
+                            url: path.join(__dirname, 'templates', 'config_in_www', 'www'),
+                            version: '',
+                            link: true
+                        }
+                    }
+                };
+                project = project + '4';
+                return create(project, appId, appName, config)
+                    .then(checkSymWWW)
+                    .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);
+            }, 60000);
+
+            it('with subdirectory should not update symlinked project/config.xml', function(done) {
+                function checkSymSubDir() {
+                    // 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 www files exist.
+                    expect(path.join(project, 'www', 'index.html')).toExist();
+
+                    // Check that www, and config.xml is really a symlink
+                    expect(fs.lstatSync(path.join(project, 'www')).isSymbolicLink()).toBe(true);
+                    expect(fs.lstatSync(path.join(project, 'config.xml')).isSymbolicLink()).toBe(true);
+
+                    // Check that config.xml was not updated. (symlinked config does not get updated!)
+                    var configXml = new ConfigParser(path.join(project, 'config.xml'));
+                    expect(configXml.packageName()).toEqual('io.cordova.hellocordova');
+                    expect(configXml.version()).toEqual('0.0.1');
+                    
+                    // Check that we got the right config.xml
+                    expect(configXml.description()).toEqual('this is the correct config.xml');
+
+                    // Check that we got package.json (the correct one) and it was changed
+                    var pkjson = require(path.join(project, 'package.json'));
+                    expect(pkjson.name).toEqual(appName.toLowerCase());
+                    expect(pkjson.valid).toEqual('true');
+                }
+                var config = {
+                    lib: {
+                        www: {
+                            template: true,
+                            url: path.join(__dirname, 'templates', 'withsubdirectory_package_json'),
+                            version: '',
+                            link: true
+                        }
+                    }
+                };
+                project = project + '5';
+                return create(project, appId, appName, config)
+                    .then(checkSymSubDir)
+                    .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);
+            }, 60000);
+
+            it('with no config should create one and update it', function(done) {
+                function checkSymNoConfig() {
+                    // 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', 'hooks.file')).toExist();
+                    expect(path.join(project, 'merges', 'merges.file')).toExist();
+
+                    // Check if www 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);
+
+                    // Check that www, hooks, merges are really a symlink; config is not
+                    expect(fs.lstatSync(path.join(project, 'www')).isSymbolicLink()).toBe(true);
+                    expect(fs.lstatSync(path.join(project, 'hooks')).isSymbolicLink()).toBe(true);
+                    expect(fs.lstatSync(path.join(project, 'merges')).isSymbolicLink()).toBe(true);
+                    expect(fs.lstatSync(path.join(project, 'config.xml')).isSymbolicLink()).not.toBe(true);
+                }
+
+                var config = {
+                    lib: {
+                        www: {
+                            template: true,
+                            url: path.join(__dirname, 'templates', 'noconfig'),
+                            version: '',
+                            link: true
+                        }
+                    }
+                };
+                project = project + '6';
+                return create(project, appId, appName, config)
+                    .then(checkSymNoConfig)
+                    .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);
+            }, 60000);
+
+        });
+
+    
+
+
+                
 });

http://git-wip-us.apache.org/repos/asf/cordova-create/blob/fa36b941/spec/templates/noconfig/hooks/hooks.file
----------------------------------------------------------------------
diff --git a/spec/templates/noconfig/hooks/hooks.file b/spec/templates/noconfig/hooks/hooks.file
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-create/blob/fa36b941/spec/templates/noconfig/merges/merges.file
----------------------------------------------------------------------
diff --git a/spec/templates/noconfig/merges/merges.file b/spec/templates/noconfig/merges/merges.file
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-create/blob/fa36b941/spec/templates/noconfig/www/index.html
----------------------------------------------------------------------
diff --git a/spec/templates/noconfig/www/index.html b/spec/templates/noconfig/www/index.html
new file mode 100644
index 0000000..646f9cb
--- /dev/null
+++ b/spec/templates/noconfig/www/index.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<!--
+    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.
+-->
+<html>
+    <head>
+        <!--
+        Customize this policy to fit your own app's needs. For more guidance, see:
+            https://github.com/apache/cordova-plugin-whitelist/blob/master/README.md#content-security-policy
+        Some notes:
+            * gap: is required only on iOS (when using UIWebView) and is needed for JS->native communication
+            * https://ssl.gstatic.com is required only on Android and is needed for TalkBack to function properly
+            * Disables use of inline scripts in order to mitigate risk of XSS vulnerabilities. To change this:
+                * Enable inline JS: add 'unsafe-inline' to default-src
+        -->
+        <meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *">
+        <meta name="format-detection" content="telephone=no">
+        <meta name="msapplication-tap-highlight" content="no">
+        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
+        <link rel="stylesheet" type="text/css" href="css/index.css">
+        <title>Hello World</title>
+    </head>
+    <body>
+        <div class="app">
+            <h1>Apache Cordova</h1>
+            <div id="deviceready" class="blink">
+                <p class="event listening">Connecting to Device</p>
+                <p class="event received">Device is Ready</p>
+            </div>
+        </div>
+        <script type="text/javascript" src="cordova.js"></script>
+        <script type="text/javascript" src="js/index.js"></script>
+    </body>
+</html>


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org