<commit_before> 'use strict';

var build       = module.exports = {};
var fs          = require('fs');
var fse         = require('fs-extra');
var expand      = require('fs-expand');
var node_path   = require('path');
var semver      = require('semver');
var async       = require('async');
var putin       = require('put-in');
var pkg_helper  = require('../util/package');
var semver_helper = require('../util/semver');
var lang        = require('../util/lang');
var spawns      = require('spawns');
var npm_dir     = require('npm-dir');
var builder     = require('neuron-builder');

// @param {Object} options
//      see ./lib/option/build.js for details
build.run = function(options, callback) {
  var profile = this.context.profile;

  // if the destination is not specified by user
  if (!options.dest) {
    var entry_cwd = options['entry-cwd'];

    if (!profile.get('server_mode') && entry_cwd) {
      // if cortex is not running in server mode, see 'doc/<language>/server-mode.md',
      // we will build module into <root-cwd>/neurons folder
      options.dest = node_path.join(entry_cwd, 'neurons');

    } else {
      // If server_mode is on, we assume that we always use cortex server to develop,
      // and all packages will be built to a certain dir.
      options.dest = profile.get('built_root');
    }
  }

  this.MESSAGE = this.context.locale.require('command-build');

  var self = this;
  async.series([

    function(done) {
      self.read_package_json(options, done);
    },

    function(done) {
      self.run_preinstall_script(options, done);
    },

    function(done) {
      // run custom build script
      self.run_prebuild_script(options, done);
    },

    function(done) {
      // determine build tasks
      self.determine_tasks(options, done);
    },

    function(done) {
      // real building
      self.build(options, done);
    }

  ], function(err) {
    callback(err);
  });
};

build.read_package_json = function(options, callback) {
  pkg_helper.get_original_package(options.cwd, function(err, pkg) {
    options.pkg = pkg;
    callback(err);
  });
};

// Run custom build scripts, such as 'grunt'
build.run_preinstall_script = function(options, callback) {
  if (!options.preinstall) {
    return callback(null);
  }

  this.run_script('preinstall', options, callback);
};

// Run custom build scripts, such as 'grunt'
build.run_prebuild_script = function(options, callback) {
  if (!options.prebuild) {
    return callback(null);
  }

  this.run_script('prebuild', options, callback);
};

build.run_script = function(script, options, callback) {
  var pkg = options.pkg;
  var scripts = lang.makeArray(pkg.scripts && pkg.scripts[script])
  // skip empty scripts
  .filter(Boolean);

  if (!scripts.length) {
    return callback(null);
  }

  var self = this;

  this.logger.info('{{cyan run}} "scripts.' + script + '" ...');

  spawns(scripts, {
    cwd: options.cwd,
    stdio: 'inherit'

  }).on('spawn', function(command) {
    self.logger.info(' - {{cyan exec}} "' + command + '" ...');

  }).on('close', function(code, signal) {
    if (code) {
      callback({
        code: 'EBUILDSCRIPT',
        message: 'build step "scripts.' + script + '" executes as a failure. exit code: ' + code,
        data: {
          code: code,
          signal: signal,
          script: script
        }
      });
    } else {
      callback(null);
    }

  }).on('error', function(err) {
    self.logger.warn('"scripts.' + script + '" let out an error: ' + err);
  });
};

build.determine_tasks = function(options, callback) {
  var cache_root = this.context.profile.get('cache_root');
  var tasks = options.tasks = {};
  var name = options.pkg.name;
  var version = options.pkg.version;
  var built_root = options.dest;

  var parsed = semver_helper.parse(version);

  if (!parsed) {
    return callback({
      code: 'EINVALIDVER',
      message: 'invalid semantic version "' + version + '" in package.json, see http://semver.org for details.',
      data: {
        version: version
      }
    });
  }

  // Cortex will no longer publish prerelease versions into the registry,
  // so use the original version directly.
  tasks[version] = node_path.join(built_root, name, version);

  var self = this;

  if (options.ranges) {
    pkg_helper.get_cached_document({
      name: name,
      cache_root: cache_root

    }, function(err, json) {
      if (err) {
        return callback(err);
      }

      var versions = json && json.versions && Object.keys(json.versions) || [];
      versions.sort(semver.rcompare);

      // '1.2.3' -> '~1.2.0'
      var range = semver_helper.get_base_range(parsed);
      var max_patch = semver.maxSatisfying(versions, range);
      var max = versions[0];

      if (
        // if the current minor(feature) doesn't exist
        !max_patch ||

        // greater or equal to
        semver.gte(version, max_patch)
      ) {
        // >>>> range
        tasks[range] = node_path.join(built_root, name, range);

        // if the package is not the latest patch,
        // it's impossible to be the latest of all versions
        if (
          // if there's no versions on the regsitry
          !max ||

          // if current version is the greatest
          semver.gte(version, max)
        ) {
          // >>>> latest
          tasks.latest = node_path.join(built_root, name, 'latest');
        }
      }

      // {
      //     '1.2.3': '/xxx/~1.2.0',
      //     'latest': '/xxx/latest'
      // }
      callback(null, tasks);
    });

  } else {
    callback(null, tasks);
  }
};

build.build = function(options, callback) {
  var pkg = options.pkg;

  var self = this;

  // distribution directory
  var dist = lang.object_member_by_namespaces(options.pkg, 'directories.dist');

  if (dist) {
    dist = node_path.join(options.cwd, dist);

    // if `dist` dir exists, skip building and just copy it
    fs.exists(dist, function(exists) {
      if (exists) {
        self.logger.info('dist dir "' + dist + '" found, {{cyan skip}} building ...');

        self.copy_dist(dist, options, callback);

      } else {
        // if `cortex.directories.dist` is declared, the dir must be existed.
        callback({
          code: 'EDISTNOTEXISTS',
          message: 'dist dir "' + dist + '" does not exist.',
          data: {
            dist: dist
          }
        });
      }
    });

  } else {
    self.get_entries(options, function(err) {
      if (err) {
        return callback(err);
      }

      var tasks = options.tasks;

      async.eachSeries(Object.keys(tasks), function(target_version, done) {
        var to = tasks[target_version];
        self.run_build_task(target_version, to, options, done);
      }, callback);
    });
  }
};

// copy dist dir to the destination dirs
build.copy_dist = function(dist, options, callback) {
  var self = this;
  var tasks = options.tasks;
  async.each(Object.keys(tasks), function(task, done) {
    var to = tasks[task];
    self.logger.info('{{cyan copy}} "' + dist + '" to "' + to + '".');
    self.cp(dist, to, done);
  }, callback);
};

build.get_node_path = function(callback) {
  callback(npm_dir.dir);
};

build.get_entries = function(options, callback) {
  var cwd = options.cwd;
  var pkg = options.pkg;

  var main_entry = pkg.main || 'index.js';

  // Standardize main entry
  // './index' -> './index.js'
  main_entry = this.ensure_ext(main_entry, 'js');

  // './index.js' -> 'index.js'
  options.main_entry =
    main_entry =
    node_path.join('.', main_entry);

  // the module '<name>.js'
  var main_by_name = node_path.join(cwd, pkg.name) + '.js';
  var main_path = node_path.join(cwd, main_entry);

  fs.exists(main_by_name, function(exists) {
    if (exists && main_by_name !== main_path) {
      return callback({
        code: 'EMAINCONFLICT',
        message: 'if "<package.name>.js" exists, it must be `package.main`, or you should rename it.',
        data: {
          name: pkg.name,
          main: pkg.main
        }
      });
    }

    var entries = options.entries = pkg.entries ?
      expand.sync(pkg.entries, {
        cwd: cwd
      }) :
      [];

    var fail = entries.some(function(entry) {
      // entries should not be located at the parent folder of the main entry.
      if (node_path.relative(node_path.dirname(main_entry), entry).indexOf('..') === 0) {
        callback({
          code: 'EINVALIDENTRIES',
          message: 'entry "' + entry + '" should not be located at the parent folder of "' + main_entry + '"',
          data: {
            main: main_entry,
            entry: entry
          }
        });

        return true;
      }
    });

    if (!fail) {
      // prevent duplication
      if (!~entries.indexOf(main_entry)) {
        entries.push(main_entry);
      }

      callback(null);
    }
  });
};

build.ensure_ext = function(path, ext) {
  var regex = new RegExp('\\.' + ext + '$', 'i');

  if (!regex.test(path)) {
    path += '.' + ext;
  }

  return path;
};

build.run_build_task = function(target_version, to, options, callback) {
  var self = this;
  var cwd = options.cwd;
  async.each(options.entries, function (entry, done) {
    // Standardize entries
    // './index.js' -> 'index.js'
    entry = node_path.join('.', entry);
    var from = node_path.join(cwd, entry);

    builder.parse(from, {
      cwd: cwd,
      targetVersion: target_version,
      pkg: options.pkg

    }, function (err, content) {
      if (err) {
        return done(err);
      }

      var file_to = entry === options.main_entry
        // main entry will
        ? options.pkg.name + '.js'
        : entry;

      var path_to = node_path.join(to, file_to);
      fse.mkdirs( node_path.dirname(path_to), function (err) {
        if (err) {
          return done(err);
        }

        fs.writeFile(path_to, content, done);
      });
    });

  }, function (err) {
    if (err) {
      return callback(err);
    }

    self.copy_css(to, options, callback);
  });
};

build.copy_css = function(to, options, callback) {
  var pkg = options.pkg;
  var css = lang.object_member_by_namespaces(pkg, 'directories.css');

  if (css) {
    var source = node_path.join(options.cwd, css);
    var dest = node_path.join(to, css);
    this.cp(source, dest, callback);
  } else {
    callback(null);
  }
};

// publish files from `from` to `to`
build.cp = function(from, to, callback) {
  var self = this;
  if (from === to) {
    callback(null);
    return;
  }

  fs.exists(to, function(exists) {
    var tasks = [];

    if (exists) {
      tasks.push(function(done) {
        fse.remove(to, done);
      });
    }

    tasks.push(function(done) {
      self.logger.info('{{cyan copy}} ' + from + ' -> ' + to);
      fse.copy(from, to, done);
    });

    async.series(tasks, callback);
  });
};
<commit_msg>command/build: adds log; #285<commit_after> 'use strict';

var build       = module.exports = {};
var fs          = require('fs');
var fse         = require('fs-extra');
var expand      = require('fs-expand');
var node_path   = require('path');
var semver      = require('semver');
var async       = require('async');
var putin       = require('put-in');
var pkg_helper  = require('../util/package');
var semver_helper = require('../util/semver');
var lang        = require('../util/lang');
var spawns      = require('spawns');
var npm_dir     = require('npm-dir');
var builder     = require('neuron-builder');

// @param {Object} options
//      see ./lib/option/build.js for details
build.run = function(options, callback) {
  var profile = this.context.profile;

  // if the destination is not specified by user
  if (!options.dest) {
    var entry_cwd = options['entry-cwd'];

    if (!profile.get('server_mode') && entry_cwd) {
      // if cortex is not running in server mode, see 'doc/<language>/server-mode.md',
      // we will build module into <root-cwd>/neurons folder
      options.dest = node_path.join(entry_cwd, 'neurons');

    } else {
      // If server_mode is on, we assume that we always use cortex server to develop,
      // and all packages will be built to a certain dir.
      options.dest = profile.get('built_root');
    }
  }

  this.MESSAGE = this.context.locale.require('command-build');

  var self = this;
  async.series([

    function(done) {
      self.read_package_json(options, done);
    },

    function(done) {
      self.run_preinstall_script(options, done);
    },

    function(done) {
      // run custom build script
      self.run_prebuild_script(options, done);
    },

    function(done) {
      // determine build tasks
      self.determine_tasks(options, done);
    },

    function(done) {
      // real building
      self.build(options, done);
    }

  ], function(err) {
    callback(err);
  });
};

build.read_package_json = function(options, callback) {
  pkg_helper.get_original_package(options.cwd, function(err, pkg) {
    options.pkg = pkg;
    callback(err);
  });
};

// Run custom build scripts, such as 'grunt'
build.run_preinstall_script = function(options, callback) {
  if (!options.preinstall) {
    return callback(null);
  }

  this.run_script('preinstall', options, callback);
};

// Run custom build scripts, such as 'grunt'
build.run_prebuild_script = function(options, callback) {
  if (!options.prebuild) {
    return callback(null);
  }

  this.run_script('prebuild', options, callback);
};

build.run_script = function(script, options, callback) {
  var pkg = options.pkg;
  var scripts = lang.makeArray(pkg.scripts && pkg.scripts[script])
  // skip empty scripts
  .filter(Boolean);

  if (!scripts.length) {
    return callback(null);
  }

  var self = this;

  this.logger.info('{{cyan run}} "scripts.' + script + '" ...');

  spawns(scripts, {
    cwd: options.cwd,
    stdio: 'inherit'

  }).on('spawn', function(command) {
    self.logger.info(' - {{cyan exec}} "' + command + '" ...');

  }).on('close', function(code, signal) {
    if (code) {
      callback({
        code: 'EBUILDSCRIPT',
        message: 'build step "scripts.' + script + '" executes as a failure. exit code: ' + code,
        data: {
          code: code,
          signal: signal,
          script: script
        }
      });
    } else {
      callback(null);
    }

  }).on('error', function(err) {
    self.logger.warn('"scripts.' + script + '" let out an error: ' + err);
  });
};

build.determine_tasks = function(options, callback) {
  var cache_root = this.context.profile.get('cache_root');
  var tasks = options.tasks = {};
  var name = options.pkg.name;
  var version = options.pkg.version;
  var built_root = options.dest;

  var parsed = semver_helper.parse(version);

  if (!parsed) {
    return callback({
      code: 'EINVALIDVER',
      message: 'invalid semantic version "' + version + '" in package.json, see http://semver.org for details.',
      data: {
        version: version
      }
    });
  }

  // Cortex will no longer publish prerelease versions into the registry,
  // so use the original version directly.
  tasks[version] = node_path.join(built_root, name, version);

  var self = this;

  if (options.ranges) {
    pkg_helper.get_cached_document({
      name: name,
      cache_root: cache_root

    }, function(err, json) {
      if (err) {
        return callback(err);
      }

      var versions = json && json.versions && Object.keys(json.versions) || [];
      versions.sort(semver.rcompare);

      // '1.2.3' -> '~1.2.0'
      var range = semver_helper.get_base_range(parsed);
      var max_patch = semver.maxSatisfying(versions, range);
      var max = versions[0];

      if (
        // if the current minor(feature) doesn't exist
        !max_patch ||

        // greater or equal to
        semver.gte(version, max_patch)
      ) {
        // >>>> range
        tasks[range] = node_path.join(built_root, name, range);

        // if the package is not the latest patch,
        // it's impossible to be the latest of all versions
        if (
          // if there's no versions on the regsitry
          !max ||

          // if current version is the greatest
          semver.gte(version, max)
        ) {
          // >>>> latest
          tasks.latest = node_path.join(built_root, name, 'latest');
        }
      }

      // {
      //     '1.2.3': '/xxx/~1.2.0',
      //     'latest': '/xxx/latest'
      // }
      callback(null, tasks);
    });

  } else {
    callback(null, tasks);
  }
};

build.build = function(options, callback) {
  var pkg = options.pkg;

  var self = this;

  // distribution directory
  var dist = lang.object_member_by_namespaces(options.pkg, 'directories.dist');

  if (dist) {
    dist = node_path.join(options.cwd, dist);

    // if `dist` dir exists, skip building and just copy it
    fs.exists(dist, function(exists) {
      if (exists) {
        self.logger.info('dist dir "' + dist + '" found, {{cyan skip}} building ...');

        self.copy_dist(dist, options, callback);

      } else {
        // if `cortex.directories.dist` is declared, the dir must be existed.
        callback({
          code: 'EDISTNOTEXISTS',
          message: 'dist dir "' + dist + '" does not exist.',
          data: {
            dist: dist
          }
        });
      }
    });

  } else {
    self.get_entries(options, function(err) {
      if (err) {
        return callback(err);
      }

      var tasks = options.tasks;

      async.eachSeries(Object.keys(tasks), function(target_version, done) {
        var to = tasks[target_version];
        self.run_build_task(target_version, to, options, done);
      }, callback);
    });
  }
};

// copy dist dir to the destination dirs
build.copy_dist = function(dist, options, callback) {
  var self = this;
  var tasks = options.tasks;
  async.each(Object.keys(tasks), function(task, done) {
    var to = tasks[task];
    self.logger.info('{{cyan copy}} "' + dist + '" to "' + to + '".');
    self.cp(dist, to, done);
  }, callback);
};

build.get_node_path = function(callback) {
  callback(npm_dir.dir);
};

build.get_entries = function(options, callback) {
  var cwd = options.cwd;
  var pkg = options.pkg;

  var main_entry = pkg.main || 'index.js';

  // Standardize main entry
  // './index' -> './index.js'
  main_entry = this.ensure_ext(main_entry, 'js');

  // './index.js' -> 'index.js'
  options.main_entry =
    main_entry =
    node_path.join('.', main_entry);

  // the module '<name>.js'
  var main_by_name = node_path.join(cwd, pkg.name) + '.js';
  var main_path = node_path.join(cwd, main_entry);

  fs.exists(main_by_name, function(exists) {
    if (exists && main_by_name !== main_path) {
      return callback({
        code: 'EMAINCONFLICT',
        message: 'if "<package.name>.js" exists, it must be `package.main`, or you should rename it.',
        data: {
          name: pkg.name,
          main: pkg.main
        }
      });
    }

    var entries = options.entries = pkg.entries ?
      expand.sync(pkg.entries, {
        cwd: cwd
      }) :
      [];

    var fail = entries.some(function(entry) {
      // entries should not be located at the parent folder of the main entry.
      if (node_path.relative(node_path.dirname(main_entry), entry).indexOf('..') === 0) {
        callback({
          code: 'EINVALIDENTRIES',
          message: 'entry "' + entry + '" should not be located at the parent folder of "' + main_entry + '"',
          data: {
            main: main_entry,
            entry: entry
          }
        });

        return true;
      }
    });

    if (!fail) {
      // prevent duplication
      if (!~entries.indexOf(main_entry)) {
        entries.push(main_entry);
      }

      callback(null);
    }
  });
};

build.ensure_ext = function(path, ext) {
  var regex = new RegExp('\\.' + ext + '$', 'i');

  if (!regex.test(path)) {
    path += '.' + ext;
  }

  return path;
};

build.run_build_task = function(target_version, to, options, callback) {
  var self = this;
  var cwd = options.cwd;
  async.each(options.entries, function (entry, done) {
    // Standardize entries
    // './index.js' -> 'index.js'
    entry = node_path.join('.', entry);
    var from = node_path.join(cwd, entry);

    builder.parse(from, {
      cwd: cwd,
      targetVersion: target_version,
      pkg: options.pkg

    }, function (err, content) {
      if (err) {
        return done(err);
      }

      var file_to = entry === options.main_entry
        // main entry will
        ? options.pkg.name + '.js'
        : entry;

      var path_to = node_path.join(to, file_to);
      fse.mkdirs( node_path.dirname(path_to), function (err) {
        if (err) {
          return done(err);
        }

        self.logger.info('{{cyan write}} ' + path_to);
        fs.writeFile(path_to, content, done);
      });
    });

  }, function (err) {
    if (err) {
      return callback(err);
    }

    self.copy_css(to, options, callback);
  });
};

build.copy_css = function(to, options, callback) {
  var pkg = options.pkg;
  var css = lang.object_member_by_namespaces(pkg, 'directories.css');

  if (css) {
    var source = node_path.join(options.cwd, css);
    var dest = node_path.join(to, css);
    this.cp(source, dest, callback);
  } else {
    callback(null);
  }
};

// publish files from `from` to `to`
build.cp = function(from, to, callback) {
  var self = this;
  if (from === to) {
    callback(null);
    return;
  }

  fs.exists(to, function(exists) {
    var tasks = [];

    if (exists) {
      tasks.push(function(done) {
        fse.remove(to, done);
      });
    }

    tasks.push(function(done) {
      self.logger.info('{{cyan copy}} ' + from + ' -> ' + to);
      fse.copy(from, to, done);
    });

    async.series(tasks, callback);
  });
};
<|endoftext|>


<commit_before>'use strict';
var mcapServe = require('mcap-serve');
var serverconfig = require('mcaprc');
var path = require('path');
var log = require('../logger')(__filename);
var printCat = require('../printcat');
var getCurrentAuthentication = require('../currentAuthentication');

var getEndpoint = function (baseurl, orga, appName, api) {
    var endpoint = '';
    endpoint += baseurl;
    if (endpoint.charAt(endpoint.length - 1 !== '/')) {
        endpoint += '/';
    }
    endpoint += orga + '/';
    endpoint += appName;
    endpoint += api;
    return endpoint;
};

module.exports.prepare = function (program, cb) {
    var server = null;
    if (program.serve === true) {
        server = serverconfig.get('default');
    } else if (program.serve) {
        server = serverconfig.get(program.serve);
    }
    if (!server) {
        var err = 'Missing server configuration or missing server instance.';
        printCat.error(err);
        return log.info(err);
    }

    var dir = process.cwd();
    if (program.cwd) {
        dir = program.cwd;
    }
    var mcapJson = require(path.resolve(dir, 'mcap.json'));

    var appName = mcapJson.name;
    var api = '/api/dataSync';

    printCat.info('Fetching informations');
    getCurrentAuthentication(server, function (err, data) {
        var orga;

        if (data && data.organization && data.organization.uniqueName) {
            orga = data.organization.uniqueName;
        } else {
            orga = 'system';
            printCat.warning('App is running without connection to %s', server.baseurl);
        }

        cb(server.baseurl, orga, appName, api);
    });
};

module.exports.serve = function (program) {

    module.exports.prepare(program, function (baseurl, orga, appName, api) {
        mcapServe({
            root: './client',
            enableLivereload: true,
            endpoint: getEndpoint(baseurl, orga, appName, api)
        }, function(err) {
            if (err) {
                printCat.error(err);
                process.exit(1);
            }
        });
    });
};
<commit_msg>fix to new mcaprc structure<commit_after>'use strict';
var mcapServe = require('mcap-serve');
var serverconfig = require('mcaprc');
var path = require('path');
var log = require('../logger')(__filename);
var printCat = require('../printcat');
var getCurrentAuthentication = require('../currentAuthentication');

var getEndpoint = function (baseurl, orga, appName, api) {
    var endpoint = '';
    endpoint += baseurl;
    if (endpoint.charAt(endpoint.length - 1 !== '/')) {
        endpoint += '/';
    }
    endpoint += orga + '/';
    endpoint += appName;
    endpoint += api;
    return endpoint;
};

module.exports.prepare = function (program, cb) {
    var server = null;
    if (program.serve === true) {
        server = serverconfig.get(serverconfig.default_server);
    } else if (program.serve) {
        server = serverconfig.get(program.serve);
    }
    if (!server) {
        var err = 'Missing server configuration or missing server instance.';
        printCat.error(err);
        return log.info(err);
    }

    var dir = process.cwd();
    if (program.cwd) {
        dir = program.cwd;
    }
    var mcapJson = require(path.resolve(dir, 'mcap.json'));

    var appName = mcapJson.name;
    var api = '/api/dataSync';

    printCat.info('Fetching informations');
    console.log(server);
    getCurrentAuthentication(server, function (err, data) {
        var orga;

        if (data && data.organization && data.organization.uniqueName) {
            orga = data.organization.uniqueName;
        } else {
            orga = 'system';
            printCat.warning('App is running without connection to %s', server.baseurl);
        }

        cb(server.baseurl, orga, appName, api);
    });
};

module.exports.serve = function (program) {

    module.exports.prepare(program, function (baseurl, orga, appName, api) {
        mcapServe({
            root: './client',
            enableLivereload: true,
            endpoint: getEndpoint(baseurl, orga, appName, api)
        }, function(err) {
            if (err) {
                printCat.error(err);
                process.exit(1);
            }
        });
    });
};
<|endoftext|>


<commit_before>var utils = require('../utils'),
    packages = require('../packages'),
    logger = require('../logger'),
    couchdb = require('../couchdb'),
    kansorc = require('../kansorc'),
    argParse = require('../args').parse,
    exec = require('child_process').exec,
    path = require('path'),
    fs = require('fs'),
    pathExists = fs.exists || path.exists,
    url = require('url'),
    urlParse = url.parse,
    urlFormat = url.format;

exports.summary = 'Load a project and push to a CouchDB database';

exports.usage = '' +
'kanso push [PATH] [DB]\n' +
'\n' +
'Parameters:\n' +
'  PATH    Path to project directory to push (defaults to ".")\n' +
'  DB      The CouchDB database to upload the app to, will use "default"\n' +
'          env set in .kansorc if none provided and .kansorc exists\n' +
'\n' +
'Options:\n' +
'  --minify    Compress CommonJS modules attachment using UglifyJS\n' +
'  --open      Open the app URL in the browser after pushing\n' +
'  --baseURL   Add a custom baseURL property to the kanso.json values\n' +
'  --id        Specify a custom document id for the generated app,\n' +
'              defaults to "_design/<name>" where <name> is from kanso.json';

/**
 * Main command called when typing "kanso push ..." on the command line
 */

exports.run = function (settings, args) {
    var a = argParse(args, {
        'minify': {match: '--minify'},
        'open': {match: '--open'},
        'baseURL': {match: '--baseURL', value: true},
        'id': {match: '--id', value: true}
    });
    var url;
    var dir = a.positional[0] || '.';
    pathExists(dir, function (exists) {
        if (!exists) {
            dir = process.cwd();
            url = a.positional[0];
        }
        dir = utils.abspath(dir);
        kansorc.extend(settings, dir + '/.kansorc', function (err, settings) {
            if (err) {
                return logger.error(err);
            }
            if (a.positional.length > 1) {
                url = a.positional[1];
            }
            else if (a.positional.length && !exists) {
                url = a.positional[0];
            }

            var env = utils.argToEnv(settings, url);
            url = env.db;
            if (env.baseURL) {
                a.options.baseURL = env.baseURL;
            }
            if (env.minify) {
                a.options.minify = env.minify;
            }
            if (env.id) {
                a.options.id = env.id;
            }

            // these options will override values in kanso.json
            // the 'id' and 'open' options are not relevant to that data
            var opt = env.overrides || {};
            opt.minify = a.options.minify;
            opt.baseURL = a.options.baseURL;

            exports.loadApp(dir, url, opt, settings,
                function (err, url, cfg, doc) {
                    if (err) {
                        logger.error(err);
                        if (settings.callback) settings.callback(err);
                        return;
                    }
                    var app_url = exports.getAppURL(url, cfg, doc);
                    var noauth_url = utils.noAuthURL(app_url);

                    if (a.options.open) {
                        exports.openInBrowser(app_url, function (err) {
                            if (err) {
                                return logger.error(err);
                            }
                            logger.end(noauth_url);
                            if (settings.callback) settings.callback();
                        });
                    }
                    else {
                        logger.end(noauth_url);
                        if (settings.callback) settings.callback();
                    }
                }
            );
        });
    });
};

/**
 * Builds the app prior to pushing
 */

exports.loadApp = function (dir, url, options, settings, callback) {
    var paths = settings.package_paths || [];
    var parent_dir = path.dirname(dir);
    if (path.basename(parent_dir) === 'packages') {
        paths.push(parent_dir);
    }
    var build_start = new Date().getTime();
    // make the url available to the modules
    options._url = utils.noAuthURL(url);
    options._utils = utils;
    packages.load(dir, paths, null, options, function (err, doc, cfg) {
        if (err) {
            if (callback) {
                return callback(err);
            }
            else {
                return logger.error(err);
            }
        }
        var build_end = new Date().getTime();
        console.log('Build complete: ' + (build_end - build_start) + 'ms');
        utils.completeAuth(url, false, function (err, url) {
            if (err) {
                return callback(err);
            }
            utils.catchAuthError(
                exports.push, url, [dir, options, cfg, doc], callback
            );
        });
    });
};

/**
 * Upload the app to the DB, catch auth errors and prompt for password
 */

exports.push = function (url, dir, options, cfg, doc, callback) {
    var db = couchdb(url);
    var root = couchdb(url);
    root.instance.pathname = '';
    root.session(function (err, info, resp) {
        if (err) {
            return callback(err);
        }
        var server_time = utils.ISODateString(new Date(resp.headers.date));
        if (!doc.kanso) {
            doc.kanso = {};
        }
        doc.kanso.push_time = server_time;
        doc.kanso.pushed_by = info.userCtx.name;

        db.ensureDB(function (err) {
            if (err) {
                return callback(err);
            }
            var id = options.id || '_design/' + cfg.name;
            logger.info('Uploading...');

            db.save(id, doc, {force: true}, function (err) {
                callback(err, url, cfg, doc);
            });
        });
    });
};

/**
 * Tries to determine the right URL to report on the command-line after a
 * successful push
 *
 * OK: <url>
 */

exports.getAppURL = function (url, cfg, doc) {
    var newurl= urlParse(url);
    delete newurl.auth;
    delete newurl.host;
    var ddoc_url = urlFormat(newurl) + '/' + doc._id;

    if (!/^_design\//.test(doc._id)) {
        // not pushed as a design doc, report doc url instead of app url
        return ddoc_url;
    }

    var app_url = ddoc_url;
    if (cfg.index) {
        // if there's an index property in kanso.json
        app_url = ddoc_url + cfg.index;
    }
    if (cfg.baseURL !== undefined) {
        // if there is a custom baseURL defined
        newurl.pathname = cfg.baseURL + '/';
        app_url = urlFormat(newurl);
    }
    else if (doc.rewrites && doc.rewrites.length) {
        app_url = ddoc_url + '/_rewrite/';
    }
    else if (doc._attachments && doc._attachments['index.html']) {
        app_url = ddoc_url + '/index.html';
    }
    else if (doc._attachments && doc._attachments['index.htm']) {
        app_url = ddoc_url + '/index.htm';
    }
    return app_url;
};

/**
 * Opens the app URL in the browser when the --open option is used
 */

exports.openInBrowser = function (url, callback) {
    if (process.platform === 'linux') {
        cmd = 'xdg-open';
    }
    else {
        // OSX
        cmd = 'open';
    }
    console.log('Opening URL in browser...');
    exec(cmd + ' ' + url, callback);
};
<commit_msg>removed ddoc.config._url property before push.<commit_after>var utils = require('../utils'),
    packages = require('../packages'),
    logger = require('../logger'),
    couchdb = require('../couchdb'),
    kansorc = require('../kansorc'),
    argParse = require('../args').parse,
    exec = require('child_process').exec,
    path = require('path'),
    fs = require('fs'),
    pathExists = fs.exists || path.exists,
    url = require('url'),
    urlParse = url.parse,
    urlFormat = url.format;

exports.summary = 'Load a project and push to a CouchDB database';

exports.usage = '' +
'kanso push [PATH] [DB]\n' +
'\n' +
'Parameters:\n' +
'  PATH    Path to project directory to push (defaults to ".")\n' +
'  DB      The CouchDB database to upload the app to, will use "default"\n' +
'          env set in .kansorc if none provided and .kansorc exists\n' +
'\n' +
'Options:\n' +
'  --minify    Compress CommonJS modules attachment using UglifyJS\n' +
'  --open      Open the app URL in the browser after pushing\n' +
'  --baseURL   Add a custom baseURL property to the kanso.json values\n' +
'  --id        Specify a custom document id for the generated app,\n' +
'              defaults to "_design/<name>" where <name> is from kanso.json';

/**
 * Main command called when typing "kanso push ..." on the command line
 */

exports.run = function (settings, args) {
    var a = argParse(args, {
        'minify': {match: '--minify'},
        'open': {match: '--open'},
        'baseURL': {match: '--baseURL', value: true},
        'id': {match: '--id', value: true}
    });
    var url;
    var dir = a.positional[0] || '.';
    pathExists(dir, function (exists) {
        if (!exists) {
            dir = process.cwd();
            url = a.positional[0];
        }
        dir = utils.abspath(dir);
        kansorc.extend(settings, dir + '/.kansorc', function (err, settings) {
            if (err) {
                return logger.error(err);
            }
            if (a.positional.length > 1) {
                url = a.positional[1];
            }
            else if (a.positional.length && !exists) {
                url = a.positional[0];
            }

            var env = utils.argToEnv(settings, url);
            url = env.db;
            if (env.baseURL) {
                a.options.baseURL = env.baseURL;
            }
            if (env.minify) {
                a.options.minify = env.minify;
            }
            if (env.id) {
                a.options.id = env.id;
            }

            // these options will override values in kanso.json
            // the 'id' and 'open' options are not relevant to that data
            var opt = env.overrides || {};
            opt.minify = a.options.minify;
            opt.baseURL = a.options.baseURL;

            exports.loadApp(dir, url, opt, settings,
                function (err, url, cfg, doc) {
                    if (err) {
                        logger.error(err);
                        if (settings.callback) settings.callback(err);
                        return;
                    }
                    var app_url = exports.getAppURL(url, cfg, doc);
                    var noauth_url = utils.noAuthURL(app_url);

                    if (a.options.open) {
                        exports.openInBrowser(app_url, function (err) {
                            if (err) {
                                return logger.error(err);
                            }
                            logger.end(noauth_url);
                            if (settings.callback) settings.callback();
                        });
                    }
                    else {
                        logger.end(noauth_url);
                        if (settings.callback) settings.callback();
                    }
                }
            );
        });
    });
};

/**
 * Builds the app prior to pushing
 */

exports.loadApp = function (dir, url, options, settings, callback) {
    var paths = settings.package_paths || [];
    var parent_dir = path.dirname(dir);
    if (path.basename(parent_dir) === 'packages') {
        paths.push(parent_dir);
    }
    var build_start = new Date().getTime();
    // make the url available to the modules
    options._url = url;
    options._utils = utils;
    packages.load(dir, paths, null, options, function (err, doc, cfg) {
        if (err) {
            if (callback) {
                return callback(err);
            }
            else {
                return logger.error(err);
            }
        }
        var build_end = new Date().getTime();
        console.log('Build complete: ' + (build_end - build_start) + 'ms');
        utils.completeAuth(url, false, function (err, url) {
            if (err) {
                return callback(err);
            }
            utils.catchAuthError(
                exports.push, url, [dir, options, cfg, doc], callback
            );
        });
    });
};

/**
 * Upload the app to the DB, catch auth errors and prompt for password
 */

exports.push = function (url, dir, options, cfg, doc, callback) {
    var db = couchdb(url);
    var root = couchdb(url);
    root.instance.pathname = '';
    root.session(function (err, info, resp) {
        if (err) {
            return callback(err);
        }
        var server_time = utils.ISODateString(new Date(resp.headers.date));
        if (!doc.kanso) {
            doc.kanso = {};
        }
        doc.kanso.push_time = server_time;
        doc.kanso.pushed_by = info.userCtx.name;

        // delete private/build time only properties
        delete doc.kanso.config._url;

        db.ensureDB(function (err) {
            if (err) {
                return callback(err);
            }
            var id = options.id || '_design/' + cfg.name;
            logger.info('Uploading...');

            db.save(id, doc, {force: true}, function (err) {
                callback(err, url, cfg, doc);
            });
        });
    });
};

/**
 * Tries to determine the right URL to report on the command-line after a
 * successful push
 *
 * OK: <url>
 */

exports.getAppURL = function (url, cfg, doc) {
    var newurl= urlParse(url);
    delete newurl.auth;
    delete newurl.host;
    var ddoc_url = urlFormat(newurl) + '/' + doc._id;

    if (!/^_design\//.test(doc._id)) {
        // not pushed as a design doc, report doc url instead of app url
        return ddoc_url;
    }

    var app_url = ddoc_url;
    if (cfg.index) {
        // if there's an index property in kanso.json
        app_url = ddoc_url + cfg.index;
    }
    if (cfg.baseURL !== undefined) {
        // if there is a custom baseURL defined
        newurl.pathname = cfg.baseURL + '/';
        app_url = urlFormat(newurl);
    }
    else if (doc.rewrites && doc.rewrites.length) {
        app_url = ddoc_url + '/_rewrite/';
    }
    else if (doc._attachments && doc._attachments['index.html']) {
        app_url = ddoc_url + '/index.html';
    }
    else if (doc._attachments && doc._attachments['index.htm']) {
        app_url = ddoc_url + '/index.htm';
    }
    return app_url;
};

/**
 * Opens the app URL in the browser when the --open option is used
 */

exports.openInBrowser = function (url, callback) {
    if (process.platform === 'linux') {
        cmd = 'xdg-open';
    }
    else {
        // OSX
        cmd = 'open';
    }
    console.log('Opening URL in browser...');
    exec(cmd + ' ' + url, callback);
};
<|endoftext|>


<commit_before>var format        = require('util').format,
    moment        = require('moment'),
    cfg           = require('nconf'),
    _s            = require('underscore.string'),
    pgnUrl        = cfg.get('app:pageUrlFormat'),
    pgnRegexp     = new RegExp(pgnUrl.replace(/\//g, '\\/').replace(':page', '\\d+'));

/*
 * Builds post or static page URL
 */
function postUrl (post, absolute) {
    if (arguments.length < 2 && 'boolean' === typeof post) {
        absolute = post;
        post = this.locals.post;
    }

    var output = post.page ? '/:slug' : cfg.get('app:urlFormat'),
        tags = {
            ':year':   function () { return moment(post.published_at).format('YYYY'); },
            ':month':  function () { return moment(post.published_at).format('MM'); },
            ':day':    function () { return moment(post.published_at).format('DD'); },
            ':slug':   function () { return post.slug; },
            ':id':     function () { return post.id; }
        };

    // replace tags like :slug or :year with actual values
    output = output.replace(/(:[a-z]+)/g, function (match) {
        if (match in tags) {
            return tags[match]();
        }
    });

    return absolute ? cfg.get('url') + output : output;
};

/*
 * Builds pagination URL
 * It assumes that 'this' is current response
 */
function pageUrl (newer) {
    var pagination = this.locals.pagination,
        page = newer ? pagination.newer : pagination.older,
        ctx = this.locals.context.replace(pgnRegexp, '');

    if ('/' === ctx) ctx = '';

    return 1 === page ? '/' : ctx + pgnUrl.replace(':page', page);
}

/*
 * Formats scheduled date for the given post and date format
 */
function dateFormat (post, format) {
    return post.scheduled
        ? moment(post.scheduled).format(format || "YYYY-MM-DD")
        : '';
};

function labelsFormat (post, join) {
    if(2 > arguments.length && 'string' === typeof post) {
        join = post;
        post = this.locals.post;
    }

    return post.labels.join(join || ', ');
};

function metaTitle () {
    return cfg.get('app:defaultMetaTitle') || cfg.get('app:title');
}

function metaDescription () {
    return cfg.get('app:defaultMetaDesc') || cfg.get('app:description');
}

function metaKeywords () {
    return cfg.get('app:defaultKeywords');
}

function encode (text) {
    return encodeURIComponent(text);
}

function bodyClass () {
    if ('/' === this.locals.context) return 'home-template';
    if (+this.req.params.page) return 'archive-template';

    var post = this.locals.post;
    if (post) {
        var bodyClass = ['post-template'];

        if (post.page) bodyClass.push('page');

        (post.labels || []).forEach(function (label) {
            bodyClass.push('tag-' + _s.slugify(label));
        })

        return bodyClass;
    }
}

function postClass (post) {
    return "";
}

/*
 * Generates an asset URL.
 * It assumes that 'this' is current application
 */
function assets (asset) {
    return '/assets' + ('/' === asset[0] ? '' : '/') + asset + '?v=ad2e223fd'
}

function initRequest (req, res, next) {
    // Set default response local variables
    res.locals({
        context:    req.path,
        metaTitle:  metaTitle,
        $url:       postUrl.bind(res),
        $pageUrl:   pageUrl.bind(res),
        $labels:    labelsFormat.bind(res)
    })

    Object.defineProperties(res.locals, {
        "bodyClass": { enumerable: true, get: bodyClass.bind(res) },
        "metaTitle": { enumerable: true, get: metaTitle.bind(res) },
        "metaDescription": { enumerable: true, get: metaDescription.bind(res) },
        "metaKeywords":  { enumerable: true, get: metaKeywords.bind(res) }
    })

    next();
}

function init (app) {

    // Set default application local variables as well as template helper functions
    app.locals({
        title:          cfg.get('app:title'),
        description:    cfg.get('app:description'),
        url:            cfg.get('url'),
        copyright:      cfg.get('app:copyright'),
        cover:          '/assets/img/header.jpg',
        $date:          dateFormat,
        $encode:        encode,
        $postClass:     postClass,
        $assets:        assets.bind(app)
    })

    // Update application locals with view settings like debug or pretty formatting
    app.locals(cfg.get('view'));

    // Initialize reqest / response specific variables
    app.use(initRequest);
};

module.exports = init;<commit_msg>Removed unused modules<commit_after>var moment        = require('moment'),
    cfg           = require('nconf'),
    _s            = require('underscore.string'),
    pgnUrl        = cfg.get('app:pageUrlFormat'),
    pgnRegexp     = new RegExp(pgnUrl.replace(/\//g, '\\/').replace(':page', '\\d+'));

/*
 * Builds post or static page URL
 */
function postUrl (post, absolute) {
    if (arguments.length < 2 && 'boolean' === typeof post) {
        absolute = post;
        post = this.locals.post;
    }

    var output = post.page ? '/:slug' : cfg.get('app:urlFormat'),
        tags = {
            ':year':   function () { return moment(post.published_at).format('YYYY'); },
            ':month':  function () { return moment(post.published_at).format('MM'); },
            ':day':    function () { return moment(post.published_at).format('DD'); },
            ':slug':   function () { return post.slug; },
            ':id':     function () { return post.id; }
        };

    // replace tags like :slug or :year with actual values
    output = output.replace(/(:[a-z]+)/g, function (match) {
        if (match in tags) {
            return tags[match]();
        }
    });

    return absolute ? cfg.get('url') + output : output;
};

/*
 * Builds pagination URL
 * It assumes that 'this' is current response
 */
function pageUrl (newer) {
    var pagination = this.locals.pagination,
        page = newer ? pagination.newer : pagination.older,
        ctx = this.locals.context.replace(pgnRegexp, '');

    if ('/' === ctx) ctx = '';

    return 1 === page ? '/' : ctx + pgnUrl.replace(':page', page);
}

/*
 * Formats scheduled date for the given post and date format
 */
function dateFormat (post, format) {
    return post.scheduled
        ? moment(post.scheduled).format(format || "YYYY-MM-DD")
        : '';
};

function labelsFormat (post, join) {
    if(2 > arguments.length && 'string' === typeof post) {
        join = post;
        post = this.locals.post;
    }

    return post.labels.join(join || ', ');
};

function metaTitle () {
    return cfg.get('app:defaultMetaTitle') || cfg.get('app:title');
}

function metaDescription () {
    return cfg.get('app:defaultMetaDesc') || cfg.get('app:description');
}

function metaKeywords () {
    return cfg.get('app:defaultKeywords');
}

function encode (text) {
    return encodeURIComponent(text);
}

function bodyClass () {
    if ('/' === this.locals.context) return 'home-template';
    if (+this.req.params.page) return 'archive-template';

    var post = this.locals.post;
    if (post) {
        var bodyClass = ['post-template'];

        if (post.page) bodyClass.push('page');

        (post.labels || []).forEach(function (label) {
            bodyClass.push('tag-' + _s.slugify(label));
        })

        return bodyClass;
    }
}

function postClass (post) {
    return "";
}

/*
 * Generates an asset URL.
 * It assumes that 'this' is current application
 */
function assets (asset) {
    return '/assets' + ('/' === asset[0] ? '' : '/') + asset + '?v=ad2e223fd'
}

function initRequest (req, res, next) {
    // Set default response local variables
    res.locals({
        context:    req.path,
        metaTitle:  metaTitle,
        $url:       postUrl.bind(res),
        $pageUrl:   pageUrl.bind(res),
        $labels:    labelsFormat.bind(res)
    })

    Object.defineProperties(res.locals, {
        "bodyClass": { enumerable: true, get: bodyClass.bind(res) },
        "metaTitle": { enumerable: true, get: metaTitle.bind(res) },
        "metaDescription": { enumerable: true, get: metaDescription.bind(res) },
        "metaKeywords":  { enumerable: true, get: metaKeywords.bind(res) }
    })

    next();
}

function init (app) {

    // Set default application local variables as well as template helper functions
    app.locals({
        title:          cfg.get('app:title'),
        description:    cfg.get('app:description'),
        url:            cfg.get('url'),
        copyright:      cfg.get('app:copyright'),
        cover:          '/assets/img/header.jpg',
        $date:          dateFormat,
        $encode:        encode,
        $postClass:     postClass,
        $assets:        assets.bind(app)
    })

    // Update application locals with view settings like debug or pretty formatting
    app.locals(cfg.get('view'));

    // Initialize reqest / response specific variables
    app.use(initRequest);
};

module.exports = init;<|endoftext|>


<commit_before>var _ = require('lodash');

var types = {
  string: 1,
  integer: 2,
  boolean: 3,
  number: 4,
  array: 5,
  enum: 6
};

var truthies = [ 'true',  't', '1', 1 ];
var falsies =  [ 'false', 'f', '0', 0 ];

var schema = [
  {
    name: 'default_org',
    type: types.string,
    nullable: true,
    defaultValue: null
  },
  {
    name: 'deploy_mode',
    type: types.enum,
    nullable: false,
    enumValues: ['dynamic', 'metadata'],
    defaultValue: 'dynamic'
  },
  {
    name: 'api_version',
    type: types.integer,
    nullable: true,
    defaultValue: 32
  },
  {
    name: 'colorize',
    type: types.boolean,
    nullable: false,
    defaultValue: true
  },
  {
    name: 'background',
    type: types.enum,
    nullable: false,
    enumValues: ['light', 'dark'],
    defaultValue: 'light'
  },
  {
    name: 'log_level',
    type: types.enum,
    nullable: true,
    enumValues: ['none', 'debug', 'info'],
    defaultValue: 'info'
  }
];

function getDefaultConfig() {
  return _.reduce(schema, function(result, val, key) {
    if(!_.isUndefined(val.defaultValue)) {
      result[val.name] = val.defaultValue;
    }
    return result;
  }, {});
}

function getBlankSchema() {
  return _.reduce(schema, function(result, val, key) {
    result[val.name] = undefined;
    return result;
  }, {});
}

function getPropSchema(prop) {
  if(!prop) return;
  return _.find(schema, function(item) {
    return (item.name.toLowerCase() === prop.toLowerCase().trim());
  });
}

function isValidProperty(prop) {
  if(!prop) return;
  var val = getPropSchema(prop);
  return (val) ? true : false;
}

function normalizeInput(prop, value) {
  if(!prop) return;
  var propSchema = getPropSchema(prop);

  if(_.isUndefined(value)) value = null;

  var normalized = {
    name: propSchema.name,
    valid: true,
  };

  if(_.isNull(value)) {
    normalized.value = value;
    if(!propSchema.nullable) {
      normalized.valid = false;
      normalized.reason = 'not nullable';
    }
  } else {

    switch(propSchema.type) {

      // handle string types
      case types.string:
        normalized.value = (_.isString(value)) ? value : value.toString();
        break;

      // handle integer types
      case types.integer:
        normalized.value = (_.isNumber(value)) ? Math.floor(value) :
          (_.isString(value)) ? parseInt(value, 10) :
          (value === true) ? 1 : 0;
        break;

      // handle floats
      case types.number:
        normalized.value = (_.isNumber(value)) ? value :
          (_.isString(value)) ? parseFloat(value) :
          (value === true) ? 1 : 0;
        break;

      // handle boolean values
      case types.boolean:
        if(_.isBoolean(value)) {
          normalized.value = value;
          break;
        }

        if(_.isString(value)) {
          var input = value.toLowerCase().trim();
          if(_.indexOf(truthies, input) !== -1) {
            normalized.value = true;
            break;
          } else if(_.indexOf(falsies, input) !== -1) {
            normalized.value = false;
            break;
          }
        }

        if(_.isNumber(value) && value === 1) {
          normalized.value = true;
          break;
        }

        if(_.isNumber(value) && value === 0) {
          normalized.value = false;
          break;
        }

        normalized.value = value;
        normalized.valid = false;
        normalized.reason = 'invalid boolean';
        break;

      case types.enum:
        var val = (_.isString(value)) ? value : value.toString();

        val = val.toLowerCase().trim();

        normalized.value = val;

        if(_.indexOf(propSchema.enumValues, val) === -1) {
          normalized.valid = false;
          normalized.reason = 'must be one of [' +
            propSchema.enumValues.join(', ') +
            ']';
        }

        break;

      // default behavior
      default:
        normalized.value = value;
        normalized.valid = false;
        normalized.reason = 'unrecognized type';

    }

  }

  return normalized;
}

function getDefaultValue(prop) {
  if(!prop) return;
  var propSchema = getPropSchema(prop);

  if(!propSchema) return;

  return propSchema.defaultValue;
}

module.exports.getDefaultConfig = getDefaultConfig;
module.exports.getBlankSchema   = getBlankSchema;
module.exports.isValidProperty  = isValidProperty;
module.exports.getPropSchema    = getPropSchema;
module.exports.normalizeInput   = normalizeInput;
module.exports.getDefaultValue  = getDefaultValue;
<commit_msg>Updating default sfdc api to 37. This enables deployment of the full lightning component bundle<commit_after>var _ = require('lodash');

var types = {
  string: 1,
  integer: 2,
  boolean: 3,
  number: 4,
  array: 5,
  enum: 6
};

var truthies = [ 'true',  't', '1', 1 ];
var falsies =  [ 'false', 'f', '0', 0 ];

var schema = [
  {
    name: 'default_org',
    type: types.string,
    nullable: true,
    defaultValue: null
  },
  {
    name: 'deploy_mode',
    type: types.enum,
    nullable: false,
    enumValues: ['dynamic', 'metadata'],
    defaultValue: 'dynamic'
  },
  {
    name: 'api_version',
    type: types.integer,
    nullable: true,
    defaultValue: 37
  },
  {
    name: 'colorize',
    type: types.boolean,
    nullable: false,
    defaultValue: true
  },
  {
    name: 'background',
    type: types.enum,
    nullable: false,
    enumValues: ['light', 'dark'],
    defaultValue: 'light'
  },
  {
    name: 'log_level',
    type: types.enum,
    nullable: true,
    enumValues: ['none', 'debug', 'info'],
    defaultValue: 'info'
  }
];

function getDefaultConfig() {
  return _.reduce(schema, function(result, val, key) {
    if(!_.isUndefined(val.defaultValue)) {
      result[val.name] = val.defaultValue;
    }
    return result;
  }, {});
}

function getBlankSchema() {
  return _.reduce(schema, function(result, val, key) {
    result[val.name] = undefined;
    return result;
  }, {});
}

function getPropSchema(prop) {
  if(!prop) return;
  return _.find(schema, function(item) {
    return (item.name.toLowerCase() === prop.toLowerCase().trim());
  });
}

function isValidProperty(prop) {
  if(!prop) return;
  var val = getPropSchema(prop);
  return (val) ? true : false;
}

function normalizeInput(prop, value) {
  if(!prop) return;
  var propSchema = getPropSchema(prop);

  if(_.isUndefined(value)) value = null;

  var normalized = {
    name: propSchema.name,
    valid: true,
  };

  if(_.isNull(value)) {
    normalized.value = value;
    if(!propSchema.nullable) {
      normalized.valid = false;
      normalized.reason = 'not nullable';
    }
  } else {

    switch(propSchema.type) {

      // handle string types
      case types.string:
        normalized.value = (_.isString(value)) ? value : value.toString();
        break;

      // handle integer types
      case types.integer:
        normalized.value = (_.isNumber(value)) ? Math.floor(value) :
          (_.isString(value)) ? parseInt(value, 10) :
          (value === true) ? 1 : 0;
        break;

      // handle floats
      case types.number:
        normalized.value = (_.isNumber(value)) ? value :
          (_.isString(value)) ? parseFloat(value) :
          (value === true) ? 1 : 0;
        break;

      // handle boolean values
      case types.boolean:
        if(_.isBoolean(value)) {
          normalized.value = value;
          break;
        }

        if(_.isString(value)) {
          var input = value.toLowerCase().trim();
          if(_.indexOf(truthies, input) !== -1) {
            normalized.value = true;
            break;
          } else if(_.indexOf(falsies, input) !== -1) {
            normalized.value = false;
            break;
          }
        }

        if(_.isNumber(value) && value === 1) {
          normalized.value = true;
          break;
        }

        if(_.isNumber(value) && value === 0) {
          normalized.value = false;
          break;
        }

        normalized.value = value;
        normalized.valid = false;
        normalized.reason = 'invalid boolean';
        break;

      case types.enum:
        var val = (_.isString(value)) ? value : value.toString();

        val = val.toLowerCase().trim();

        normalized.value = val;

        if(_.indexOf(propSchema.enumValues, val) === -1) {
          normalized.valid = false;
          normalized.reason = 'must be one of [' +
            propSchema.enumValues.join(', ') +
            ']';
        }

        break;

      // default behavior
      default:
        normalized.value = value;
        normalized.valid = false;
        normalized.reason = 'unrecognized type';

    }

  }

  return normalized;
}

function getDefaultValue(prop) {
  if(!prop) return;
  var propSchema = getPropSchema(prop);

  if(!propSchema) return;

  return propSchema.defaultValue;
}

module.exports.getDefaultConfig = getDefaultConfig;
module.exports.getBlankSchema   = getBlankSchema;
module.exports.isValidProperty  = isValidProperty;
module.exports.getPropSchema    = getPropSchema;
module.exports.normalizeInput   = normalizeInput;
module.exports.getDefaultValue  = getDefaultValue;
<|endoftext|>


<commit_before>const debug = require('ghost-ignition').debug('services:url:resources'),
    Promise = require('bluebird'),
    _ = require('lodash'),
    Resource = require('./Resource'),
    config = require('../../config'),
    models = require('../../models'),
    common = require('../../lib/common');

/**
 * These are the default resources and filters.
 * These are the minimum filters for public accessibility of resources.
 */
const resourcesConfig = [
    {
        type: 'posts',
        modelOptions: {
            modelName: 'Post',
            filter: 'visibility:public+status:published+page:false',
            reducedFields: true,
            withRelated: ['tags', 'authors'],
            withRelatedFields: {
                tags: ['tags.id', 'tags.slug'],
                authors: ['users.id', 'users.slug']
            }
        },
        events: {
            add: 'post.published',
            update: 'post.published.edited',
            remove: 'post.unpublished'
        }
    },
    {
        type: 'pages',
        modelOptions: {
            modelName: 'Post',
            reducedFields: true,
            filter: 'visibility:public+status:published+page:true'
        },
        events: {
            add: 'page.published',
            update: 'page.published.edited',
            remove: 'page.unpublished'
        }
    },
    {
        type: 'tags',
        keep: ['id', 'slug', 'updated_at', 'created_at'],
        modelOptions: {
            modelName: 'Tag',
            reducedFields: true,
            filter: 'visibility:public'
        },
        events: {
            add: 'tag.added',
            update: 'tag.edited',
            remove: 'tag.deleted'
        }
    },
    {
        type: 'users',
        modelOptions: {
            modelName: 'User',
            reducedFields: true,
            filter: 'visibility:public'
        },
        events: {
            add: 'user.activated',
            update: 'user.activated.edited',
            remove: 'user.deactivated'
        }
    }
];

/**
 * NOTE: We are querying knex directly, because the Bookshelf ORM overhead is too slow.
 */
class Resources {
    constructor(queue) {
        this.queue = queue;
        this.data = {};

        this.listeners = [];
        this._listeners();
    }

    _listenOn(eventName, listener) {
        this.listeners.push({
            eventName: eventName,
            listener: listener
        });

        common.events.on(eventName, listener);
    }

    _listeners() {
        /**
         * We fetch the resources as early as possible.
         * Currently the url service needs to use the settings cache,
         * because we need to `settings.permalink`.
         */
        this._listenOn('db.ready', this._onDatabaseReady.bind(this));
    }

    _onDatabaseReady() {
        const ops = [];
        debug('db ready. settings cache ready.');

        _.each(resourcesConfig, (resourceConfig) => {
            this.data[resourceConfig.type] = [];
            ops.push(this._fetch(resourceConfig));

            this._listenOn(resourceConfig.events.add, (model) => {
                return this._onResourceAdded.bind(this)(resourceConfig.type, model);
            });

            this._listenOn(resourceConfig.events.update, (model) => {
                return this._onResourceUpdated.bind(this)(resourceConfig.type, model);
            });

            this._listenOn(resourceConfig.events.remove, (model) => {
                return this._onResourceRemoved.bind(this)(resourceConfig.type, model);
            });
        });

        Promise.all(ops)
            .then(() => {
                // CASE: all resources are fetched, start the queue
                this.queue.start({
                    event: 'init',
                    tolerance: 100,
                    requiredSubscriberCount: 1
                });
            });
    }

    _fetch(resourceConfig, options = {offset: 0, limit: 999}) {
        debug('_fetch', resourceConfig.type, resourceConfig.modelOptions);

        let modelOptions = _.cloneDeep(resourceConfig.modelOptions);
        const isSQLite = config.get('database:client') === 'sqlite3';

        // CASE: prevent "too many SQL variables" error on SQLite3
        if (isSQLite) {
            modelOptions.offset = options.offset;
            modelOptions.limit = options.limit;
        }

        return models.Base.Model.raw_knex.fetchAll(modelOptions)
            .then((objects) => {
                debug('fetched', resourceConfig.type, objects.length);

                _.each(objects, (object) => {
                    this.data[resourceConfig.type].push(new Resource(resourceConfig.type, object));
                });

                if (objects.length && isSQLite) {
                    options.offset = (options.offset + 1) * options.limit;
                    return this._fetch(resourceConfig, {offset: options.offset, limit: options.limit});
                }
            });
    }

    _onResourceAdded(type, model) {
        const resource = new Resource(type, model.toJSON());

        debug('_onResourceAdded', type);
        this.data[type].push(resource);

        this.queue.start({
            event: 'added',
            action: 'added:' + model.id,
            eventData: {
                id: model.id,
                type: type
            }
        });
    }

    /**
     * CASE:
     *  - post was fetched on bootstrap
     *  - that means, the post is already published
     *  - resource exists, but nobody owns it
     *  - if the model changes, it can be that somebody will then own the post
     *
     * CASE:
     *   - post was fetched on bootstrap
     *   - that means, the post is already published
     *   - resource exists and is owned by somebody
     *   - but the data changed and is maybe no longer owned?
     *   - e.g. featured:false changes and your filter requires featured posts
     */
    _onResourceUpdated(type, model) {
        debug('_onResourceUpdated', type);

        this.data[type].every((resource) => {
            if (resource.data.id === model.id) {
                resource.update(model.toJSON());

                // CASE: pretend it was added
                if (!resource.isReserved()) {
                    this.queue.start({
                        event: 'added',
                        action: 'added:' + model.id,
                        eventData: {
                            id: model.id,
                            type: type
                        }
                    });
                }

                // break!
                return false;
            }

            return true;
        });
    }

    _onResourceRemoved(type, model) {
        let index = null;
        let resource;

        this.data[type].every((_resource, _index) => {
            if (_resource.data.id === model._previousAttributes.id) {
                resource = _resource;
                index = _index;
                // break!
                return false;
            }

            return true;
        });

        // CASE: there are possible cases that the resource was not fetched e.g. visibility is internal
        if (index === null) {
            debug('can\'t find resource', model._previousAttributes.id);
            return;
        }

        delete this.data[type][index];
        resource.remove();
    }

    getAll() {
        return this.data;
    }

    getAllByType(type) {
        return this.data[type];
    }

    getByIdAndType(type, id) {
        return _.find(this.data[type], {data: {id: id}});
    }

    reset() {
        _.each(this.listeners, (obj) => {
            common.events.removeListener(obj.eventName, obj.listener);
        });

        this.listeners = [];
        this.data = {};
    }

    softReset() {
        this.data = {};

        _.each(resourcesConfig, (resourceConfig) => {
            this.data[resourceConfig.type] = [];
        });
    }
}

module.exports = Resources;
<commit_msg>Fixed offset calculation for SQlite3<commit_after>const debug = require('ghost-ignition').debug('services:url:resources'),
    Promise = require('bluebird'),
    _ = require('lodash'),
    Resource = require('./Resource'),
    config = require('../../config'),
    models = require('../../models'),
    common = require('../../lib/common');

/**
 * These are the default resources and filters.
 * These are the minimum filters for public accessibility of resources.
 */
const resourcesConfig = [
    {
        type: 'posts',
        modelOptions: {
            modelName: 'Post',
            filter: 'visibility:public+status:published+page:false',
            reducedFields: true,
            withRelated: ['tags', 'authors'],
            withRelatedFields: {
                tags: ['tags.id', 'tags.slug'],
                authors: ['users.id', 'users.slug']
            }
        },
        events: {
            add: 'post.published',
            update: 'post.published.edited',
            remove: 'post.unpublished'
        }
    },
    {
        type: 'pages',
        modelOptions: {
            modelName: 'Post',
            reducedFields: true,
            filter: 'visibility:public+status:published+page:true'
        },
        events: {
            add: 'page.published',
            update: 'page.published.edited',
            remove: 'page.unpublished'
        }
    },
    {
        type: 'tags',
        keep: ['id', 'slug', 'updated_at', 'created_at'],
        modelOptions: {
            modelName: 'Tag',
            reducedFields: true,
            filter: 'visibility:public'
        },
        events: {
            add: 'tag.added',
            update: 'tag.edited',
            remove: 'tag.deleted'
        }
    },
    {
        type: 'users',
        modelOptions: {
            modelName: 'User',
            reducedFields: true,
            filter: 'visibility:public'
        },
        events: {
            add: 'user.activated',
            update: 'user.activated.edited',
            remove: 'user.deactivated'
        }
    }
];

/**
 * NOTE: We are querying knex directly, because the Bookshelf ORM overhead is too slow.
 */
class Resources {
    constructor(queue) {
        this.queue = queue;
        this.data = {};

        this.listeners = [];
        this._listeners();
    }

    _listenOn(eventName, listener) {
        this.listeners.push({
            eventName: eventName,
            listener: listener
        });

        common.events.on(eventName, listener);
    }

    _listeners() {
        /**
         * We fetch the resources as early as possible.
         * Currently the url service needs to use the settings cache,
         * because we need to `settings.permalink`.
         */
        this._listenOn('db.ready', this._onDatabaseReady.bind(this));
    }

    _onDatabaseReady() {
        const ops = [];
        debug('db ready. settings cache ready.');

        _.each(resourcesConfig, (resourceConfig) => {
            this.data[resourceConfig.type] = [];
            ops.push(this._fetch(resourceConfig));

            this._listenOn(resourceConfig.events.add, (model) => {
                return this._onResourceAdded.bind(this)(resourceConfig.type, model);
            });

            this._listenOn(resourceConfig.events.update, (model) => {
                return this._onResourceUpdated.bind(this)(resourceConfig.type, model);
            });

            this._listenOn(resourceConfig.events.remove, (model) => {
                return this._onResourceRemoved.bind(this)(resourceConfig.type, model);
            });
        });

        Promise.all(ops)
            .then(() => {
                // CASE: all resources are fetched, start the queue
                this.queue.start({
                    event: 'init',
                    tolerance: 100,
                    requiredSubscriberCount: 1
                });
            });
    }

    _fetch(resourceConfig, options = {offset: 0, limit: 999}) {
        debug('_fetch', resourceConfig.type, resourceConfig.modelOptions);

        let modelOptions = _.cloneDeep(resourceConfig.modelOptions);
        const isSQLite = config.get('database:client') === 'sqlite3';

        // CASE: prevent "too many SQL variables" error on SQLite3
        if (isSQLite) {
            modelOptions.offset = options.offset;
            modelOptions.limit = options.limit;
        }

        return models.Base.Model.raw_knex.fetchAll(modelOptions)
            .then((objects) => {
                debug('fetched', resourceConfig.type, objects.length);

                _.each(objects, (object) => {
                    this.data[resourceConfig.type].push(new Resource(resourceConfig.type, object));
                });

                if (objects.length && isSQLite) {
                    options.offset = options.offset + options.limit;
                    return this._fetch(resourceConfig, {offset: options.offset, limit: options.limit});
                }
            });
    }

    _onResourceAdded(type, model) {
        const resource = new Resource(type, model.toJSON());

        debug('_onResourceAdded', type);
        this.data[type].push(resource);

        this.queue.start({
            event: 'added',
            action: 'added:' + model.id,
            eventData: {
                id: model.id,
                type: type
            }
        });
    }

    /**
     * CASE:
     *  - post was fetched on bootstrap
     *  - that means, the post is already published
     *  - resource exists, but nobody owns it
     *  - if the model changes, it can be that somebody will then own the post
     *
     * CASE:
     *   - post was fetched on bootstrap
     *   - that means, the post is already published
     *   - resource exists and is owned by somebody
     *   - but the data changed and is maybe no longer owned?
     *   - e.g. featured:false changes and your filter requires featured posts
     */
    _onResourceUpdated(type, model) {
        debug('_onResourceUpdated', type);

        this.data[type].every((resource) => {
            if (resource.data.id === model.id) {
                resource.update(model.toJSON());

                // CASE: pretend it was added
                if (!resource.isReserved()) {
                    this.queue.start({
                        event: 'added',
                        action: 'added:' + model.id,
                        eventData: {
                            id: model.id,
                            type: type
                        }
                    });
                }

                // break!
                return false;
            }

            return true;
        });
    }

    _onResourceRemoved(type, model) {
        let index = null;
        let resource;

        this.data[type].every((_resource, _index) => {
            if (_resource.data.id === model._previousAttributes.id) {
                resource = _resource;
                index = _index;
                // break!
                return false;
            }

            return true;
        });

        // CASE: there are possible cases that the resource was not fetched e.g. visibility is internal
        if (index === null) {
            debug('can\'t find resource', model._previousAttributes.id);
            return;
        }

        delete this.data[type][index];
        resource.remove();
    }

    getAll() {
        return this.data;
    }

    getAllByType(type) {
        return this.data[type];
    }

    getByIdAndType(type, id) {
        return _.find(this.data[type], {data: {id: id}});
    }

    reset() {
        _.each(this.listeners, (obj) => {
            common.events.removeListener(obj.eventName, obj.listener);
        });

        this.listeners = [];
        this.data = {};
    }

    softReset() {
        this.data = {};

        _.each(resourcesConfig, (resourceConfig) => {
            this.data[resourceConfig.type] = [];
        });
    }
}

module.exports = Resources;
<|endoftext|>