#!/usr/bin/env bash

SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/include.sh"

echo -e "\033[1mRunning tests:\033[0m"

cd $file_path

#
# Different way to stop process
#
$pm2 start echo.js
$pm2 start echo.js -f
$pm2 start echo.js -f

should 'should have start 3 apps' 'restart_time' 3

$pm2 stop 12412
$pm2 stop 0

should 'should have stopped 1 apps' 'stopped' 1

$pm2 stop asdsdaecho.js

$pm2 stop echo

should 'should have stopped 3 apps' 'stopped' 3

#
# Describe process
#
$pm2 describe 0
spec "should describe stopped process"

$pm2 restart 1

$pm2 describe 1
spec "should describe online process"

$pm2 describe asdsa
ispec "should exit with right exit code when no process found"

#
# Update pm2
#
$pm2 updatePM2
spec "should update pm2"

#
# Verify PID
#
$pm2 kill

$pm2 start echo.js -p echo.pid

sleep 0.5
ls echo-0.pid
spec "should pid file exists"

$pm2 stop all

sleep 1

ls echo-0.pid
ispec "should pid file be deleted once stopped"

$pm2 kill

$pm2 start echo.js -p echo.pid -i 1

sleep 1

ls echo-0.pid
spec "should pid file exists"

$pm2 stop all

sleep 1

ls echo-0.pid
ispec "should pid file be deleted once stopped"

$pm2 kill

#
# Main tests
#
$pm2 kill
spec "kill daemon"

$pm2 start eyayimfake
ispec "should fail if script doesnt exist"

$pm2
ispec "No argument"

$pm2 list

$pm2 start cluster-pm2.json
spec "Should start well formated json with name for file prefix"

$pm2 list
spec "Should list processes successfully"

$pm2 start multi-echo.json
spec "Should start multiple applications"

$pm2 generate echo
spec "Should generate echo sample json"

$pm2 start echo-pm2.json -f
spec "Should start echo service"

$pm2 list

# $pm2 logs &
# spec "Should display logs"
# TMPPID=$!

# sleep 1

# kill $!
# spec "Should kill logs"

# $pm2 logs echo &
# spec "Should display logs"
# TMPPID=$!

# sleep 1

# kill $!
# spec "Should kill logs"

$pm2 web
spec "Should start web interface"

sleep 0.3

JSON_FILE='/tmp/web-json'

$http_get -q http://localhost:9615/ -O $JSON_FILE
cat $JSON_FILE | grep "HttpInterface.js" > /dev/null
spec "Should get the right JSON with HttpInterface file launched"

$pm2 flush
spec "Should clean logs"

# cat ~/.pm2/logs/echo-out.log | wc -l
# spec "File Log should be cleaned"

sleep 0.3
$http_get -q http://localhost:9615/ -O $JSON_FILE
cat $JSON_FILE | grep "restart_time\":0" > /dev/null
spec "Should get the right JSON with HttpInterface file launched"

#
# Restart only one process
#
$pm2 restart 1
should 'should has restarted process' 'restart_time: 1' 1

#
# Restart all processes
#
$pm2 restart all
spec "Should restart all processes"

sleep 1
$http_get -q http://localhost:9615/ -O $JSON_FILE
OUT=`cat $JSON_FILE | grep -o "restart_time\":1" | wc -l`

[ $OUT -eq 7 ] || fail "Error while wgeting data via web interface"
success "Got data from interface"

$pm2 list

$pm2 dump
spec "Should dump current processes"

$pm2 save
spec "Should save (dump alias) current processes"

ls ~/.pm2/dump.pm2
spec "Dump file should be present"

$pm2 stop all
spec "Should stop all processes"

sleep 0.5
should 'should have stopped 8 apps' 'stopped' 8

$pm2 kill

#
# Issue #71
#

PROC_NAME='ECHONEST'
# Launch a script with name option
$pm2 start echo.js --name $PROC_NAME -f
should 'should have started app with name' 'ECHONEST' 7

# Restart a process by name
$pm2 restart $PROC_NAME
should 'should have restarted app by name' 'restart_time: 1' 1

$pm2 kill

$pm2 resurrect
spec "Should resurrect all apps"

sleep 0.5
should 'should have resurrected all processes' 'restart_time' 8

$pm2 delete all
spec "Should delete all processes"

sleep 0.5
should 'should have deleted process' 'restart_time' 0

#
# Cron
#
$pm2 start cron.js -c "* * * asdasd"
ispec "Cron should throw error when pattern invalid"

$pm2 start cron.js -c "* * * * * *"
spec "Should cron restart echo.js"

$pm2 kill test
ispec "Should not kill with extra args"

$pm2 kill
spec "Should kill daemon"


'use strict';

/**
 * @file Utilities for PM2
 * @author Alexandre Strzelewicz <as@unitech.io>
 * @project PM2
 */
var p             = require('path');
var Common        = require('../Common');
var treekill      = require('../TreeKill');
var cst           = require('../../constants.js');
var debug         = require('debug')('god:methods');
var os            = require('os');

/**
 * Kill process group in a cross platform way
 * @method crossPlatformGroupKill
 * @param int pid
 * @param string sig
 */
function crossPlatformGroupKill(pid, sig) {
    // This would have not been needed if node.js were able
    // to handle group process kill on Windows (pid < -1).
    if (os.platform() === 'win32') treekill(pid, sig);
    else process.kill(-pid, sig);
}

/**
 * Description
 * @method exports
 * @param {} God
 * @return
 */
module.exports = function(God) {

  /**
   * Description
   * @method logAndGenerateError
   * @param {} err
   * @return NewExpression
   */
  God.logAndGenerateError = function(err) {
    // Is an Error object
    if (err instanceof Error) {
      console.trace(err);
      return err;
    }
    // Is a JSON or simple string
    console.error(err);
    return new Error(err);
  };

  /**
   * Utility functions
   * @method getProcesses
   * @return MemberExpression
   */
  God.getProcesses = function() {
    return God.clusters_db;
  };

  /**
   * Description
   * @method getFormatedProcesses
   * @return arr
   */
  God.getFormatedProcesses = function getFormatedProcesses() {
    var db = Common.clone(God.clusters_db);
    var arr = [];

    for (var key in db) {
      if (db[key]) {
        arr.push({
          pid     : db[key].process.pid,
          name    : db[key].pm2_env.name,
          pm2_env : db[key].pm2_env,
          pm_id   : db[key].pm2_env.pm_id
        });
      }
    }
    db = null;
    return arr;
  };

  /**
   * Description
   * @method findProcessById
   * @param {} id
   * @return ConditionalExpression
   */
  God.findProcessById = function findProcessById(id) {
    return God.clusters_db[id] ? God.clusters_db[id] : null;
  };

  /**
   * Description
   * @method findByName
   * @param {} name
   * @return arr
   */
  God.findByName = function(name) {
    var db = God.clusters_db;
    var arr = [];

    if (name == 'all') {
      for (var key in db) {
        // Avoid _old_proc process style
        if (typeof(God.clusters_db[key].pm2_env.pm_id) === 'number')
          arr.push(db[key]);
      }
      return arr;
    }

    for (var key in db) {
      if (God.clusters_db[key].pm2_env.name == name ||
          God.clusters_db[key].pm2_env.pm_exec_path == p.resolve(name)) {
        arr.push(db[key]);
      }
    }
    return arr;
  };

  /**
   * Description
   * @method findByScript
   * @param {} script
   * @param {} cb
   * @return
   */
  God.findByScript = function(script, cb) {
    var db = Common.clone(God.clusters_db);
    var arr = [];

    for (var key in db) {
      if (p.basename(db[key].pm2_env.pm_exec_path) == script) {
        arr.push(db[key].pm2_env);
      }
    }
    cb(null, arr.length == 0 ? null : arr);
  };

  /**
   * Description
   * @method findByPort
   * @param {} port
   * @param {} cb
   * @return
   */
  God.findByPort = function(port, cb) {
    var db = God.clusters_db;
    var arr = [];

    for (var key in db) {
      if (db[key].pm2_env.port && db[key].pm2_env.port == port) {
        arr.push(db[key].pm2_env);
      }
    }
    cb(null, arr.length == 0 ? null : arr);
  };

  /**
   * Description
   * @method findByFullPath
   * @param {} path
   * @param {} cb
   * @return
   */
  God.findByFullPath = function(path, cb) {
    var db = God.clusters_db;
    var procs = [];

    for (var key in db) {
      if (db[key].pm2_env.pm_exec_path == path) {
        procs.push(db[key]);
      }
    }
    cb(null, procs.length == 0 ? null : procs);
  };

  /**
   * Check if a process is alive in system processes
   * Return TRUE if process online
   * @method checkProcess
   * @param {} pid
   * @return
   */
  God.checkProcess = function(pid) {
    if (!pid) return false;

    try {
      // Sending 0 signal do not kill the process
      process.kill(pid, 0);
      return true;
    }
    catch (err) {
      return false;
    }
  };

  /**
   * Description
   * @method processIsDead
   * @param {} pid
   * @param {} cb
   * @return Literal
   */
  God.processIsDead = function(pid, cb, sigkill) {
    if (!pid) return cb({type : 'param:missing', msg : 'no pid passed'});

    var timeout = null;

    var timer = setInterval(function() {
      if (God.checkProcess(pid) === false) {
        debug('Process with pid %d killed', pid);
        clearTimeout(timeout);
        clearInterval(timer);
        return cb(null, true);
      }
      console.log('Process with pid %d still not killed, retrying...', pid);
      return false;
    }, 100);

    timeout = setTimeout(function() {
      clearInterval(timer);
      if (sigkill) {
        console.log('Process with pid %d could not be killed', pid);
        return cb({type : 'timeout', msg : 'timeout'});
      }
      else {
        console.log('Process with pid %d still alive after %sms, sending it SIGKILL now...', pid, cst.KILL_TIMEOUT);
        try {
          crossPlatformGroupKill(parseInt(pid),'SIGKILL');
        } catch(e) { console.error('Process cannot be killed', e.message || e.stack || e); }
        return God.processIsDead(pid, cb, true);
      }
    }, cst.KILL_TIMEOUT);
    return false;
  };

  /**
   * Description
   * @method killProcess
   * @param int pid
   * @param Object pm2_env
   * @param function cb
   * @return CallExpression
   */
  God.killProcess = function(pid, pm2_env, cb) {
    if (!pid) return cb({msg : 'no pid passed or null'});

    var mode = pm2_env.exec_mode;

    try {
      if(pm2_env.treekill !== true) {
        process.kill(parseInt(pid), 'SIGINT');
      }
      else {
        if (mode.indexOf('cluster') === 0) {
          treekill(parseInt(pid), 'SIGINT');
        }
        else
          crossPlatformGroupKill(parseInt(pid), 'SIGINT');
      }
    } catch(e) {
      console.error('%s pid can not be killed', pid, e);
      return cb(null, 'Cannot be killed');
    }

    return God.processIsDead(pid, cb);
  };

  /**
   * Description
   * @method getNewId
   * @return UpdateExpression
   */
  God.getNewId = function() {
    return God.next_id++;
  };

  /**
   * When a process is restarted or reloaded reset fields
   * to monitor unstable starts
   * @method resetState
   * @param {} pm2_env
   * @return
   */
  God.resetState = function(pm2_env) {
    pm2_env.created_at = Date.now();
    pm2_env.unstable_restarts = 0;
  };

  /**
   * Description
   * @method deepReset
   * @param {} pm2_env
   * @return
   */
  God.deepReset = function(pm2_env) {
    pm2_env.created_at = Date.now();
    pm2_env.unstable_restarts = 0;
  };

  /**
   * Description
   * @method forcegc
   * @return
   */
  God.forceGc = function(opts, cb) {
    if (global.gc) {
      global.gc();
      debug('Garbage collection triggered successfully');
      if (cb) cb(null, {success: true});
    }
    else {
      debug('Garbage collection failed');
      if (cb) cb(null, {success: false});
    }
  };

};


process.argv.forEach(function (val, index, array) {
  console.log(index + ': ' + val);
});

console.log('Argv2 = ', process.argv[2]);

setInterval(function() {
  console.log('HERE ARE MY ARGS !!! = ', process.argv);
}, 800);


var debug = require('debug')('axm:notify');
var util  = require('util');
var Common    = require('./common.js');

var Options = require('./pm2_module.js');

var Transport = require('./utils/transport.js');

var Notify = {};

var jsonize = function(err, filter, space) {
  if (typeof(err) != 'object')
    return err;

  var plainObject = {};

  Object.getOwnPropertyNames(err).forEach(function(key) {
    plainObject[key] = err[key];
  });
  return plainObject;
};

Notify.catchAll = function(opts) {

  if (opts === undefined)
    opts = { errors : true };

  Options.configureModule({
    error : opts.errors
  });

  if (process.env.exec_mode == 'cluster_mode')
    return false;

  var catchException = function(err) {
    //debug(err.stack || err);
    Transport.send({
      type : 'process:exception',
      data : jsonize(err)
    }, true);
    console.error(err.stack || err);
    process.exit(255);
  };

  if (opts.errors === true
     && util.inspect(process.listeners('uncaughtException')).length === 2) {
    process.once('uncaughtException', catchException);
  }
  else if (opts.errors === false
           && util.inspect(process.listeners('uncaughtException')).length !== 2) {
    process.removeAllListeners('uncaughtException');
  }
};

Notify._interpretError = function(err) {
  var s_err = {};

  if (typeof(err) === 'string') {
    // Simple string processing
    s_err = new Error(err);
  }
  else if (!(err instanceof Error) && typeof(err) === 'object') {
    // JSON processing
    s_err = new Error(JSON.stringify(err));
    s_err.data = err;
  }
  else if (err instanceof Error) {
    // Error object type processing
    s_err = err;
  }

  return jsonize(s_err);
};

Notify.notify = function(err) {
  var ret_err = this._interpretError(err);

  // full_err
  //debug(ret_err);

  Transport.send({
    type : 'process:exception',
    data : ret_err
  }, true);

  return ret_err;
};

Notify.expressErrorHandler = function() {
  var self = this;

  Options.configureModule({
    error : true
  });

  return function errorHandler(err, req, res, next) {
    if (res.statusCode < 400) res.statusCode = 500;

    //debug(err.stack || err);

    err.url = req.url;
    err.component = req.url;
    err.action = req.method;
    err.params = req.body;
    err.session = req.session;

    Transport.send({
      type  : 'process:exception',
      data  : jsonize(err)
    }, true);
    return next(err);
  };
};

module.exports = Notify;


var timer = setInterval(function(){
  console.log('tick', Date.now());
}, 100);

setTimeout(function(){
  clearInterval(timer);
  throw new Error('error has been caught')
}, 350);


console.error('%s pid can not be killed', pid, e);

keep keep keep keep keep replace keep keep keep keep keep

return God.processIsDead(pid, pm2_env, cb);


//
// PM2 Monit and Server web interface
// Disserve JSON in light way
// by Strzelewicz Alexandre
//

var http  = require('http');
var os    = require('os');
var Satan = require('./Satan');
var urlT  = require('url');
var cst   = require('../constants.js');

// Start daemon
//
// Usually it would be is started in the parent process already,
// but if I run "node HttpInterface" directly, I would probably
// like it to be not daemonized
Satan.start(true);

http.createServer(function (req, res) {
  // Add CORS headers to allow browsers to fetch data directly
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Headers', 'Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With');
  res.setHeader('Access-Control-Allow-Methods', 'GET');

  // We always send json
  res.setHeader('Content-Type','application/json');

  var path = urlT.parse(req.url).pathname;

//  console.log('Access on PM2 monit point %s', path);

  if (path == '/') {
    // Main monit route
    Satan.executeRemote('getMonitorData', {}, function(err, data_proc) {
      var data = {
        system_info: { hostname: os.hostname(),
                       uptime: os.uptime()
                     },
        monit: { loadavg: os.loadavg(),
                 total_mem: os.totalmem(),
                 free_mem: os.freemem(),
                 cpu: os.cpus(),
                 interfaces: os.networkInterfaces()
               },
        processes: data_proc
      };

      res.statusCode = 200;
      res.write(JSON.stringify(data));
      return res.end();
    });
  }
  else {
    // 404
    res.statusCode = 404;
    res.write(JSON.stringify({err : '404'}));
    return res.end();
  }
}).listen(cst.WEB_INTERFACE);


// ps -p 5828 -o pid,vsz=MEMORY -o user,group=GROUP -o comm,args=ARGS


node_modules
test
History.md
Makefile
component.json


},
"instances": {
"type": "number"
},
"port": {
"type": "number"
},
"log_file": {

it should be a NUMBER - byte, "[NUMBER]G"(Gigabyte), "[NUMBER]M"(Megabyte) or "[NUMBER]K"(Kilobyte)

it should be a NUMBER - milliseconds, "[NUMBER]h"(hours), "[NUMBER]m"(minutes) or "[NUMBER]s"(seconds)

it should be "cluster"("cluster_mode") or "fork"("fork_mode") only


var stop = false;

/**
 * Description
 * @method add
 * @param {} a
 * @param {} b
 * @return sum
 */
function add(a, b) {
  while (a.length < b.length) a.unshift(0);
  while (a.length > b.length) b.unshift(0);
  var carry = 0, sum = []
  for (var i = a.length - 1; i >= 0; i--) {
    var s = a[i] + b[i] + carry;
    if (s >= 10) {
      s = s - 10;
      carry = 1;
    } else {
      carry = 0;
    }
    sum.unshift(s);
  }
  if (carry)
    sum.unshift(carry);
  return sum;
}

/**
 * Description
 * @method fib
 * @param {} n
 * @return CallExpression
 */
function fib(n) {
  var f1 = [0];
  var f2 = [1];

  while (n--) {
    var f3 = add(f1, f2)
    if (stop) return false;
    f1 = f2;
    f2 = f3;
  }
  return f1.join("");
}

var axm = require('pmx');

axm.action('load:start', function(reply) {
  fib(50000);
  reply({success : true});
});

axm.action('load:stop', function(reply) {
  stop = true;
  reply({success : true});
});


var Config = require('../../lib/tools/Config');

describe('JSON validation tests', function() {
  it('should fail when passing wrong json', function() {
    var ret = Config.verifyJSON({
      "exec_interpreter"   : "node",
      "exec_mode"          : "clusasdter_mode",
      "instances"          : "max",
      "log_date_format"    : "YYYY-MM-DD HH:mm Z",
      "max_memory_restart" : "160",
      "merge_logs"         : true,
      "name"               : "hapi_playground",
      "script"             : "chidld.js",
      "cwd"                : "examadsples",
      "node_args"          : "--harmoasdny",
      "ignore_watch"        : ["[\\/\\\\]\\./", "log"],
      "watch"              : "true"
    });

    /**
     * Error about instances to not be an integer
     * Error about watch to not be a boolean
     */
    ret.errors.length.should.eql(2);
  });

  it('should succeed while passing right json', function() {
    var ret = Config.verifyJSON({
      "exec_interpreter"   : "node",
      "exec_mode"          : "cluster_mode",
      "instances"          : 0,
      "log_date_format"    : "YYYY-MM-DD HH:mm Z",
      "max_memory_restart" : "160",
      "merge_logs"         : true,
      "error_file"         : "err.file",
      "out_file"           : "out.file",
      "pid_file"           : "pid.file",
      "log_file"           : "my-merged-log-file.log",
      "name"               : "hapi_playground",
      "script"             : "child.js",
      "cwd"                : "examples",
      "node_args"          : "--harmony",
      "max_memory_restart" : "10M",
      "ignore_watch"        : ["[\\/\\\\]\\./", "log"],
      "watch"              : true,
      "node_args"          : ["hey","hay"],
      "env"                : {}
    });

    ret.errors.length.should.eql(0);
  });

});


// Inject custom modules
if (process.env.pmx !== "false") {
  require('pmx').init({
    http: false
  });
}

// Cron restart feature
if (process.env.cron_restart) {
  var cron_pattern = process.env.cron_restart;
  var cronJob = require('cron').CronJob;
  var job = new cronJob({
    cronTime: cron_pattern,
    onTick: function() {
      process.exit(0);
    },
    start: false
  });
  job.start();
}

// Rename the process
process.title = 'node ' + process.env.pm_exec_path;

// Require the real application
if (process.env.pm_exec_path)
  require('module')._load(process.env.pm_exec_path, null, true);
else
  throw new Error('Could not _load() the script');

// Change some values to make node think that the user's application
// was started directly such as `node app.js`
process.mainModule.loaded = false;
require.main = process.mainModule;


} else if (command.catalogRefresh.refreshFailed) {

Console.info("(But the update of the local catalog failed, so it might exist)");

var formatAsList = function (list, options) {

Console.info("(But the update of the package catalog failed, so it might exist)");

var showRequireProfile = ('METEOR_PROFILE_REQUIRE' in process.env);
if (showRequireProfile)
  require('./profile-require.js').start();

var _ = require('underscore');
var Fiber = require('fibers');
var Console = require('./console.js').Console;
var files = require('./files.js');
var path = require('path');
var warehouse = require('./warehouse.js');
var tropohouse = require('./tropohouse.js');
var release = require('./release.js');
var project = require('./project.js');
var fs = require('fs');
var catalog = require('./catalog.js');
var buildmessage = require('./buildmessage.js');
var main = exports;

// node (v8) defaults to only recording 10 lines of stack trace. This
// in especially insufficient when using fibers, because you get
// proper call stacks instead of only seeing the stack up to the most
// recent callback invocation. Increase the limit (for the `meteor` tool
// itself, not for apps).
//
// http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
Error.stackTraceLimit = Infinity;

STRICT_MODE = !!process.env.METEOR_MDG_STRICT_MODE;

///////////////////////////////////////////////////////////////////////////////
// Command registration
///////////////////////////////////////////////////////////////////////////////

var Command = function (options) {
  options = _.extend({
    minArgs: 0,
    options: {},
    requiresApp: false,
    requiresRelease: true,
    hidden: false,
    pretty: false
  }, options);

  if (! _.has(options, 'maxArgs'))
    options.maxArgs = options.minArgs;

  _.each(["name", "func"], function (key) {
    if (! _.has(options, key))
      throw new Error("command missing '" + key + "'?");
  });

  _.extend(this, options);

  _.each(this.options, function (value, key) {
    if (key === "args" || key === "appDir")
      throw new Error(options.name + ": bad option name " + key);
    if (! _.has(value, 'type'))
      value.type = String;
    if (_.has(value, 'default') && _.has(value, 'required'))
      throw new Error(options.name + ": " + key + " can't be both optional " +
                      "and required");
    if (_.has(value, 'short') && value.short.length !== 1)
      throw new Error(options.name + ": " + key + " has a bad short option");
  });
};

// map from command name to a Command, or to a subcommand map (a map
// of subcommand names to either Commands or further submaps).
//
// Options that function as commands (eg, "meteor --arch") are treated
// as subcommands of "--".
var commands = {};

// map from full command name ('deploy' or 'admin grant') to
// - description: one-line help message, for use in command list
// - usage: full usage help. ends with a newline but no blank lines
var messages = {};

// Exception to throw from a command to bail out and show command
// usage information.
main.ShowUsage = function () {};

// Exception to throw from a helper function inside a command which is identical
// to returning the given exit code from the command.  ONLY USE THIS IN HELPERS
// THAT ARE ONLY CALLED DIRECTLY FROM COMMANDS! DON'T BE LAZY AND PUT THROW OF
// THIS IN RANDOM LIBRARY CODE!
main.ExitWithCode = function (code) {
  this.code = code;
};

_.extend(main.ExitWithCode.prototype, {
  toString: function () {
    var self = this;
    return "ExitWithCode:" + self.code;
  }
});

// Exception to throw to skip the process.exit call.
main.WaitForExit = function () {};

// Exception to throw from a command to exit, restart, and reinvoke
// the command with the latest available (downloaded) Meteor release.
// If track is specified, it uses the latest available in the given
// track instead of the default track.
main.SpringboardToLatestRelease = function (track) {
  this.track = track;
};

// Exception to throw from a command to exit, restart, and reinvoke
// the command with the given Meteor release.
main.SpringboardToSpecificRelease = function (releaseRecord, msg) {
  this.releaseRecord = releaseRecord;
  this.msg = msg;
};

// Register a command-line command.
//
// options:
// - name
//   - can be a basic command, like "deploy"
//   - can be a subcommand, like "admin grant"
//     (distinguished by presence of ' ')
//   - can be an option that functions as a command, like "--arch"
//     (distinguished by starting with '--')
// - minArgs: minimum non-option arguments that can be present (default 0)
// - maxArgs: maximum non-option arguments that can be present (defaults to
//   whatever value you passed for minArgs; use Infinity for unlimited)
// - catalogRefresh: strategy object specifying when to refresh the catalog.
// - options: map from long option name to:
//   - type: String, Number, or Boolean. default is String. a future
//     version could support [String] and [Number] to allow the option to
//     be passed more than once, but we don't do that yet.
//   - short: single character short alias (eg, 'p' for 'port', to do -p 3000)
//   - default: value to use if none supplied
//   - required: true if required (incompatible with 'default')
// - requiresApp: does this command work with an app? possible values
//   (defaults to false):
//   - true if an app is required, and command must be run inside an
//     app. The command will be run using the app's Meteor release
//     (unless overridden by --release or a checkout). An 'appDir'
//     option will be passed with the absolute path to the app's
//     top-level directory, and an error will be printed if the
//     command isn't run from inside an app.
//   - false if an app is not required. But if the command does happen
//     to have been run from an app, 'appDir' will be
//     provided. Moreover, in that case, we will still use the version
//     of this program that goes with the Meteor release of the
//     app. This is not ideal but is necessary for 'meteor help' to
//     behave in a sane way in our current system. (XXX In the future
//     we should separate the build system out into a package that is
//     versioned with the release, and then take the CLI tool out of
//     the release and always use the latest available version.)
//   - function: some apps determine whether they use an app based on
//     their arguments (eg, 'deploy' versus 'deploy --delete'). for
//     these, set requiresApp to a function that takes 'options' (same as
//     would be received by the actual command function) and returns
//     true or false.
// - requiresRelease: defaults to true. Set to false if this command
//   doesn't need a functioning Meteor release to be available (that
//   is, if the command does not need the ability to resolve
//   packages). There is only one case where this comes up: if you
//   create an app with a checkout (so that it has no release), and
//   then run that app with released Meteor. Normally this just prints
//   an error saying that you have to pick a release, but you can
//   disable that by setting this flag to false. Even if you set this
//   flag, we will still *attempt* to run the correct Meteor release
//   just like we always do; it's just that in that one case, instead
//   of bailing out with an error we will run your command with
//   release.current === null.
// - hidden: do not show in command list in help
//
// An error will be printed if an unrecognized option is passed on the
// command line (eg, '--foo' when you don't have a 'foo' key in
// options.options), or a required option is missing, or the number of
// other arguments isn't as required by minArgs / maxArgs.
//
// func: function to call when the command is chosen. receives one
// argument, an options dictionary that contains:
// - the values of any 'options' that were provided
// - args: an array of the other command-line arguments
// - appDir: if run from inside an app tree, the absolute path to the
//   app's top-level directory
//
// func should do one of the following:
// - On success, return undefined (or 0). This indicates successful
//   completion, and the program will exit with status 0.
// - On failure, return a positive number. The program will exit with that
//   status.
// - If the command-line arguments aren't valid, 'throw new
//   main.ShowUsage'. This will print usage info for the command and
//   exit with status 1.
// - If you have started (for example) a subprocess or worker fiber
//   and want to wait until it's finished to exit, 'throw new
//   main.WaitForExit'. This will skip the call to process.exit and the
//   program will keep running until node thinks that everything is
//   done.
// - To quit, restart, and rerun the command with a latest available
//   (downloaded) Meteor release, 'throw new main.SpringboardToLatestRelease'.
//
// Commands should never call process.exit()! They should instead
// return an appropriate value.

main.registerCommand = function (options, func) {
  options = _.clone(options);
  options.func = func;

  var nameParts = options.name.trim().split(/\s+/);
  options.name = nameParts.join(' ');

  if (nameParts[0].indexOf('--') === 0) {
    // "--foo" -> "--" "foo"
    nameParts[0] = nameParts[0].substr(2);
    nameParts.unshift('--');
  }

  var target = commands;
  while (nameParts.length > 1) {
    var part = nameParts.shift();
    if (! _.has(target, part))
      target[part] = {};
    target = target[part];
  }

  if (_.has(target, nameParts[0])) {
    throw Error("Duplicate command: " + options.name);
  }

  target[nameParts[0]] = new Command(options);
};

///////////////////////////////////////////////////////////////////////////////
// Load all the commands
///////////////////////////////////////////////////////////////////////////////

// NB: files required up to this point may not define commands

require('./commands.js');
require('./commands-packages.js');

///////////////////////////////////////////////////////////////////////////////
// Long-form help
///////////////////////////////////////////////////////////////////////////////

// Returns an array of entries with keys:
// - name (entry name, typically a command name)
// - body (contents of body, trimmed to end with a newline but no blank lines)
var loadHelp = function () {
  var ret = [];
  var raw = fs.readFileSync(path.join(__dirname, 'help.txt'), 'utf8');
  return _.map(raw.split(/^>>>/m).slice(1), function (r) {
    var lines = r.split('\n');
    var name = lines.shift().trim();
    return {
      name: name,
      body: lines.join('\n').replace(/\s*$/, '') + '\n'
    };
  });
};

var longHelp = exports.longHelp = function (commandName) {
  commandName = commandName.trim();
  var parts = commandName.length ? commandName.split(' ') : [];
  var node = commands;
  _.each(parts, function (part) {
    if (! _.has(node, part))
      throw new Error("walked off edge of command tree?");
    node = node[part];
  });

  var help = loadHelp();

  // can use to see if there is help text for a particular command
  var helpDict = {};
  _.each(help, function (helpEntry) {
    helpDict[helpEntry.name] = helpEntry;
  });

  var commandList = null;
  if (! (node instanceof Command)) {
    commandList = '';
    var items = [];
    var commandsWanted = {};

    _.each(node, function (n, shortName) {
      var fullName = commandName + (commandName.length > 0 ? " " : "") +
        shortName;
      // For now, we don't include commands with subcommands in the
      // list -- if you have a command 'admin grant' then 'admin' does
      // not appear in the top-level help. If we one day want to make
      // these kinds of commands visible to casual users, we'll need a
      // way to mark them as visible or hidden.

      // Also, use helpDict to only include commands that have help text,
      // otherwise there is nothing to display
      if (n instanceof Command && ! n.hidden && helpDict[fullName])
        commandsWanted[fullName] = { name: shortName };
    });

    var maxNameLength = _.max(_.map(commandsWanted, function (c) {
      return c.name.length;
    }));

    // Assemble help text for subcommands.. in the order they appear
    // in the help file
    _.each(help, function (helpEntry) {
      if (_.has(commandsWanted, helpEntry.name)) {
        var shortName = commandsWanted[helpEntry.name].name;
        commandList += "   " + shortName +
          new Array(maxNameLength + 1).join(' ').substr(shortName.length) +
          "   " + helpEntry.body.split('\n')[0] + "\n";
      }
    });

    // Remove trailing newline so that you can write "{{commands}}" on
    // a line by itself and it does what you think it would
    commandList = commandList.substr(0, commandList.length - 1);
  }

  var entry = _.find(help, function (c) {
    return c.name === commandName;
  });
  if (! entry)
    throw new Error("help missing for " + commandName + "?");
  var ret = entry.body.split('\n').slice(1).join('\n');
  if (commandList !== null)
    ret = ret.replace('{{commands}}', commandList);

  return ret;
};

///////////////////////////////////////////////////////////////////////////////
// Springboarding
///////////////////////////////////////////////////////////////////////////////

// Exit and restart the program, with the same arguments, but using a
// different version of the tool and/or forcing a particular release.
//
// - release: required. the version of the tool to run. must
//   already be downloaded.
// - releaseOverride: optional. if provided, a release name to force
//   us to use when restarting (this functions exactly like --release
//   and will cause release.forced to be true).
var springboard = function (rel, releaseOverride) {
  if (process.env.METEOR_DEBUG_SPRINGBOARD)
    console.log("WILL SPRINGBOARD TO", rel.getToolsPackageAtVersion());

  var archinfo = require('./archinfo.js');
  var isopack = require('./isopack.js');

  var toolsPkg = rel.getToolsPackage();
  var toolsVersion = rel.getToolsVersion();

  // XXX split better
  try {
    Console.setPretty(true);
    Console.enableProgressDisplay(true);

    var messages = buildmessage.capture({
      title: "Downloading tools package " + toolsPkg + "@" + toolsVersion
    }, function () {
      tropohouse.default.maybeDownloadPackageForArchitectures({
        packageName: toolsPkg,
        version: toolsVersion,
        architectures: [archinfo.host()],
        definitelyNotLocal: true
      });
    });

    Console.enableProgressDisplay(false);
    Console.setPretty(false);
  } catch (err) {
    // We have failed to download the tool that we are supposed to springboard
    // to! That's bad. Let's exit.
    process.stderr.write(
      "Could not springboard to release: " + rel.name +
      ": could not download tool in " +
      rel.getToolsPackageAtVersion() + "\n");
    process.exit(1);
  }
  if (messages.hasMessages()) {
    process.stderr.write(
      "Could not springboard to release: " + rel.name + ".\n" +
        messages.formatMessages());
    process.exit(1);
  }

  var packagePath = tropohouse.default.packagePath(toolsPkg, toolsVersion);
  var toolIsopack = new isopack.Isopack;
  toolIsopack.initFromPath(toolsPkg, packagePath);
  var toolRecord = _.findWhere(toolIsopack.toolsOnDisk,
                               {arch: archinfo.host()});
  if (!toolRecord)
    throw Error("missing tool for " + archinfo.host() + " in " +
                toolsPkg + "@" + toolsVersion);
  var executable = path.join(packagePath, toolRecord.path, 'meteor');

  // Strip off the "node" and "meteor.js" from argv and replace it with the
  // appropriate tools's meteor shell script.
  var newArgv = process.argv.slice(2);

  if (releaseOverride !== undefined) {
    // We used to just append --release=<releaseOverride> to the arguments, and
    // though that's probably safe in practice, it makes us worry about things
    // like other --release options.  So now we use an environment
    // variable. #SpringboardEnvironmentVar
    process.env['METEOR_SPRINGBOARD_RELEASE'] = releaseOverride;
  }

  // Now exec; we're not coming back.
  require('kexec')(executable, newArgv);
  throw Error('exec failed?');
};

// Springboard to a pre-0.9.0 release.
var oldSpringboard = function (toolsVersion) {
  // Strip off the "node" and "meteor.js" from argv and replace it with the
  // appropriate tools's meteor shell script.
  var newArgv = process.argv.slice(2);
  var cmd = path.join(warehouse.getToolsDir(toolsVersion), 'bin', 'meteor');

  // Now exec; we're not coming back.
  require('kexec')(cmd, newArgv);
  throw Error('exec failed?');
};

///////////////////////////////////////////////////////////////////////////////
// Main entry point
///////////////////////////////////////////////////////////////////////////////

// This is the main function that runs when you type 'meteor'.

// It's mostly concerned with validating command-line arguments,
// finding the requested command in the commands table, and making
// sure that you're using the version of the Meteor tools that match
// your project.

Fiber(function () {
  // If running inside the Emacs shell, set stdin to be blocking,
  // reversing node's normal setting of O_NONBLOCK on the evaluation
  // of process.stdin (because Node unblocks stdio when forking). This
  // fixes execution of Mongo from within Emacs shell.
  if (process.env.EMACS == "t") {
    process.stdin;
    var child_process = require('child_process');
    child_process.spawn('true', [], {stdio: 'inherit'});
  }

  // Check required Node version.
  // This code is duplicated in tools/server/boot.js.
  var MIN_NODE_VERSION = 'v0.10.29';
  if (require('semver').lt(process.version, MIN_NODE_VERSION)) {
    process.stderr.write(
      'Meteor requires Node ' + MIN_NODE_VERSION + ' or later.\n');
    process.exit(1);
  }

  // This is a bit of a hack, but: if we don't check this in the tool, then the
  // first time we do a isopack.load, it will fail due to the check in the
  // meteor package, and that'll look a lot uglier.
  if (process.env.ROOT_URL) {
    var parsedUrl = require('url').parse(process.env.ROOT_URL);
    if (!parsedUrl.host) {
      process.stderr.write('$ROOT_URL, if specified, must be an URL.\n');
      process.exit(1);
    }
  }

  // Parse the arguments.
  //
  // We must first identify which options are boolean and which take
  // arguments (which must be consistent across all defined
  // commands). This is necessary to resolve cases like 'meteor --flag
  // stuff thing'. Is the command 'stuff' with a boolean option
  // 'flag', or in the command 'thing' with an option 'flag' that is
  // set to 'stuff'? To resolve this we require that 'flag' be
  // consistently declared as a boolean (or not a boolean) across all
  // commands.
  //
  // XXX The problem with the above is that which commands are boolean
  // may change across releases, and when we springboard, we actually
  // have to parse the options with the *target* version's
  // semantics. All in all, I think we might be better served to
  // require options to come after the command, other than special
  // options (--release, --help, and options that act as
  // commands). Then we don't have to require consistency of boolean
  // status between commands; we instead have to require consistency
  // of boolean status of a particular option, for a command, across
  // releases. Since we always start out by running the latest version
  // of Meteor, which can have knowledge of all past versions
  // (including the boolean status of formerly present but removed
  // options, including options to removed commands), this should let
  // us be 100% correct. (Of course, we could still do this if we
  // required options to be consistent across commands as well, but I
  // think this is a better tradeoff.) In this model, we'd do option
  // parsing in two passes, where the first pass just pulls out the
  // command, and the second parses the arguments with knowledge of
  // the command. I would make this change right now but we're on a
  // tight timetable for 1.0 and there is no advantage to doing it now
  // rather than later. #ImprovingCrossVersionOptionParsing

  var isBoolean = { "--help": true };
  var walkCommands = function (node) {
    _.each(node, function (value, key) {
      if (value instanceof Command) {
        _.each(value.options || {}, function (optionInfo, optionName) {
          var names = ["--" + optionName];
          if (_.has(optionInfo, 'short'))
            names.push("-" + optionInfo.short);
          _.each(names, function (name) {
            var optionIsBoolean = (optionInfo.type === Boolean);
            if (_.has(isBoolean, name)) {
              if (isBoolean[name] !== optionIsBoolean)  {
                throw new Error("conflict: option '" + name + "' is used " +
                                "both as a boolean and as another type for " +
                                "command " + key);
              }
            } else {
              isBoolean[name] = optionIsBoolean;
            }
          });
        });
      } else {
        walkCommands(value);
      }
    });
  };
  walkCommands(commands);

  // This is for things like '--arch' and '--version' which look like
  // options, but actually function pretty much like commands. That's
  // a little weird but it feels good and it follows a grand Unix
  // tradition.
  _.each(commands['--'] || {}, function (value, key) {
    if (_.has(isBoolean, "--" + key))
      throw new Error("--" + key + " is both an option and a command?");
    isBoolean["--" + key] = true;
  });

  // Now parse!
  var argv = process.argv.slice(2);
  var rawOptions = {}; // map from '--foo' or '-f' to array of values
  var rawArgs = [];
  for (var i = 0; i < argv.length; i++) {
    var term = argv[i];

    // --: stop-parsing marker
    if (term === "--") {
      // Remainder is unparsed
      rawArgs = rawArgs.concat(argv.slice(i + 1));
      break;
    }

    // -: just an argument named '-'
    if (term === "-") {
      rawArgs.push(term);
      continue;
    }

    if (term.match(/^--?=/)) {
      process.stderr.write("Option names cannot begin with '='.\n");
      process.exit(1);
    }

    // A single option, like --foo or -f
    if (term.match(/^--/) || term.match(/^-.$/)) {
      var value = undefined;

      // Split the term (once only!) on an equal sign.
      var equals = term.indexOf('=');
      if (equals !== -1) {
        value = term.substr(equals + 1);
        term = term.substr(0, equals);
      }

      if (! _.has(rawOptions, term))
        rawOptions[term] = [];

      // Save off the value of the option. true for (known) booleans,
      // null if value is missing, else a string. Don't try to
      // validate or interpret it yet.
      if (isBoolean[term]) {
        // If we got an '=' for a boolean, this is an error, which will be
        // printed prettily later if we push false here.
        rawOptions[term].push(value === undefined);
      } else if (value !== undefined) {
        // Handle '--foo=bar' and '--foo=' (which means "set to empty string").
        rawOptions[term].push(value);
      } else if (i === argv.length - 1) {
        rawOptions[term].push(null);
      } else {
        rawOptions[term].push(argv[i + 1]);
        i ++;
      }
      continue;
    }

    // Compound short option ('-abc', '-p45', '-abcp45')? Rewrite it
    // in place into '-a -b -c', '-p 45', '-a -b -c -p 45'. Not that
    // anyone really talks this way anymore.
    if (term.match(/^-/)) {
      if (term.match(/^-[-=]?$/))
        throw Error("these cases should be handled above?");

      var replacements = [];
      for (var j = 1; j < term.length; j++) {
        var subterm = "-" + term.charAt(j);
        if (isBoolean[subterm] === false) {
          // If we recognize this short option, and we're sure that it
          // takes a value, and there are remaining characters in the
          // short option, then those remaining characters are the value.
          replacements.push(subterm);
          var remainder = term.substr(j + 1);
          if (remainder.length) {
            // If there's an '=' here, don't include it in the option value. A
            // trailing '=' *should* cause us to set the option value to ''.
            if (remainder.charAt(0) === '=')
              remainder = remainder.substr(1);
            replacements.push(remainder);
            break;
          }
        } else if (isBoolean[subterm] &&
                   j + 1 < term.length && term.charAt(j + 1) === '=') {
          // We know it's a boolean, but we've been given an '='. This will
          // cause a pretty error later.
          if (! _.has(rawOptions, subterm))
            rawOptions[subterm] = [];
          rawOptions[subterm].push(false);
          // Don't process the '=' on the next pass.
          j ++;
        } else {
          // It's a boolean without an '=', or it's something we've never heard
          // of.  (In the latter case, assume it's boolean for now, and we'll
          // print an error later.)
          replacements.push(subterm);
        }
      }

      _.partial(argv.splice, i, 1).apply(argv, replacements);
      i --;
      continue;
    }

    // It is a plain old argument!
    rawArgs.push(term);
  }

  // Figure out if we're running in a directory that is part of a Meteor
  // application or package. Determine any additional directories to
  // search for packages.

  var appDir = files.findAppDir();
  if (appDir) {
    appDir = path.resolve(appDir);
    // Set the project root directory. This doesn't do any dependency
    // calculation -- we can't do that until the release is initialized.
    project.project.setRootDir(appDir);
  }

  // XXX compare this to the previous block's usesWarehouse...
  if (files.inCheckout()) {
    // When running from a checkout, uniload does use local packages, but *ONLY
    // THOSE FROM THE CHECKOUT*: not app packages or $PACKAGE_DIRS packages.
    // One side effect of this: we really really expect them to all build, and
    // we're fine with dying if they don't (there's no worries about needing to
    // springboard).
    var messages = buildmessage.capture({ title: "Initializing local packages" }, function () {
      catalog.uniload.initialize({
        localPackageDirs: [path.join(files.getCurrentToolsDir(), 'packages')]
      });
    });
    if (messages.hasMessages()) {
      process.stderr.write("=> Errors while scanning core packages:\n\n");
      process.stderr.write(messages.formatMessages());
      process.exit(1);
    }
  } else {
    // This doesn't need to be in a buildmessage, because the
    // BuiltUniloadCatalog really shouldn't need to build anything: it's just a
    // bunch of precompiled isopacks!
    catalog.uniload.initialize({
      uniloadDir: files.getUniloadDir()
    });
  }

  // Initialize the server catalog. Among other things, this is where
  // we get release information (used by springboarding).  This doesn't
  // build anything (except maybe, if running from a checkout, packages
  // that we need to uniload, which really ought to build) so it's OK
  // to die on errors.
  var messages = buildmessage.capture({ title: "Initializing server catalog" }, function () {
    catalog.official.initialize({
      offline: !!process.env.METEOR_OFFLINE_CATALOG
    });
  });
  if (messages.hasMessages()) {
    process.stderr.write("=> Errors while initializing package catalog:\n\n");
    process.stderr.write(messages.formatMessages());
    process.exit(1);
  }

  // We do NOT initialize catalog.complete yet.  When we do that, we will build
  // all local packages, and for both performance and correctness reasons, we
  // will wait until after the springboard check to do so.

  // Now before we do anything else, figure out the release to use,
  // and if that release goes with a different version of the tools,
  // quit and run those tools instead.
  //
  // Note that doing this correctly requires knowledge of which
  // arguments are boolean (in 'meteor --option --release 1.0', is
  // '--release' a flag or the values of '--option')? We have to use
  // the flag definitions in the current (latest) version of meteor to
  // decide whether to exec the other version of meteor that would
  // interpret the flags. That's not ideal, but it should do fine in
  // practice, and it's better than assuming that all options are or
  // aren't boolean when interpreting --release. See
  // #ImprovingCrossVersionOptionParsing.

  var releaseOverride = null;
  var releaseForced = false;
  var releaseExplicit = false;
  if (_.has(rawOptions, '--release')) {
    if (rawOptions['--release'].length > 1) {
      process.stderr.write(
"--release should only be passed once.\n" +
"Try 'meteor help' for help.\n");
      process.exit(1);
    }
    releaseOverride = rawOptions['--release'][0];
    releaseForced = true;
    if (! releaseOverride) {
      process.stderr.write(
"The --release option needs a value.\n" +
"Try 'meteor help' for help.\n");
      process.exit(1);
    }
    delete rawOptions['--release'];
  }

  // Let's keep track of whether this is an explicit release, due to different
  // update behavior.
  if (releaseOverride) {
   releaseExplicit = true;
  }

  if (_.has(process.env, 'METEOR_SPRINGBOARD_RELEASE')) {
    // See #SpringboardEnvironmentVar
    // Note that this does *NOT* cause release.forced to be true.
    // release.forced should only be set when the user actually
    // ran with --release, not just because (eg) they ran
    // 'meteor update' and we springboarded to the latest release.
    // (It's important that 'meteor update' be able to tell these
    // conditions apart even after the springboard!)
    releaseOverride = process.env['METEOR_SPRINGBOARD_RELEASE'];
  }

  var releaseName, appRelease;
  if (appDir) {
    // appRelease will be null if a super old project with no
    // .meteor/release or 'none' if created by a checkout
    appRelease = project.project.getMeteorReleaseVersion();
    // This is what happens if the file exists and is empty. This really
    // shouldn't happen unless the user did it manually.
    if (appRelease === '') {
      process.stderr.write(
"Problem! This project has a .meteor/release file which is empty.\n" +
"The file should either contain the release of Meteor that you want to use,\n" +
"or the word 'none' if you will only use the project with unreleased\n" +
"checkouts of Meteor. Please edit the .meteor/release file in the project\n" +
"and change it to a valid Meteor release or 'none'.\n");
      process.exit(1);
    } else if (appRelease === null) {
      process.stderr.write(
"Problem! This project does not have a .meteor/release file.\n" +
"The file should either contain the release of Meteor that you want to use,\n" +
"or the word 'none' if you will only use the project with unreleased\n" +
"checkouts of Meteor. Please edit the .meteor/release file in the project\n" +
"and change it to a valid Meteor release or 'none'.\n");
      process.exit(1);
    }
  }

  if (! files.usesWarehouse()) {
    // Running from a checkout
    if (releaseOverride) {
      process.stderr.write(
        "Can't specify a release when running Meteor from a checkout.\n");
      process.exit(1);
    }
    releaseName = null;
  } else {
    // Running from an install
    if (releaseOverride) {
      // Use the release explicitly specified on the command line.
      releaseName = releaseOverride;
    } else if (appDir) {
      // Running from an app directory. Use release specified by app.
      if (appRelease === 'none') {
        // Looks like we don't have a release. Leave release.current === null.
      } else {
        // Use the project's desired release
        releaseName = appRelease;
      }
    } else {
      // Run outside an app dir with no --release flag. Use the latest
      // release we know about (in the default track).
      var messages = buildmessage.capture(function () {
        releaseName = release.latestDownloaded();
      });
      if (messages.hasMessages()) {
        process.stderr.write("=> Errors while determining latest release:\n" +
                             messages.formatMessages());
        process.exit(1);
      }
    }
  }

  if (releaseName !== undefined) {
    // First, if we know just by looking at our disk that this is a legacy
    // pre-0.9.0 release, springboard to it immediately, before calling
    // release.load (which will refresh the catalog because the release
    // presumably doesn't exist on the new server, and this is a slow
    // operation).
    if (releaseName !== null &&
        warehouse.realReleaseExistsInWarehouse(releaseName)) {
      var manifest = warehouse.ensureReleaseExistsAndReturnManifest(
        releaseName);
      oldSpringboard(manifest.tools);  // doesn't return
    }

    try {
      var rel = release.load(releaseName);
    } catch (e) {
      if (e instanceof release.NoSuchReleaseError) {
        // OK, this release doesn't exist... unless it's an old pre-0.9.0
        // release. Let's try using the legacy "warehouse" module to load it.
        try {
          var manifest = warehouse.ensureReleaseExistsAndReturnManifest(
            releaseName);
        } catch (e) {
          // XXX handle OfflineError too?
          if (e instanceof warehouse.NoSuchReleaseError) {
            if (releaseOverride) {
              process.stderr.write(releaseName + ": unknown release.\n");
            } else if (appDir) {
              process.stderr.write(
"Problem! This project says that it uses version " + releaseName + " of Meteor,\n" +
"but you don't have that version of Meteor installed and the Meteor update\n" +
"servers don't have it either. Please edit the .meteor/release file in the\n" +
"project and change it to a valid Meteor release.\n");
            } else {
              throw new Error("can't load latest release?");
            }
            process.exit(1);
          }
          throw e;
        }
        // OK, it was an old release. We should old-springboard to it.
        oldSpringboard(manifest.tools);
      }

      throw e;
    }

    // Let's keep track of whether this is an explicit release, due to different
    // update behavior.
    if (releaseOverride) {
      releaseForced = true;
    }

    release.setCurrent(rel, releaseForced, releaseExplicit);
  }

  // If we're not running the correct version of the tools for this
  // release, fetch it and re-run.
  //
  // This will never happen when we're springboarding as part of an
  // update, because the correct tools version will have been chosen
  // the first time around. It will also never happen if the current
  // release is a checkout, because that doesn't make any sense.
  if (release.current && release.current.isProperRelease() &&
      release.current.getToolsPackageAtVersion() !== files.getToolsVersion()) {
    springboard(release.current); // does not return!
  }

  // OK, now it's finally time to set up the complete catalog. Only after this
  // can we use the build system (other than via uniload).

  // Figure out the directories that we should search for local
  // packages (in addition to packages downloaded from the package
  // server)
  var localPackageDirs = [];
  if (appDir)
    localPackageDirs.push(path.join(appDir, 'packages'));

  if (process.env.PACKAGE_DIRS) {
    // User can provide additional package directories to search in
    // PACKAGE_DIRS (colon-separated).
    localPackageDirs = localPackageDirs.concat(
      _.map(process.env.PACKAGE_DIRS.split(':'), function (p) {
        return path.resolve(p);
      }));
  }

  if (!files.usesWarehouse()) {
    // Running from a checkout, so use the Meteor core packages from
    // the checkout.
    localPackageDirs.push(path.join(
      files.getCurrentToolsDir(), 'packages'));
  }

  var messages = buildmessage.capture({ title: "Initializing catalog" }, function () {
    catalog.complete.initialize({
      localPackageDirs: localPackageDirs
    });
  });
  if (messages.hasMessages()) {
    process.stderr.write("=> Errors while scanning packages:\n\n");
    process.stderr.write(messages.formatMessages());
    process.exit(1);
  }

  // Check for the '--help' option.
  var showHelp = false;
  if (_.has(rawOptions, '--help')) {
    showHelp = true;
    delete rawOptions['--help'];
  }

  var commandName = '';
  var command = null;

  // Check for a command like '--arch' or '--version'. Make sure
  // it stands alone. (And this is ignored if you've passed --help.)
  if (! showHelp) {
    _.each(commands['--'] || {}, function (value, key) {
      var fullName = "--" + key;

      if (rawOptions[fullName]) {
        if (rawOptions[fullName].length > 1) {
          process.stderr.write("It doesn't make sense to pass " +
                               fullName + " more than once.\n");
          process.exit(1);
        }
        if (_.size(rawOptions) > 1 || rawArgs.length !== 0 || command) {
          process.stderr.write("Can't pass anything else along with " +
                               value.name + ".\n");
          process.exit(1);
        }
        command = value;
        commandName = command.name;
        delete rawOptions['--' + key];
      }
    });
  }

  // OK, if not one of those, the first (non-'--') argument(s) should
  // name the command.
  if (! command) {
    if (rawArgs.length === 0) {
      // No arguments means 'run'. Unless it's 'meteor --help'.
      if (! showHelp) {
        command = commands.run
        commandName = "run";
        if (! command)
          throw new Error("no 'run' command?");
      }
    } else {
      // Find the command they specified.
      var walk = commands;
      for (var i = 0; i < rawArgs.length; i++) {
        var word = rawArgs[i];

        // Support "meteor help", "meteor help deploy", "meteor help admin",
        // "meteor admin help", "meteor admin help grant", etc.  (But not
        // "meteor deploy help" or "meteor admin grant help": once we find an
        // actual command, we assume "help" is an argument, eg a site called
        // 'help'!)
        if (word === "help") {
          showHelp = true;
          continue;
        }

        commandName += (commandName.length > 0 ? " " : "") + word;

        if (! _.has(walk, word)) {
          process.stderr.write(
"'" + commandName + "' is not a Meteor command. See 'meteor --help'.\n");
          process.exit(1);
        }

        if (walk[word] instanceof Command) {
          command = walk[word];
          rawArgs = rawArgs.slice(i + 1); // consume arguments used
          break;
        }

        walk = walk[word];
      }
    }
  }

  if (! command && ! showHelp) {
    // They typed something like 'meteor admin' (when they were
    // supposed to type 'meteor admin grant' or something).
    process.stderr.write(
"Try 'meteor " + commandName + " help' for available commands.\n");
    process.exit(1);
  }

  // At this point we have a command[*]. Did they ask for help, or do
  // they actually want to run the command? If the former, print the
  // help and don't criticize anything else they may have given us.
  //
  // [*] the one exception being 'meteor --help' or 'meteor help', in
  // which case showHelp will be true and command will be null

  if (showHelp) {
    process.stdout.write(longHelp(commandName) + "\n");
    process.exit(0);
  }

  // They want to run the command. Interpret the options and make sure
  // that they're valid.

  var options = { args: rawArgs };

  _.each(command.options, function (optionInfo, optionName) {
    var presentLong = _.has(rawOptions, "--" + optionName);
    var presentShort = _.has(optionInfo, 'short') &&
      _.has(rawOptions, "-" + optionInfo.short);

    if (presentShort && presentLong) {
      // this would get caught below, but give a clearer error message
      process.stderr.write(
commandName + ": can't pass both -" + optionInfo.short + " and --" +
            optionName + ".\n" +
"Try 'meteor help " + commandName + "' for help.\n");
      process.exit(1);
    }
    var helpfulOptionName = "--" + optionName +
      (presentShort ? " (-" + optionInfo.short + ")" : "");

    // Collect all values we've received for this option, across the
    // long and short versions, and across possibly multiple
    // occurrences of the option on the command line
    var values = [];
    if (presentLong)
      values = values.concat(rawOptions["--" + optionName]);
    if (presentShort)
      values = values.concat(rawOptions["-" + optionInfo.short]);

    if (values.length > 1) {
      // in the future, we could support multiple values, but we don't
      // for now since no command needs it
      process.stderr.write(
commandName + ": can only take one " + helpfulOptionName + " option.\n" +
"Try 'meteor help " + commandName + "' for help.\n");
      process.exit(1);
    } else if (values.length === 1) {
      // OK, they provided exactly one value. Check its type and add
      // to the output.
      var value = values[0];
      if (value === null) {
        // This option requires a value and they didn't give it one
        // (it was the last word on the command line).
        process.stderr.write(
commandName + ": the " + helpfulOptionName + " option needs a value.\n" +
"Try 'meteor help " + commandName + "' for help.\n");
        process.exit(1);
      } else if (optionInfo.type === Number) {
        if (! value.match(/^[0-9]+$/)) {
          process.stderr.write(
commandName + ": " + helpfulOptionName + " must be a number.\n" +
"Try 'meteor help " + commandName + "' for help.\n");
          process.exit(1);
        }
        value = parseInt(value);
      } else if (optionInfo.type === Boolean) {
        if (!value) {
          process.stderr.write(
commandName + ": the " + helpfulOptionName + " option does not need a value.\n" +
"Try 'meteor help " + commandName + "' for help.\n");
          process.exit(1);
        }
        value = true;
      } else if (optionInfo.type === String) {
        // nothing to do, 'value' needs no parsing or validation
      } else {
        throw new Error("unknown option type?");
      }
      options[optionName] = value;

      // Remove from the list of input arguments so that later we can
      // detect unrecognized arguments.
      if (presentLong)
        delete rawOptions["--" + optionName];
      if (presentShort)
        delete rawOptions["-" + optionInfo.short];
    } else {
      // Option not supplied. Throw an error if it was required,
      // supply a default value if one is defined, or just leave it
      // out.
      if (_.has(optionInfo, 'default')) {
        options[optionName] = optionInfo.default;
      } else if (optionInfo.required) {
        process.stderr.write(
commandName + ": the --" + optionName + " option is required.\n" +
longHelp(commandName) + "\n");
        process.exit(1);
      }
    }
  });

  // Check for unrecognized options.
  if (_.keys(rawOptions).length > 0) {
    process.stderr.write(
_.keys(rawOptions)[0] + ": unknown option.\n" +
longHelp(commandName) + "\n");
    process.exit(1);
  }

  // Check argument count.
  if (options.args.length < command.minArgs) {
    process.stderr.write(
commandName + ": not enough arguments.\n" +
longHelp(commandName) + "\n");
    process.exit(1);
  }

  if (options.args.length > command.maxArgs) {
    process.stderr.write(
commandName + ": too many arguments.\n" +
longHelp(commandName) + "\n");
    process.exit(1);
  }

  // We know we have a valid command and options. Now check to see if
  // the command can only be run from an app dir, and add the appDir
  // option if running from an app.
  var requiresApp = command.requiresApp;
  if (typeof requiresApp === "function")
    requiresApp = requiresApp(options);

  if (appDir)
    options.appDir = appDir;

  if (requiresApp && ! options.appDir) {
    // This is where you end up if you type 'meteor' with no args,
    // since you'll default to the 'run' command which requires an
    // app. Be welcoming to our new developers!
    process.stderr.write(
commandName + ": You're not in a Meteor project directory.\n" +
"\n" +
"To create a new Meteor project:\n" +
"   meteor create <project name>\n" +
"For example:\n" +
"   meteor create myapp\n" +
"\n" +
"For more help, see 'meteor --help'.\n");
    process.exit(1);
  }

  // Same check for commands that can only be run from a package dir.
  var requiresPackage = command.requiresPackage;
  if (typeof requiresPackage === "function") {
    requiresPackage = requiresPackage(options);
  }

  var packageDir = files.findPackageDir();
  if (packageDir)
    packageDir = path.resolve(packageDir);

  if (packageDir) {
    options.packageDir = packageDir;
  }

  if (requiresPackage) {
    if (! options.packageDir) {
      process.stderr.write(
        commandName + ": You're not in a Meteor package directory.\n");
      process.exit(1);
    }
    // Commands that require you to be in a package directory add that package
    // as a local package to the catalog. Other random commands don't (but if we
    // see a reason for them to, we can change this rule).
    messages = buildmessage.capture(function () {
      catalog.complete.addLocalPackage(options.packageDir);
    });
    if (messages.hasMessages()) {
      process.stderr.write("=> Errors while scanning current package:\n\n");
      process.stderr.write(messages.formatMessages());
      process.exit(1);
    }
  }

  if (command.requiresRelease && ! release.current) {
    process.stderr.write(
"You must specify a Meteor version with --release when you work with this\n" +
"project. It was created from an unreleased Meteor checkout and doesn't\n" +
"have a version associated with it.\n" +
"\n" +
"You can permanently set a release for this project with 'meteor update'.\n");
    process.exit(1);
  }

  if (command.requiresApp && release.current.isCheckout() &&
      appRelease && appRelease !== "none") {
    // For commands that work with apps, if we have overridden the
    // app's usual release by using a checkout, print a reminder banner.
    process.stderr.write(
"=> Running Meteor from a checkout -- overrides project version (" +
        appRelease + ")\n");
  }

  // Now that we're ready to start executing the command, if we are in
  // startup time profiling mode, print the profile.
  if (showRequireProfile)
    require('./profile-require.js').printReport();

  Console.setPretty(command.pretty);

  Console.enableProgressDisplay(true);

  // Run the command!
  try {
    // Before run, do a package sync if one is configured
    var catalogRefreshStrategy = command.catalogRefresh;
    if (STRICT_MODE && catalogRefreshStrategy === undefined) {
      throw new Error("Command does not define catalogRefresh");
    }
    if (catalogRefreshStrategy && catalogRefreshStrategy.beforeCommand) {
      var messages = buildmessage.capture({title: 'Refreshing package catalog'}, function () {
        catalogRefreshStrategy.beforeCommand();
      });
      if (messages.hasMessages()) {
        Console.printMessages(messages);
        throw main.ExitWithCode(1);
      }
    }

    var ret = command.func(options);
  } catch (e) {
    Console.enableProgressDisplay(false);

    if (e === main.ShowUsage || e === main.WaitForExit ||
        e === main.SpringboardToLatestRelease ||
        e === main.SpringboardToSpecificReleaseg ||
        e === main.WaitForExit) {
      throw new Error(
        "you meant 'throw new main.Foo', not 'throw main.Foo'");
    } else if (e instanceof main.ShowUsage) {
      process.stderr.write(longHelp(commandName) + "\n");
      process.exit(1);
    } else if (e instanceof main.SpringboardToLatestRelease) {
      // Load the latest release's metadata so that we can figure out
      // the tools version that it uses. We should only do this if
      // we know there is some latest release on this track.
      var latestRelease = release.load(release.latestDownloaded(e.track));
      springboard(latestRelease, latestRelease.name);
      // (does not return)
    } else if (e instanceof main.SpringboardToSpecificRelease) {
      // Springboard to a specific release.
      var relName = e.releaseRecord.track + "@" + e.releaseRecord.version;
      var nextRelease = release.load(relName);
      springboard(nextRelease, relName);
      // (does not return)
    } else if (e instanceof main.WaitForExit) {
      return;
    } else if (e instanceof main.ExitWithCode) {
      process.exit(e.code);
    } else {
      throw e;
    }
  }

  Console.enableProgressDisplay(false);

  // Exit. (We will not get here if the command threw an exception
  // such as main.WaitForExit).
  if (ret === undefined)
    ret = 0;
  if (typeof ret !== "number")
    throw new Error("command returned non-number?");
  process.exit(ret);
}).run();


// for convenience
var loginButtonsSession = Accounts._loginButtonsSession;

var loginResultCallback = function (serviceName, err) {
  if (!err) {
    loginButtonsSession.closeDropdown();
  } else if (err instanceof Accounts.LoginCancelledError) {
    // do nothing
  } else if (err instanceof ServiceConfiguration.ConfigError) {
    loginButtonsSession.configureService(serviceName);
  } else {
    loginButtonsSession.errorMessage(err.reason || "Unknown error");
  }
};

// In the login redirect flow, we'll have the result of the login
// attempt at page load time when we're redirected back to the
// application.  Register a callback to update the UI (i.e. to close
// the dialog on a successful login or display the error on a failed
// login).
//
Accounts.onPageLoadLogin(function (attemptInfo) {
  // Ignore if we have a left over login attempt for a service that is no longer registered.
  if (_.contains(_.pluck(getLoginServices(), "name"), attemptInfo.type))
    loginResultCallback(attemptInfo.type, attemptInfo.error);
});

Template._loginButtonsLoggedOutSingleLoginButton.events({
  'click .login-button': function () {
    var serviceName = this.name;
    loginButtonsSession.resetMessages();

    // XXX Service providers should be able to specify their
    // `Meteor.loginWithX` method name.
    var loginWithService = Meteor["loginWith" +
                                  (serviceName === 'meteor-developer' ?
                                   'MeteorDeveloperAccount' :
                                   capitalize(serviceName))];

    var options = {}; // use default scope unless specified
    if (Accounts.ui._options.requestPermissions[serviceName])
      options.requestPermissions = Accounts.ui._options.requestPermissions[serviceName];
    if (Accounts.ui._options.requestOfflineToken[serviceName])
      options.requestOfflineToken = Accounts.ui._options.requestOfflineToken[serviceName];
    if (Accounts.ui._options.forceApprovalPrompt[serviceName])
      options.forceApprovalPrompt = Accounts.ui._options.forceApprovalPrompt[serviceName];

    loginWithService(options, function (err) {
      loginResultCallback(serviceName, err);
    });
  }
});

Template._loginButtonsLoggedOutSingleLoginButton.helpers({
  configured: function () {
    return !!ServiceConfiguration.configurations.findOne({service: this.name});
  },
  capitalizedName: function () {
    if (this.name === 'github')
      // XXX we should allow service packages to set their capitalized name
      return 'GitHub';
    else if (this.name === 'meteor-developer')
      return 'Meteor';
    else
      return capitalize(this.name);
  }
});

// XXX from http://epeli.github.com/underscore.string/lib/underscore.string.js
var capitalize = function(str){
  str = str == null ? '' : String(str);
  return str.charAt(0).toUpperCase() + str.slice(1);
};


appcache@1.0.2
application-configuration@1.0.3
autoupdate@1.1.2
base64@1.0.1
binary-heap@1.0.1
blaze-tools@1.0.1
blaze@2.0.2
boilerplate-generator@1.0.1
callback-hook@1.0.1
check@1.0.2
coffeescript@1.0.4
ctl-helper@1.0.4
ctl@1.0.2
ddp@1.0.10
deps@1.0.5
ejson@1.0.4
fastclick@1.0.1
follower-livedata@1.0.2
geojson-utils@1.0.1
html-tools@1.0.2
htmljs@1.0.2
http@1.0.7
id-map@1.0.1
jquery-waypoints@1.0.1
jquery@1.0.1
json@1.0.1
less@1.0.10
livedata@1.0.11
logging@1.0.4
markdown@1.0.2
meteor-platform@1.1.2
meteor@1.1.2
minifiers@1.1.1
minimongo@1.0.4
mobile-status-bar@1.0.1
mongo@1.0.7
observe-sequence@1.0.3
ordered-dict@1.0.1
random@1.0.1
reactive-dict@1.0.4
reactive-var@1.0.3
reload-safetybelt@1.0.1
reload@1.1.1
retry@1.0.1
routepolicy@1.0.2
session@1.0.3
showdown@1.0.2
simple:highlight.js@1.0.2
simple:markdown-templating@1.2.6
spacebars-compiler@1.0.3
spacebars@1.0.3
spiderable@1.0.4
standard-app-packages@1.0.3
templating@1.0.8
tracker@1.0.3
ui@1.0.4
underscore@1.0.1
url@1.0.1
webapp-hashing@1.0.1
webapp@1.1.3


throw new Error("Command does not define catalogRefresh");

if (catalogRefreshStrategy && catalogRefreshStrategy.beforeCommand) {

var messages = buildmessage.capture({title: 'Refreshing package catalog'}, function () {

var messages = buildmessage.capture({title: 'Updating package catalog'}, function () {


var Fiber = require('fibers');
var Future = require('fibers/future');
var _ = require('underscore');
var files = require('./files.js');
var parseStack = require('./parse-stack.js');
var fiberHelpers = require('./fiber-helpers.js');
var Progress = require('./progress.js').Progress;

var debugBuild = !!process.env.METEOR_DEBUG_BUILD;

// A job is something like "building package foo". It contains the set
// of messages generated by tha job. A given build run could contain
// several jobs. Each job has an (absolute) path associated with
// it. Filenames in messages within a job are to be interpreted
// relative to that path.
var Job = function (options) {
  var self = this;
  self.messages = [];

  // Should be something like "building package 'foo'"
  // Should look good in "While $title:\n[messages]"
  self.title = options.title;
  self.rootPath = options.rootPath;

  // Array of Job (jobs created inside this job)
  self.children = [];
};

_.extend(Job.prototype, {
  // options may include type ("error"), message, func, file, line,
  // column, stack (in the format returned by parseStack.parse())
  addMessage: function (options) {
    var self = this;
    self.messages.push(options);
  },

  hasMessages: function () {
    var self = this;
    return self.messages.length > 0;
  },

  // Returns a multi-line string suitable for displaying to the user
  formatMessages: function (indent) {
    var self = this;
    var out = "";
    var already = {};
    indent = new Array((indent || 0) + 1).join(' ');

    _.each(self.messages, function (message) {
      var stack = message.stack || [];

      var line = indent;
      if (message.file) {
        line+= message.file;
        if (message.line) {
          line += ":" + message.line;
          if (message.column) {
            // XXX maybe exclude unless specifically requested (eg,
            // for an automated tool that's parsing our output?)
            line += ":" + message.column;
          }
        }
        line += ": ";
      } else {
        // not sure how to display messages without a filenanme.. try this?
        line += "error: ";
      }
      // XXX line wrapping would be nice..
      line += message.message;
      if (message.func && stack.length <= 1) {
        line += " (at " + message.func + ")";
      }
      line += "\n";

      if (stack.length > 1) {
        _.each(stack, function (frame) {
          // If a nontrivial stack trace (more than just the file and line
          // we already complained about), print it.
          var where = "";
          if (frame.file) {
            where += frame.file;
            if (frame.line) {
              where += ":" + frame.line;
              if (frame.column) {
                where += ":" + frame.column;
              }
            }
          }

          if (! frame.func && ! where)
            return; // that's a pretty lame stack frame

          line += "  at ";
          if (frame.func)
            line += frame.func + " (" + where + ")\n";
          else
            line += where + "\n";
        });
        line += "\n";
      }

      // Deduplicate messages (only when exact duplicates, including stack)
      if (! (line in already)) {
        out += line;
        already[line] = true;
      }
    });

    return out;
  }

});

// A MessageSet contains a set of jobs, which in turn each contain a
// set of messages.
var MessageSet = function () {
  var self = this;
  self.jobs = [];
};

_.extend(MessageSet.prototype, {
  formatMessages: function () {
    var self = this;

    var jobsWithMessages = _.filter(self.jobs, function (job) {
      return job.hasMessages();
    });

    return _.map(jobsWithMessages, function (job) {
      var out = '';
      out += "While " + job.title + ":\n";
      out += job.formatMessages(0);
      return out;
    }).join('\n'); // blank line between jobs
  },

  hasMessages: function () {
    var self = this;
    return !! _.find(self.jobs, function (job) {
      return job.hasMessages();
    });
  },

  // Copy all of the messages in another MessageSet into this
  // MessageSet. If the other MessageSet is subsequently mutated,
  // results are undefined.
  //
  // XXX rather than this, the user should be able to create a
  // MessageSet and pass it into capture(), and functions such as
  // bundle() should take and mutate, rather than return, a
  // MessageSet.
  merge: function (messageSet) {
    var self = this;
    _.each(messageSet.jobs, function (j) {
      self.jobs.push(j);
    });
  }
});

var spaces = function (n) {
  return _.times(n, function() { return ' ' }).join('');
};

// XXX: This is now a little bit silly... ideas:
// Can we just have one hierarchical state?
// Can we combined job & messageSet
// Can we infer nesting level?
var currentMessageSet = new fiberHelpers.EnvironmentVariable;
var currentJob = new fiberHelpers.EnvironmentVariable;
var currentNestingLevel = new fiberHelpers.EnvironmentVariable(0);
var currentProgress = new fiberHelpers.EnvironmentVariable;

var rootProgress = new Progress();

var getRootProgress = function () {
  return rootProgress;
};

var reportProgress = function (state) {
  var progress = currentProgress.get();
  if (progress) {
    progress.reportProgress(state);
  }
};

var reportProgressDone = function () {
  var progress = currentProgress.get();
  if (progress) {
    progress.reportProgressDone();
  }
};

var getCurrentProgressTracker = function () {
  var progress = currentProgress.get();
  return progress ? progress : rootProgress;
};

var addChildTracker = function (title) {
  var options = {};
  options.title = title;
  return getCurrentProgressTracker().addChildTask(options);
};

// Create a new MessageSet, run `f` with that as the current
// MessageSet for the purpose of accumulating and recovering from
// errors (see error()), and then discard the return value of `f` and
// return the MessageSet.
//
// Note that you must also create a job (with enterJob) to actually
// begin capturing errors. Alternately you may pass `options`
// (otherwise optional) and a job will be created for you based on
// `options`.
var capture = function (options, f) {
  var messageSet = new MessageSet;
  var parentMessageSet = currentMessageSet.get();

  {
    var parentProgress = currentProgress.get();
    if (!parentProgress) {
      parentProgress = rootProgress;
    }
    var childOptions = {};
    if (typeof options === "object") {
      if (options.title) {
        childOptions.title = options.title;
      }
    }
    var progress = parentProgress.addChildTask(childOptions);
  }

  currentProgress.withValue(progress, function () {
    currentMessageSet.withValue(messageSet, function () {
      var job = null;
      if (typeof options === "object") {
        job = new Job(options);
        messageSet.jobs.push(job);
      } else {
        f = options; // options not actually provided
      }

      currentJob.withValue(job, function () {
        var nestingLevel = currentNestingLevel.get();
        currentNestingLevel.withValue(nestingLevel + 1, function () {
          var start;
          if (debugBuild) {
            start = Date.now();
            console.log(spaces(nestingLevel * 2), "START CAPTURE", nestingLevel, options.title, "took " + (end - start));
          }
          try {
            f();
          } finally {
            progress.reportProgressDone();

            if (debugBuild) {
              var end = Date.now();
              console.log(spaces(nestingLevel * 2), "END CAPTURE", nestingLevel, options.title, "took " + (end - start));
            }
          }
        });
      });
    });
  });
  return messageSet;
};

// Called from inside capture(), creates a new Job inside the current
// MessageSet and run `f` inside of it, so that any messages emitted
// by `f` are logged in the Job. Returns the return value of `f`. May
// be called recursively.
//
// Called not from inside capture(), does nothing (except call f).
//
// options:
// - title: a title for the job (required)
// - rootPath: the absolute path relative to which paths in messages
//   in this job should be interpreted (omit if there is no way to map
//   files that this job talks about back to files on disk)
var enterJob = function (options, f) {
  if (typeof options === "function") {
    f = options;
    options = {};
  }

  var progress;
  {
    var parentProgress = currentProgress.get();
    if (!parentProgress) {
      parentProgress = rootProgress;
    }
    var progressOptions = {};
    // XXX: Just pass all the options?
    if (typeof options === "object") {
      if (options.title) {
        progressOptions.title = options.title;
      }
      if (options.forkJoin) {
        progressOptions.forkJoin = options.forkJoin;
      }
    }
    progress = parentProgress.addChildTask(progressOptions);
  }

  return currentProgress.withValue(progress, function () {
    if (!currentMessageSet.get()) {
      var nestingLevel = currentNestingLevel.get();
      var start;
      if (debugBuild) {
        start = Date.now();
        console.log(spaces(nestingLevel * 2), "START", nestingLevel, options.title);
      }
      try {
        return currentNestingLevel.withValue(nestingLevel + 1, function () {
          return f();
        });
      } finally {
        progress.reportProgressDone();
        if (debugBuild) {
          var end = Date.now();
          console.log(spaces(nestingLevel * 2), "DONE", nestingLevel, options.title, "took " + (end - start));
        }
      }
    }

    var job = new Job(options);
    var originalJob = currentJob.get();
    originalJob && originalJob.children.push(job);
    currentMessageSet.get().jobs.push(job);

    return currentJob.withValue(job, function () {
      var nestingLevel = currentNestingLevel.get();
      return currentNestingLevel.withValue(nestingLevel + 1, function () {
        var start;
        if (debugBuild) {
          start = Date.now();
          console.log(spaces(nestingLevel * 2), "START", nestingLevel, options.title);
        }
        try {
          return f();
        } finally {
          progress.reportProgressDone();
          if (debugBuild) {
            var end = Date.now();
            console.log(spaces(nestingLevel * 2), "DONE", nestingLevel, options.title, "took " + (end - start));
          }
        }
      });
    });
  });
};

// If not inside a job, return false. Otherwise, return true if any
// messages (presumably errors) have been recorded for this job
// (including subjobs created inside this job), else false.
var jobHasMessages = function () {
  var search = function (job) {
    if (job.hasMessages())
      return true;
    return !! _.find(job.children, search);
  };

  return currentJob.get() ? search(currentJob.get()) : false;
};

// Given a function f, return a "marked" version of f. The mark
// indicates that stack traces should stop just above f. So if you
// mark a user-supplied callback function before calling it, you'll be
// able to show the user just the "user portion" of the stack trace
// (the part inside their own code, and not all of the innards of the
// code that called it).
var markBoundary = function (f) {
  return parseStack.markBottom(f);
};

// Record a build error. If inside a job, add the error to the current
// job and return (caller should do its best to recover and
// continue). Otherwise, throws an exception based on the error.
//
// options may include
// - file: the file containing the error, relative to the root of the build
//   (this must be agreed upon out of band)
// - line: the (1-indexed) line in the file that contains the error
// - column: the (1-indexed) column in that line where the error begins
// - func: the function containing the code that triggered the error
// - useMyCaller: true to capture information the caller (function
//   name, file, and line). It captures not the information of the
//   caller of error(), but that caller's caller. It saves them in
//   'file', 'line', and 'column' (overwriting any values passed in
//   for those). It also captures the user portion of the stack,
//   starting at and including the caller's caller.
//   If this is a number instead of 'true', skips that many stack frames.
// - downcase: if true, the first character of `message` will be
//   converted to lower case.
// - secondary: ignore this error if there are are already other
//   errors in this job (the implication is that it's probably
//   downstream of the other error, ie, a consequence of our attempt
//   to continue past other errors)
var error = function (message, options) {
  options = options || {};

  if (options.downcase)
    message = message.slice(0,1).toLowerCase() + message.slice(1);

  if (! currentJob.get())
    throw new Error("Error: " + message);

  if (options.secondary && jobHasMessages())
    return; // skip it

  var info = _.extend({
    message: message
  }, options);

  if ('useMyCaller' in info) {
    if (info.useMyCaller) {
      info.stack = parseStack.parse(new Error()).slice(2);
      var howManyToSkip = (
        typeof info.useMyCaller === "number" ? info.useMyCaller : 0);
      var caller = info.stack[howManyToSkip];
      info.func = caller.func;
      info.file = caller.file;
      info.line = caller.line;
      info.column = caller.column;
    }
    delete info.useMyCaller;
  }

  currentJob.get().addMessage(info);
};

// Record an exception. The message as well as any file and line
// information be read directly out of the exception. If not in a job,
// throws the exception instead. Also capture the user portion of the stack.
//
// There is special handling for files.FancySyntaxError exceptions. We
// will grab the file and location information where the syntax error
// actually occurred, rather than the place where the exception was
// thrown.
var exception = function (error) {
  if (! currentJob.get()) {
    // XXX this may be the wrong place to do this, but it makes syntax errors in
    // files loaded via isopack.load have context.
    if (error instanceof files.FancySyntaxError) {
      error = new Error("Syntax error: " + error.message + " at " +
        error.file + ":" + error.line + ":" + error.column);
    }
    throw error;
  }

  var message = error.message;

  if (error instanceof files.FancySyntaxError) {
    // No stack, because FancySyntaxError isn't a real Error and has no stack
    // property!
    currentJob.get().addMessage({
      message: message,
      file: error.file,
      line: error.line,
      column: error.column
    });
  } else {
    var stack = parseStack.parse(error);
    var locus = stack[0];
    currentJob.get().addMessage({
      message: message,
      stack: stack,
      func: locus.func,
      file: locus.file,
      line: locus.line,
      column: locus.column
    });
  }
};

var assertInJob = function () {
  if (! currentJob.get())
    throw new Error("Expected to be in a buildmessage job");
};

var assertInCapture = function () {
  if (! currentMessageSet.get())
    throw new Error("Expected to be in a buildmessage capture");
};

// Like _.each, but runs each operation in a separate job
var forkJoin = function (options, iterable, fn) {
  if (!_.isFunction(fn)) {
    fn = iterable;
    iterable = options;
    options = {};
  }

  var futures = [];
  var results = [];
  // XXX: We could check whether the sub-jobs set estimates, and if not
  // assume they each take the same amount of time and auto-report their completion
  var errors = [];
  var firstError = null;

  options.forkJoin = true;

  enterJob(options, function () {
    var job = currentJob.get();
    var messageSet = currentMessageSet.get();
    var progress = currentProgress.get();
    var nestingLevel = currentNestingLevel.get();

    var parallel = (options.parallel !== undefined) ? options.parallel : true;
    if (parallel) {
      _.each(iterable, function (/*arguments*/) {
        var fut = new Future();
        var fnArguments = arguments;
        Fiber(function () {
          currentNestingLevel.withValue(nestingLevel, function() {
            currentProgress.withValue(progress, function () {
              currentMessageSet.withValue(messageSet, function () {
                currentJob.withValue(job, function () {
                  try {
                    var result = enterJob({title: (options.title || '') + ' child'}, function () {
                      return fn.apply(null, fnArguments);
                    });
                    fut['return'](result);
                  } catch (e) {
                    fut['throw'](e);
                  }
                });
              });
            });
          });
        }).run();
        futures.push(fut);
      });

      _.each(futures, function (future) {
        try {
          var result = future.wait();
          results.push(result);
          errors.push(null);
        } catch (e) {
          results.push(null);
          errors.push(e);

          if (firstError === null) {
            firstError = e;
          }
        }
      });
    } else {
      // not parallel
      _.each(iterable, function (/*arguments*/) {
        var fnArguments = arguments;
        try {
          var result = fn.apply(null, fnArguments);
          results.push(result);
          errors.push(null);
        } catch (e) {
          results.push(null);
          errors.push(e);

          if (firstError === null) {
            firstError = e;
          }
        }
      });
    }
  });

  if (firstError) {
    throw firstError;
  }

  return results;
};

var buildmessage = exports;
_.extend(exports, {
  capture: capture,
  enterJob: enterJob,
  markBoundary: markBoundary,
  error: error,
  exception: exception,
  jobHasMessages: jobHasMessages,
  assertInJob: assertInJob,
  assertInCapture: assertInCapture,
  forkJoin: forkJoin,
  getRootProgress: getRootProgress,
  reportProgress: reportProgress,
  reportProgressDone: reportProgressDone,
  getCurrentProgressTracker: getCurrentProgressTracker,
  addChildTracker: addChildTracker
});


Package.describe({
  summary: "Observe changes to various sequence types such as arrays, cursors and objects",
  version: "1.0.3"
});

Package.on_use(function (api) {
  api.use('tracker');
  api.use('minimongo');  // for idStringify
  api.use('underscore');
  api.use('random');
  api.export('ObserveSequence');
  api.add_files(['observe_sequence.js']);
});

Package.on_test(function (api) {
  api.use('tinytest');
  api.use('observe-sequence');
  api.use('underscore');
  api.use('ejson');
  api.add_files(['observe_sequence_tests.js'], 'client');
});


Console.debug("Ignoring error refreshing package catalog", err);

keep keep keep replace keep keep keep keep

Console.debug("Ignoring error updating package catalog", err);


#!/bin/bash

ORIGDIR=$(pwd)
cd $(dirname $0)
cd ../../..
TOPDIR=$(pwd)

INFINITY=10000

cd $ORIGDIR

# Call git grep to find all js files with the appropriate comment tags,
# and only then pass it to JSDoc which will parse the JS files.
# This is a whole lot faster than calling JSDoc recursively.
git grep -al "@summary" | xargs -L ${INFINITY} -t \
    ${TOPDIR}/dev_bundle/bin/node \
    ${TOPDIR}/dev_bundle/lib/node_modules/.bin/jsdoc \
    -t "${TOPDIR}/scripts/admin/jsdoc/docdata-jsdoc-template" \
    -c "${TOPDIR}/scripts/admin/jsdoc/jsdoc-conf.json"


.header {
    padding: 20px 0;
}

.details {
    margin-top: -18px;
}

.mask {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0px;
    left: 0px;
    background-color: #000000;
    opacity: .4;
    z-index: 1;
}

.invite-row .invite {
    margin: 10px 10px 10px 0;
}

.rsvp-buttons {
    text-align: center;
    margin: 40px 0 40px 0;
}

.description {
    margin: 20px 0 20px 0;
}

.attendance .who {
    margin-bottom: 5px;
}

.attendance .invite {
    text-align: center;
}

input.chosen {
    font-weight: bold;
}

.map {
    position: relative;
    background-image: url('/soma.png');
    background-position: -20px -20px;
    width: 500px;
    height: 500px;
}

.map circle.public {
    fill: #49AFCD;
}

.map circle.private {
    fill: #DA4F49;
}

.map text {
    text-anchor: middle;
    fill: white;
    font-weight: bold;
}

.map circle.callout {
    stroke-width: 5px;
    stroke-dasharray: 9, 5;
    stroke-opacity: .8;
    fill: none;
    stroke: red;
}

.attribution {
    position: absolute;
    bottom: 0;
    background-color: white;
    padding: 3px;
    padding-bottom: 0;
}


var selftest = require('../selftest.js');
var Sandbox = selftest.Sandbox;
var path = require("path");
var fs = require("fs");

selftest.define("build errors - colon in filename", function () {
  var s = new Sandbox();
  var run;

  s.createApp("myapp", "build-errors");
  s.cd("myapp");
  run = s.run("build", "myapp.tgz");
  run.waitSecs(60);
  run.matchErr("crash in plugin (compiling foo.awesome)");
  run.expectExit(1);
  run.forbidAll("Couldn't parse stack");
});