keep keep keep keep keep replace keep keep keep keep keep

var main = require('./main.js');
var path = require('path');
var _ = require('underscore');
var fs = require('fs');
var files = require('./files.js');
var deploy = require('./deploy.js');
var buildmessage = require('./buildmessage.js');
var project = require('./project.js').project;
var warehouse = require('./warehouse.js');
var auth = require('./auth.js');
var config = require('./config.js');
var release = require('./release.js');
var Future = require('fibers/future');
var runLog = require('./run-log.js');
var packageClient = require('./package-client.js');
var utils = require('./utils.js');
var httpHelpers = require('./http-helpers.js');
var archinfo = require('./archinfo.js');
var tropohouse = require('./tropohouse.js');
var packageCache = require('./package-cache.js');
var PackageSource = require('./package-source.js');
var compiler = require('./compiler.js');
var catalog = require('./catalog.js');
var stats = require('./stats.js');
var unipackage = require('./unipackage.js');
var cordova = require('./commands-cordova.js');
var commandsPackages = require('./commands-packages.js');
var execFileSync = require('./utils.js').execFileSync;

// The architecture used by Galaxy servers; it's the architecture used
// by 'meteor deploy'.
var DEPLOY_ARCH = 'os.linux.x86_64';

// Given a site name passed on the command line (eg, 'mysite'), return
// a fully-qualified hostname ('mysite.meteor.com').
//
// This is fairly simple for now. It appends 'meteor.com' if the name
// doesn't contain a dot, and it deletes any trailing dots (the
// technically legal hostname 'mysite.com.' is canonicalized to
// 'mysite.com').
//
// In the future, you should be able to make this default to some
// other domain you control, rather than 'meteor.com'.
var qualifySitename = function (site) {
  if (site.indexOf(".") === -1)
    site = site + ".meteor.com";
  while (site.length && site[site.length - 1] === ".")
    site = site.substring(0, site.length - 1);
  return site;
};

// Given a (non necessarily fully qualified) site name from the
// command line, return true if the site is hosted by a Galaxy, else
// false.
var hostedWithGalaxy = function (site) {
  var site = qualifySitename(site);
  return !! require('./deploy-galaxy.js').discoverGalaxy(site);
};

// Get all local packages available. Returns a map from the package name to the
// version record for that package.
var getLocalPackages = function () {
  var ret = {};
  buildmessage.assertInCapture();

  var names = catalog.complete.getAllPackageNames();
  _.each(names, function (name) {
    if (catalog.complete.isLocalPackage(name)) {
      ret[name] = catalog.complete.getLatestVersion(name);
    }
  });

  return ret;
};

var parseHostPort = function (str) {
  // XXX factor this out into a {type: host/port}?
  var portMatch = str.match(/^(?:(.+):)?([0-9]+)$/);
  if (!portMatch) {
    throw new Error(
"run: --port (-p) must be a number or be of the form 'host:port' where\n" +
"port is a number. Try 'meteor help run' for help.\n");
  }

  var host = portMatch[1] || 'localhost';
  var port = parseInt(portMatch[2]);

  return {
    host: host,
    port: port
  };
};

///////////////////////////////////////////////////////////////////////////////
// options that act like commands
///////////////////////////////////////////////////////////////////////////////

// Prints the Meteor architecture name of this host
main.registerCommand({
  name: '--arch',
  requiresRelease: false
}, function (options) {
  var archinfo = require('./archinfo.js');
  console.log(archinfo.host());
});

// Prints the current release in use. Note that if there is not
// actually a specific release, we print to stderr and exit non-zero,
// while if there is a release we print to stdout and exit zero
// (making this useful to scripts).
// XXX: What does this mean in our new release-free world?
main.registerCommand({
  name: '--version',
  requiresRelease: false
}, function (options) {
  if (release.current === null) {
    if (! options.appDir)
      throw new Error("missing release, but not in an app?");
    process.stderr.write(
"This project was created with a checkout of Meteor, rather than an\n" +
"official release, and doesn't have a release number associated with\n" +
"it. You can set its release with 'meteor update'.\n");
    return 1;
  }

  if (release.current.isCheckout()) {
    process.stderr.write("Unreleased (running from a checkout)\n");
    return 1;
  }

  console.log(release.current.getDisplayName());
});

// Internal use only. For automated testing.
main.registerCommand({
  name: '--long-version',
  requiresRelease: false
}, function (options) {
  if (files.inCheckout()) {
    process.stderr.write("checkout\n");
    return 1;
  } else if (release.current === null) {
    // .meteor/release says "none" but not in a checkout.
    process.stderr.write("none\n");
    return 1;
  } else {
    process.stdout.write(release.current.name + "\n");
    process.stdout.write(files.getToolsVersion() + "\n");
    return 0;
  }
});

// Internal use only. For automated testing.
main.registerCommand({
  name: '--requires-release',
  requiresRelease: true
}, function (options) {
  return 0;
});

///////////////////////////////////////////////////////////////////////////////
// run
///////////////////////////////////////////////////////////////////////////////

main.registerCommand({
  name: 'run',
  requiresApp: true,
  maxArgs: Infinity,
  options: {
    port: { type: String, short: "p", default: '3000' },
    'app-port': { type: String },
    production: { type: Boolean },
    'raw-logs': { type: Boolean },
    settings: { type: String },
    'no-server': { type: Boolean },
    program: { type: String },
    verbose: { type: Boolean, short: "v" },
    // With --once, meteor does not re-run the project if it crashes
    // and does not monitor for file changes. Intentionally
    // undocumented: intended for automated testing (eg, cli-test.sh),
    // not end-user use. #Once
    once: { type: Boolean },
    // With --clean, meteor cleans the application directory and uses the
    // bundled assets only. Encapsulates the behavior of once (does not rerun)
    // and does not monitor for file changes. Not for end-user use.
    clean: { type: Boolean}
  }
}, function (options) {

  // Calculate project versions. (XXX: Theoretically, we should not be doing it
  // here. We should do it lazily, if the command calls for it. However, we do
  // end up recalculating them for stats, for example, and, more importantly, we
  // have noticed some issues if we just leave this in the air. We think that
  // those issues are concurrency-related, or possibly some weird
  // order-of-execution of interaction that we are failing to understand. This
  // seems to fix it in a clear and understandable fashion.)
  var messages = buildmessage.capture(function () {
    project.getVersions();  // #StructuredProjectInitialization
  });
  if (messages.hasMessages()) {
    process.stderr.write(messages.formatMessages());
    return 1;
  }

  try {
    var parsedHostPort = parseHostPort(options.port);
  } catch (err) {
    process.stderr.write(err.message);
    return 1;
  }

  // If we are targeting the remote devices
  if (_.intersection(options.args, ['ios-device', 'android-device']).length) {
    // ... and if you didn't specify your ip address as host, print a warning
    if (parsedHostPort.host === 'localhost')
      process.stderr.write(
        "WARN: You are testing your app on a remote device but your host option\n" +
        "WARN: is set to 'localhost'. Perhaps you want to change it to your local\n" +
        "WARN: network's IP address with the -p option?\n");
  }

  // Always bundle for the browser by default.
  var webArchs = ["web.browser"];

  // If additional args were specified, then also start a mobile build.
  if (options.args.length) {
    // will asynchronously start mobile emulators/devices
    try {
      // --clean encpasulates the behavior of once
      if (options.clean) {
        options.once = true;
      }

      var appName = path.basename(options.appDir);
      var localPath = path.join(options.appDir, '.meteor', 'local');
      cordova.buildPlatforms(localPath, options.args,
        _.extend({ appName: appName, debug: ! options.production },
                 options, parsedHostPort));
      cordova.runPlatforms(localPath, options.args, options);
    } catch (err) {
      process.stderr.write(err.message + '\n');
      return 1;
    }
  }

  if (!_.isEmpty(project.getCordovaPlatforms())) {
    webArchs.push("web.cordova");
  }

  if (options['no-server'])
    return 0;

  var appHost, appPort;
  if (options['app-port']) {
    var appPortMatch = options['app-port'].match(/^(?:(.+):)?([0-9]+)?$/);
    if (!appPortMatch) {
      process.stderr.write(
"run: --app-port must be a number or be of the form 'host:port' where\n" +
"port is a number. Try 'meteor help run' for help.\n");
      return 1;
    }
    appHost = appPortMatch[1] || null;
    // It's legit to specify `--app-port host:` and still let the port be
    // randomized.
    appPort = appPortMatch[2] ? parseInt(appPortMatch[2]) : null;
  }

  if (release.forced) {
    var appRelease = project.getMeteorReleaseVersion();
    if (release.current.name !== appRelease) {
      console.log("=> Using Meteor %s as requested (overriding Meteor %s)",
                  release.current.name, appRelease);
      console.log();
    }
  }

  auth.tryRevokeOldTokens({timeout: 1000});

  if (options['raw-logs'])
    runLog.setRawLogs(true);

  var runAll = require('./run-all.js');
  return runAll.run(options.appDir, {
    proxyPort: parsedHostPort.port,
    proxyHost: parsedHostPort.host,
    appPort: appPort,
    appHost: appHost,
    settingsFile: options.settings,
    program: options.program || undefined,
    buildOptions: {
      minify: options.production,
      webArchs: webArchs
    },
    rootUrl: process.env.ROOT_URL,
    mongoUrl: process.env.MONGO_URL,
    oplogUrl: process.env.MONGO_OPLOG_URL,
    once: options.once
  });
});

///////////////////////////////////////////////////////////////////////////////
// create
///////////////////////////////////////////////////////////////////////////////

main.registerCommand({
  name: 'create',
  maxArgs: 1,
  options: {
    list: { type: Boolean },
    example: { type: String },
    package: { type: Boolean }
  }
}, function (options) {

  // Creating a package is much easier than creating an app, so if that's what
  // we are doing, do that first. (For example, we don't springboard to the
  // latest release to create a package if we are inside an app)
  if (options.package) {
    var packageName = options.args[0];

    // No package examples exist yet.
    if (options.list && options.example) {
      process.stderr.write("No package examples exist at this time.\n\n");
      throw new main.ShowUsage;
    }

    if (!packageName) {
      process.stderr.write("Please specify the name of the package. \n");
      throw new main.ShowUsage;
    }

    if (fs.existsSync(packageName)) {
      process.stderr.write(packageName + ": Already exists\n");
      return 1;
    }

    var transform = function (x) {
      var xn = x.replace(/~name~/g, packageName);

      // If we are running from checkout, comment out the line sourcing packages
      // from a release, with the latest release filled in (in case they do want
      // to publish later). If we are NOT running from checkout, fill it out
      // with the current release.
      var relString;
      if (release.current.isCheckout()) {
        xn = xn.replace(/~cc~/g, "//");
        var rel = commandsPackages.doOrDie(function () {
          return catalog.complete.getDefaultReleaseVersion();
        });
        var relString = rel.track + "@" + rel.version;
      } else {
        xn = xn.replace(/~cc~/g, "");
        relString = release.current.name;
      }

      // If we are not in checkout, write the current release here.
      return xn.replace(/~release~/g, relString);
    };
    try {
      files.cp_r(path.join(__dirname, 'skel-pack'), packageName, {
        transformFilename: function (f) {
          return transform(f);
      },
      transformContents: function (contents, f) {
        if ((/(\.html|\.js|\.css)/).test(f))
          return new Buffer(transform(contents.toString()));
        else
          return contents;
      },
      ignore: [/^local$/]
    });
   } catch (err) {
     process.stderr.write("Could not create package: " + err.message + "\n");
     return 1;
   }

    process.stdout.write(packageName + ": created\n");
    return 0;
  }

  // Suppose you have an app A, and from some directory inside that
  // app, you run 'meteor create /my/new/app'. The new app should use
  // the latest available Meteor release, not the release that A
  // uses. So if we were run from inside an app directory, and the
  // user didn't force a release with --release, we need to
  // springboard to the correct release and tools version.
  //
  // (In particular, it's not sufficient to create the new app with
  // this version of the tools, and then stamp on the correct release
  // at the end.)
  if (! release.current.isCheckout() && !release.forced) {
    var needToSpringboard = commandsPackages.doOrDie(function () {
      return release.current.name !== release.latestDownloaded();
    });
    if (needToSpringboard)
      throw new main.SpringboardToLatestRelease;
  }

  var exampleDir = path.join(__dirname, '..', 'examples');
  var examples = _.reject(fs.readdirSync(exampleDir), function (e) {
    return (e === 'unfinished' || e === 'other'  || e[0] === '.');
  });

  if (options.list) {
    process.stdout.write("Available examples:\n");
    _.each(examples, function (e) {
      process.stdout.write("  " + e + "\n");
    });
    process.stdout.write("\n" +
"Create a project from an example with 'meteor create --example <name>'.\n");
    return 0;
  };

  var appPath;
  if (options.args.length === 1)
    appPath = options.args[0];
  else if (options.example)
    appPath = options.example;
  else
    throw new main.ShowUsage;

  if (fs.existsSync(appPath)) {
    process.stderr.write(appPath + ": Already exists\n");
    return 1;
  }

  if (files.findAppDir(appPath)) {
    process.stderr.write(
      "You can't create a Meteor project inside another Meteor project.\n");
    return 1;
  }

  var transform = function (x) {
    return x.replace(/~name~/g, path.basename(appPath));
  };

  if (options.example) {
    if (examples.indexOf(options.example) === -1) {
      process.stderr.write(options.example + ": no such example\n\n");
      process.stderr.write("List available applications with 'meteor create --list'.\n");
      return 1;
    } else {
      files.cp_r(path.join(exampleDir, options.example), appPath, {
        // We try not to check the identifier into git, but it might still
        // accidentally exist and get added (if running from checkout, for
        // example). To be on the safe side, explicitly remove the identifier
        // from example apps.
        ignore: [/^local$/, /^identifier$/]
      });
    }
  } else {
    files.cp_r(path.join(__dirname, 'skel'), appPath, {
      transformFilename: function (f) {
        return transform(f);
      },
      transformContents: function (contents, f) {
        if ((/(\.html|\.js|\.css)/).test(f))
          return new Buffer(transform(contents.toString()));
        else
          return contents;
      },
      ignore: [/^local$/, /^identifier$/]
    });
  }

  // We are actually working with a new meteor project at this point, so
  // reorient its path. We might do some things to it, but they should be
  // invisible to the user, so mute non-error output.
  project.setRootDir(appPath);
  project.setMuted(true);
  project.writeMeteorReleaseVersion(
    release.current.isCheckout() ? "none" : release.current.name);

  var messages = buildmessage.capture(function () {
    project._ensureDepsUpToDate();
  });
  if (messages.hasMessages()) {
    process.stderr.write(messages.formatMessages());
    return 1;
  }

  process.stdout.write(appPath + ": created");
  if (options.example && options.example !== appPath)
    process.stderr.write(" (from '" + options.example + "' template)");
  process.stdout.write(".\n\n");

  process.stdout.write(
    "To run your new app:\n" +
      "   cd " + appPath + "\n" +
      "   meteor\n");
});

///////////////////////////////////////////////////////////////////////////////
// run-upgrader
///////////////////////////////////////////////////////////////////////////////

main.registerCommand({
  name: 'run-upgrader',
  hidden: true,
  minArgs: 1,
  maxArgs: 1,
  requiresApp: true
}, function (options) {
  var upgrader = options.args[0];

  var upgraders = require("./upgraders.js");
  console.log("%s: running upgrader %s.",
              path.basename(options.appDir), upgrader);
  upgraders.runUpgrader(upgrader);
});

///////////////////////////////////////////////////////////////////////////////
// build
///////////////////////////////////////////////////////////////////////////////

var buildCommands = {
  minArgs: 1,
  maxArgs: 1,
  requiresApp: true,
  options: {
    debug: { type: Boolean },
    directory: { type: Boolean },
    architecture: { type: String },
    port: { type: String, short: "p", default: "localhost:3000" },
    settings: { type: String },
    // Undocumented
    'for-deploy': { type: Boolean },
  }
};

var buildWithOptions = function (options) {
  // XXX output, to stderr, the name of the file written to (for human
  // comfort, especially since we might change the name)

  // XXX name the root directory in the bundle based on the basename
  // of the file, not a constant 'bundle' (a bit obnoxious for
  // machines, but worth it for humans)

  // Error handling for options.architecture. We must pass in only one of three
  // architectures. See archinfo.js for more information on what the
  // architectures are, what they mean, et cetera.
  var VALID_ARCHITECTURES =
  ["os.osx.x86_64", "os.linux.x86_64", "os.linux.x86_32"];
  if (options.architecture &&
      _.indexOf(VALID_ARCHITECTURES, options.architecture) === -1) {
    process.stderr.write("Invalid architecture: " + options.architecture + "\n");
    process.stderr.write(
      "Please use one of the following: " + VALID_ARCHITECTURES + "\n");
    return 1;
  }

  var bundleArch =  options.architecture || archinfo.host();
  var webArchs = ["web.browser"];

  var localPath = path.join(options.appDir, '.meteor', 'local');

  var mobilePlatforms = project.getCordovaPlatforms();
  var appName = path.basename(options.appDir);

  if (! _.isEmpty(mobilePlatforms)) {
    if (options.port === buildCommands.options.port.default) {
      process.stdout.write("WARNING: Building your app with host: localhost.\n" +
                           "Pass a -p argument to specify a host URL.\n");
    }
    var cordovaSettings = {};

    try {
      var parsedHostPort = parseHostPort(options.port);
    } catch (err) {
      process.stderr.write(err.message);
      return 1;
    }

    cordova.buildPlatforms(localPath, mobilePlatforms,
      _.extend({}, options, parsedHostPort, { appName: appName }));
    webArchs.push("web.cordova");
  }

  var buildDir = path.join(localPath, 'build_tar');
  var outputPath = path.resolve(options.args[0]); // get absolute path
  var bundlePath = options['directory'] ?
      path.join(outputPath, 'bundle') : path.join(buildDir, 'bundle');

  var loader;
  var messages = buildmessage.capture(function () {
    loader = project.getPackageLoader();
  });
  if (messages.hasMessages()) {
    process.stderr.write("Errors prevented bundling your app:\n");
    process.stderr.write(messages.formatMessages());
    return 1;
  }

  var statsMessages = buildmessage.capture(function () {
    stats.recordPackages();
  });
  if (statsMessages.hasMessages()) {
    process.stdout.write("Error recording package list:\n" +
                         statsMessages.formatMessages());
    // ... but continue;
  }

  var bundler = require(path.join(__dirname, 'bundler.js'));
  var bundleResult = bundler.bundle({
    outputPath: bundlePath,
    buildOptions: {
      minify: ! options.debug,
      // XXX is this a good idea, or should linux be the default since
      //     that's where most people are deploying
      //     default?  i guess the problem with using DEPLOY_ARCH as default
      //     is then 'meteor bundle' with no args fails if you have any local
      //     packages with binary npm dependencies
      serverArch: bundleArch,
      webArchs: webArchs
    }
  });
  if (bundleResult.errors) {
    process.stderr.write("Errors prevented bundling:\n");
    process.stderr.write(bundleResult.errors.formatMessages());
    return 1;
  }

  files.mkdir_p(outputPath);
  if (!options['directory']) {
    try {
      var outputTar = path.join(outputPath, appName + '.tar.gz');
      files.createTarball(path.join(buildDir, 'bundle'), outputTar);
    } catch (err) {
      console.log(JSON.stringify(err));
      process.stderr.write("Couldn't create tarball\n");
    }
  }

  // Copy over the Cordova builds AFTER we bundle so that they are not included
  // in the main bundle.
  _.each(mobilePlatforms, function (platformName) {
    var buildPath = path.join(localPath, 'cordova-build',
                              'platforms', platformName);
    var platformPath = path.join(outputPath, platformName);
    files.cp_r(buildPath, platformPath);
  });

  files.rm_recursive(buildDir);
};

main.registerCommand(_.extend({ name: 'build' }, buildCommands),
    function (options) {
  buildWithOptions(options);
});

// Deprecated -- identical functionality to 'build'
main.registerCommand(_.extend({ name: 'bundle', hidden: true }, buildCommands),
    function (options) {
  process.stdout.write("WARNING: 'bundle' has been deprecated. " +
                       "Use 'build' instead.\n");
  buildWithOptions(options);
});

///////////////////////////////////////////////////////////////////////////////
// mongo
///////////////////////////////////////////////////////////////////////////////

main.registerCommand({
  name: 'mongo',
  maxArgs: 1,
  options: {
    url: { type: Boolean, short: 'U' }
  },
  requiresApp: function (options) {
    return options.args.length === 0;
  }
}, function (options) {
  var mongoUrl;
  var usedMeteorAccount = false;

  if (options.args.length === 0) {
    // localhost mode
    var findMongoPort =
      require('./run-mongo.js').findMongoPort;
    var mongoPort = findMongoPort(options.appDir);

    // XXX detect the case where Meteor is running, but MONGO_URL was
    // specified?

    if (! mongoPort) {
      process.stdout.write(
"mongo: Meteor isn't running a local MongoDB server.\n" +
"\n" +
"This command only works while Meteor is running your application\n" +
"locally. Start your application first. (This error will also occur if\n" +
"you asked Meteor to use a different MongoDB server with $MONGO_URL when\n" +
"you ran your application.)\n" +
"\n" +
"If you're trying to connect to the database of an app you deployed\n" +
"with 'meteor deploy', specify your site's name with this command.\n"
);
      return 1;
    }
    mongoUrl = "mongodb://127.0.0.1:" + mongoPort + "/meteor";

  } else {
    // remote mode
    var site = qualifySitename(options.args[0]);
    config.printUniverseBanner();

    if (hostedWithGalaxy(site)) {
      var deployGalaxy = require('./deploy-galaxy.js');
      mongoUrl = deployGalaxy.temporaryMongoUrl(site);
    } else {
      mongoUrl = deploy.temporaryMongoUrl(site);
      usedMeteorAccount = true;
    }

    if (! mongoUrl)
      // temporaryMongoUrl() will have printed an error message
      return 1;
  }
  if (options.url) {
    console.log(mongoUrl);
  } else {
    if (usedMeteorAccount)
      auth.maybePrintRegistrationLink();
    process.stdin.pause();
    var runMongo = require('./run-mongo.js');
    runMongo.runMongoShell(mongoUrl);
    throw new main.WaitForExit;
  }
});

///////////////////////////////////////////////////////////////////////////////
// reset
///////////////////////////////////////////////////////////////////////////////

main.registerCommand({
  name: 'reset',
  // Doesn't actually take an argument, but we want to print an custom
  // error message if they try to pass one.
  maxArgs: 1,
  requiresApp: true
}, function (options) {
  if (options.args.length !== 0) {
    process.stderr.write(
"meteor reset only affects the locally stored database.\n" +
"\n" +
"To reset a deployed application use\n" +
"  meteor deploy --delete appname\n" +
"followed by\n" +
"  meteor deploy appname\n");
    return 1;
  }

  // XXX detect the case where Meteor is running the app, but
  // MONGO_URL was set, so we don't see a Mongo process

  var findMongoPort =
    require(path.join(__dirname, 'run-mongo.js')).findMongoPort;
  var isRunning = !! findMongoPort(options.appDir);
  if (isRunning) {
    process.stderr.write(
"reset: Meteor is running.\n" +
"\n" +
"This command does not work while Meteor is running your application.\n" +
"Exit the running Meteor development server.\n");
    return 1;
  }

  var localDir = path.join(options.appDir, '.meteor', 'local');
  files.rm_recursive(localDir);

  process.stdout.write("Project reset.\n");
});

///////////////////////////////////////////////////////////////////////////////
// deploy
///////////////////////////////////////////////////////////////////////////////

main.registerCommand({
  name: 'deploy',
  minArgs: 1,
  maxArgs: 1,
  options: {
    'delete': { type: Boolean, short: 'D' },
    debug: { type: Boolean },
    settings: { type: String },
    star: { type: String },
    // No longer supported, but we still parse it out so that we can
    // print a custom error message.
    password: { type: String },
    // Shouldn't be documented until the Galaxy release. Marks the
    // application as an admin app, so that it will be available in
    // Galaxy admin interface.
    admin: { type: Boolean },
    // Override architecture to deploy whatever stuff we have locally, even if
    // it contains binary packages that should be incompatible. A hack to allow
    // people to deploy from checkout or do other weird shit. We are not
    // responsible for the consequences.
    'override-architecture-with-local' : { type: Boolean }
  },
  requiresApp: function (options) {
    return options.delete || options.star ? false : true;
  }
}, function (options) {
  var site = qualifySitename(options.args[0]);
  config.printUniverseBanner();
  var useGalaxy = hostedWithGalaxy(site);
  var deployGalaxy;

  if (options.delete) {
    if (useGalaxy) {
      deployGalaxy = require('./deploy-galaxy.js');
      return deployGalaxy.deleteApp(site);
    } else {
      return deploy.deleteApp(site);
    }
  }

  // We are actually going to end up compiling an app at this point, so figure
  // out its versions. . (XXX: Theoretically, we should not be doing it here. We
  // should do it lazily, if the command calls for it. However, we do end up
  // recalculating them for stats, for example, and, more importantly, we have
  // noticed some issues if we just leave this in the air. We think that those
  // issues are concurrency-related, or possibly some weird order-of-execution
  // of interaction that we are failing to understand. This seems to fix it in a
  // clear and understandable fashion.)
  var messages = buildmessage.capture(function () {
    project.getVersions();  // #StructuredProjectInitialization
  });
  if (messages.hasMessages()) {
    process.stderr.write(messages.formatMessages());
    return 1;
  }

  if (options.password) {
    if (useGalaxy) {
      process.stderr.write("Galaxy does not support --password.\n");
    } else {
      process.stderr.write(
"Setting passwords on apps is no longer supported. Now there are\n" +
"user accounts and your apps are associated with your account so that\n" +
"only you (and people you designate) can access them. See the\n" +
"'meteor claim' and 'meteor authorized' commands.\n");
    }
    return 1;
  }

  var starball = options.star;
  if (starball && ! useGalaxy) {
    // XXX it would be nice to support this for non-Galaxy deploys too
    process.stderr.write(
"--star: only supported when deploying to Galaxy.\n");
    return 1;
  }

  var loggedIn = auth.isLoggedIn();
  if (! loggedIn) {
    process.stderr.write(
"To instantly deploy your app on a free testing server, just enter your\n" +
"email address!\n" +
"\n");

    if (! auth.registerOrLogIn())
      return 1;
  }

  // Override architecture iff applicable.
  var buildArch = DEPLOY_ARCH;
  if (options['override-architecture-with-local']) {
    process.stdout.write(
      "\n => WARNING: OVERRIDING DEPLOY ARCHITECTURE WITH LOCAL ARCHITECTURE\n");
    process.stdout.write(
      " => If your app contains binary code, it may break terribly and you will be sad.\n\n");
    buildArch = archinfo.host();
  }

  var buildOptions = {
    minify: ! options.debug,
    serverArch: buildArch
  };

  var deployResult;
  if (useGalaxy) {
    deployGalaxy = require('./deploy-galaxy.js');
    deployResult = deployGalaxy.deploy({
      app: site,
      appDir: options.appDir,
      settingsFile: options.settings,
      starball: starball,
      buildOptions: buildOptions,
      admin: options.admin
    });
  } else {
    deployResult = deploy.bundleAndDeploy({
      appDir: options.appDir,
      site: site,
      settingsFile: options.settings,
      buildOptions: buildOptions
    });
  }

  if (deployResult === 0) {
    auth.maybePrintRegistrationLink({
      leadingNewline: true,
      // If the user was already logged in at the beginning of the
      // deploy, then they've already been prompted to set a password
      // at least once before, so we use a slightly different message.
      firstTime: ! loggedIn
    });
  }

  return deployResult;
});

///////////////////////////////////////////////////////////////////////////////
// logs
///////////////////////////////////////////////////////////////////////////////

main.registerCommand({
  name: 'logs',
  minArgs: 1,
  maxArgs: 1,
  options: {
    // XXX once Galaxy is released, document this
    stream: { type: Boolean, short: 'f' }
  }
}, function (options) {
  var site = qualifySitename(options.args[0]);

  if (hostedWithGalaxy(site)) {
    var deployGalaxy = require('./deploy-galaxy.js');
    var ret = deployGalaxy.logs({
      app: site,
      streaming: options.stream
    });
    if (options.stream && ret === null)
      throw new main.WaitForExit;
    return ret;
  } else {
    return deploy.logs(site);
  }
});

///////////////////////////////////////////////////////////////////////////////
// authorized
///////////////////////////////////////////////////////////////////////////////

main.registerCommand({
  name: 'authorized',
  minArgs: 1,
  maxArgs: 1,
  options: {
    add: { type: String, short: "a" },
    remove: { type: String, short: "r" },
    list: { type: Boolean }
  }
}, function (options) {

  if (options.add && options.remove) {
    process.stderr.write(
      "Sorry, you can only add or remove one user at a time.\n");
    return 1;
  }

  if ((options.add || options.remove) && options.list) {
    process.stderr.write(
"Sorry, you can't change the users at the same time as you're listing them.\n");
    return 1;
  }

  config.printUniverseBanner();
  auth.pollForRegistrationCompletion();
  var site = qualifySitename(options.args[0]);

  if (hostedWithGalaxy(site)) {
    process.stderr.write(
"Sites hosted on Galaxy do not have an authorized user list.\n" +
"Instead, go to your Galaxy dashboard to change the authorized users\n" +
"of your Galaxy.\n");
    return 1;
  }

  if (! auth.isLoggedIn()) {
    process.stderr.write(
      "You must be logged in for that. Try 'meteor login'.\n");
    return 1;
  }

  if (options.add)
    return deploy.changeAuthorized(site, "add", options.add);
  else if (options.remove)
    return deploy.changeAuthorized(site, "remove", options.remove);
  else
    return deploy.listAuthorized(site);
});

///////////////////////////////////////////////////////////////////////////////
// claim
///////////////////////////////////////////////////////////////////////////////

main.registerCommand({
  name: 'claim',
  minArgs: 1,
  maxArgs: 1
}, function (options) {
  config.printUniverseBanner();
  auth.pollForRegistrationCompletion();
  var site = qualifySitename(options.args[0]);

  if (! auth.isLoggedIn()) {
    process.stderr.write(
"You must be logged in to claim sites. Use 'meteor login' to log in.\n" +
"If you don't have a Meteor developer account yet, create one by clicking\n" +
"'Sign in' and then 'Create account' at www.meteor.com.\n\n");
    return 1;
  }

  if (hostedWithGalaxy(site)) {
    process.stderr.write(
      "Sorry, you can't claim sites that are hosted on Galaxy.\n");
    return 1;
  }

  return deploy.claim(site);
});

///////////////////////////////////////////////////////////////////////////////
// test-packages
///////////////////////////////////////////////////////////////////////////////

//
// Test your local packages.
//
main.registerCommand({
  name: 'test-packages',
  maxArgs: Infinity,
  options: {
    port: { type: String, short: "p", default: "localhost:3000" },
    deploy: { type: String },
    production: { type: Boolean },
    settings: { type: String },
    verbose: { type: Boolean, short: "v" },

    // Undocumented. See #Once
    once: { type: Boolean },
    // Undocumented. To ensure that QA covers both
    // PollingObserveDriver and OplogObserveDriver, this option
    // disables oplog for tests.  (It still creates a replset, it just
    // doesn't do oplog tailing.)
    'disable-oplog': { type: Boolean },
    // Undocumented flag to use a different test driver.
    'driver-package': { type: String },

    // Undocumented, sets the path of where the temp app should be created
    'test-app-path': { type: String },

    // hard-coded options with all known Cordova platforms
    ios: { type: Boolean },
    'ios-device': { type: Boolean },
    android: { type: Boolean },
    'android-device': { type: Boolean }
  }
}, function (options) {
  try {
    var parsedHostPort = parseHostPort(options.port);
  } catch (err) {
    process.stderr.write(err.message);
    return 1;
  }

  // XXX not good to change the options this way
  _.extend(options, parsedHostPort);

  var testPackages = null;
  try {
    var packages = getPackagesForTest(options.args);
    testPackages = packages.testPackages;
    localPackages = packages.localPackages;
    options.localPackageNames = packages.localPackages;
  } catch (err) {
    process.stderr.write('\n' + err.message);
    return 1;
  }

  // Make a temporary app dir (based on the test runner app). This will be
  // cleaned up on process exit. Using a temporary app dir means that we can
  // run multiple "test-packages" commands in parallel without them stomping
  // on each other.
  //
  // Note: testRunnerAppDir deliberately DOES NOT MATCH the app
  // package search path baked into release.current.catalog: we are
  // bundling the test runner app, but finding app packages from the
  // current app (if any).
  var testRunnerAppDir =
    options['test-app-path'] || files.mkdtemp('meteor-test-run');
  files.cp_r(path.join(__dirname, 'test-runner-app'), testRunnerAppDir);

  // We are going to operate in the special test project, so let's remap our
  // main project to the test directory.
  project.setRootDir(testRunnerAppDir);
  project.setMuted(true); // Mute output where applicable
  project.writeMeteorReleaseVersion(release.current.name || 'none');
  project.forceEditPackages(
    [options['driver-package'] || 'test-in-browser'],
    'add');

  var mobileOptions = ['ios', 'ios-device', 'android', 'android-device'];
  var mobilePlatforms = [];

  _.each(mobileOptions, function (option) {
    if (options[option])
      mobilePlatforms.push(option);
  });

  if (! _.isEmpty(mobilePlatforms)) {
    var localPath = path.join(testRunnerAppDir, '.meteor', 'local');

    var platforms =
      _.map(mobilePlatforms, function (t) { return t.replace(/-device$/, ''); });

    platforms = _.uniq(platforms);

    project.addCordovaPlatforms(platforms);

    try {
      cordova.buildPlatforms(localPath, mobilePlatforms,
        _.extend({}, options, {
          appName: path.basename(testRunnerAppDir),
          debug: ! options.production
        }));
      cordova.runPlatforms(localPath, mobilePlatforms, options);
    } catch (err) {
      process.stderr.write(err.message + '\n');
      return 1;
    }
  }

  return runTestAppForPackages(testPackages, testRunnerAppDir, options);
});

// Ensures that packages are prepared and built for testing
var getPackagesForTest = function (packages) {
  var testPackages;
  var localPackageNames = [];
  if (packages.length === 0) {
    // Only test local packages if no package is specified.
    // XXX should this use the new getLocalPackageNames?
    var packageList = commandsPackages.doOrDie(function () {
      return getLocalPackages();
    });
    if (! packageList) {
      // Couldn't load the package list, probably because some package
      // has a parse error. Bail out -- this kind of sucks; we would
      // like to find a way to get reloading.
      throw new Error("No packages to test");
    }
    testPackages = _.keys(packageList);
  } else {
    var messages = buildmessage.capture(function () {
      testPackages = _.map(packages, function (p) {
        return buildmessage.enterJob({
          title: "trying to test package `" + p + "`"
        }, function () {

          // If it's a package name, just pass it through.
          if (p.indexOf('/') === -1) {
            if (p.indexOf('@') !== -1) {
              buildmessage.error(
                "You may not specify versions for local packages: " + p );
              // Recover by returning p anyway.
            }
            // Check to see if this is a real package, and if it is a real
            // package, if it has tests.
            var versionRec = catalog.complete.getLatestVersion(p);
            if (!versionRec) {
              buildmessage.error(
                "Unknown package: " + p );
            }
            if (!catalog.complete.isLocalPackage(p)) {
              buildmessage.error(
                "Not a local package, cannot test: " + p );
              return p;
            }
            if (versionRec && !versionRec.testName) {
              buildmessage.error(
                "There are no tests for package: " + p );
            }
            return p;
          }
          // Otherwise it's a directory; load it into a Package now. Use
          // path.resolve to strip trailing slashes, so that packageName doesn't
          // have a trailing slash.
          //
          // Why use addLocalPackage instead of just loading the packages
          // and passing Unipackage objects to the bundler? Because we
          // actually need the Catalog to know about the package, so that
          // we are able to resolve the test package's dependency on the
          // main package. This is not ideal (I hate how this mutates global
          // state) but it'll do for now.
          var packageDir = path.resolve(p);
          var packageName = path.basename(packageDir);
          catalog.complete.addLocalPackage(packageName, packageDir);
          localPackageNames.push(packageName);
          return packageName;
        });

      });
    });

    if (messages.hasMessages()) {
      process.stderr.write("\n" + messages.formatMessages());
      return 1;
    }
  }

  return { testPackages: testPackages, localPackages: localPackageNames };
};

var runTestAppForPackages = function (testPackages, testRunnerAppDir, options) {
  // When we test packages, we need to know their versions and all of their
  // dependencies. We are going to add them to the project and have the project
  // compute them for us. This means that right now, we are testing all packages
  // as they work together.
  var tests = [];
  var messages = buildmessage.capture(function () {
    _.each(testPackages, function(name) {
      var versionRecord = catalog.complete.getLatestVersion(name);
      if (versionRecord && versionRecord.testName) {
        tests.push(versionRecord.testName);
      }
    });
  });
  if (messages.hasMessages()) {
    process.stderr.write(messages.formatMessages());
    return 1;
  }

  project.forceEditPackages(tests, 'add');

  var buildOptions = {
    minify: options.production
  };

  var ret;
  if (options.deploy) {
    buildOptions.serverArch = DEPLOY_ARCH;
    ret = deploy.bundleAndDeploy({
      appDir: testRunnerAppDir,
      site: options.deploy,
      settingsFile: options.settings,
      buildOptions: buildOptions,
      recordPackageUsage: false
    });
  } else {
    var runAll = require('./run-all.js');
    ret = runAll.run(testRunnerAppDir, {
      // if we're testing packages from an app, we still want to make
      // sure the user doesn't 'meteor update' in the app, requiring
      // a switch to a different release
      appDirForVersionCheck: options.appDir,
      proxyPort: options.port,
      disableOplog: options['disable-oplog'],
      settingsFile: options.settings,
      banner: "Tests",
      buildOptions: buildOptions,
      rootUrl: process.env.ROOT_URL,
      mongoUrl: process.env.MONGO_URL,
      oplogUrl: process.env.MONGO_OPLOG_URL,
      once: options.once,
      recordPackageUsage: false
    });
  }

  _.each(options.localPackageNames || [], function (name) {
    catalog.complete.removeLocalPackage(name);
  });

  return ret;
};

///////////////////////////////////////////////////////////////////////////////
// rebuild
///////////////////////////////////////////////////////////////////////////////

main.registerCommand({
  name: 'rebuild',
  maxArgs: Infinity,
  hidden: true
}, function (options) {
  var messages;
  var count = 0;
  // No packages specified. Rebuild everything.
  if (options.args.length === 0) {
    if (options.appDir) {
      // The catalog doesn't know about other programs in your app. Let's blow
      // away their .build directories if they have them, and not rebuild
      // them. Sort of hacky, but eh.
      var programsDir = project.getProgramsDirectory();
      var programsSubdirs = project.getProgramsSubdirs();
      _.each(programsSubdirs, function (program) {
        // The implementation of this part of the function might change once we
        // change the control file format to explicitly specify packages and
        // programs instead of just loading everything in the programs directory?
        files.rm_recursive(path.join(programsDir, program, '.build.' + program));
      });
    }

    messages = buildmessage.capture(function () {
      count = catalog.complete.rebuildLocalPackages();
    });
  } else {
    messages = buildmessage.capture(function () {
      count = catalog.complete.rebuildLocalPackages(options.args);
    });
  }
  if (count)
    console.log("Built " + count + " packages.");
  if (messages.hasMessages()) {
    process.stderr.write("\n" + messages.formatMessages());
    return 1;
  }
});

///////////////////////////////////////////////////////////////////////////////
// login
///////////////////////////////////////////////////////////////////////////////

main.registerCommand({
  name: 'login',
  options: {
    email: { type: String },
    // Undocumented: get credentials on a specific Galaxy. Do we still
    // need this?
    galaxy: { type: String }
  }
}, function (options) {
  return auth.loginCommand(_.extend({
    overwriteExistingToken: true
  }, options));
});

///////////////////////////////////////////////////////////////////////////////
// logout
///////////////////////////////////////////////////////////////////////////////

main.registerCommand({
  name: 'logout'
}, function (options) {
  return auth.logoutCommand(options);
});

///////////////////////////////////////////////////////////////////////////////
// whoami
///////////////////////////////////////////////////////////////////////////////

main.registerCommand({
  name: 'whoami'
}, function (options) {
  return auth.whoAmICommand(options);
});

///////////////////////////////////////////////////////////////////////////////
// self-test
///////////////////////////////////////////////////////////////////////////////

// XXX we should find a way to make self-test fully self-contained, so that it
// ignores "packageDirs" (ie, it shouldn't fail just because you happen to be
// sitting in an app with packages that don't build)

main.registerCommand({
  name: 'self-test',
  minArgs: 0,
  maxArgs: 1,
  options: {
    changed: { type: Boolean },
    'force-online': { type: Boolean },
    slow: { type: Boolean },
    browserstack: { type: Boolean },
    history: { type: Number }
  },
  hidden: true
}, function (options) {
  var selftest = require('./selftest.js');

  // Auto-detect whether to skip 'net' tests, unless --force-online is passed.
  var offline = false;
  if (!options['force-online']) {
    try {
      require('./http-helpers.js').getUrl("http://www.google.com/");
    } catch (e) {
      if (e instanceof files.OfflineError)
        offline = true;
    }
  }

  var testRegexp = undefined;
  if (options.args.length) {
    try {
      testRegexp = new RegExp(options.args[0]);
    } catch (e) {
      if (!(e instanceof SyntaxError))
        throw e;
      process.stderr.write("Bad regular expression: " + options.args[0] + "\n");
      return 1;
    }
  }

  var clients = {
    browserstack: options.browserstack
  };

  return selftest.runTests({
    onlyChanged: options.changed,
    offline: offline,
    includeSlowTests: options.slow,
    historyLines: options.history,
    clients: clients,
    testRegexp: testRegexp
  });

});

///////////////////////////////////////////////////////////////////////////////
// list-sites
///////////////////////////////////////////////////////////////////////////////

main.registerCommand({
  name: 'list-sites',
  minArgs: 0,
  maxArgs: 0
}, function (options) {
  auth.pollForRegistrationCompletion();
  if (! auth.isLoggedIn()) {
    process.stderr.write(
      "You must be logged in for that. Try 'meteor login'.\n");
    return 1;
  }

  return deploy.listSites();
});

///////////////////////////////////////////////////////////////////////////////
// dummy
///////////////////////////////////////////////////////////////////////////////

// Dummy test command. Used for automated testing of the command line
// option parser.

main.registerCommand({
  name: 'dummy',
  options: {
    email: { type: String, short: "e", required: true },
    port: { type: Number, short: "p", default: 3000 },
    url: { type: Boolean, short: "U" },
    'delete': { type: Boolean, short: "D" },
    changed: { type: Boolean }
  },
  maxArgs: 2,
  hidden: true
}, function (options) {
  var p = function (key) {
    if (_.has(options, key))
      return JSON.stringify(options[key]);
    return 'none';
  };

  process.stdout.write(p('email') + " " + p('port') + " " + p('changed') +
                       " " + p('args') + "\n");
  if (options.url)
    process.stdout.write('url\n');
  if (options['delete'])
    process.stdout.write('delete\n');
});


var Scanner = HTMLTools.Scanner;
var getComment = HTMLTools.Parse.getComment;
var getDoctype = HTMLTools.Parse.getDoctype;
var getHTMLToken = HTMLTools.Parse.getHTMLToken;

// "tokenize" is not really a great operation for real use, because
// it ignores the special content rules for tags like "style" and
// "script".
var tokenize = function (input) {
  var scanner = new Scanner(input);
  var tokens = [];
  while (! scanner.isEOF()) {
    var token = getHTMLToken(scanner);
    if (token)
      tokens.push(token);
  }

  return tokens;
};

Tinytest.add("html-tools - comments", function (test) {
  var succeed = function (input, content) {
    var scanner = new Scanner(input);
    var result = getComment(scanner);
    test.isTrue(result);
    test.equal(scanner.pos, content.length + 7);
    test.equal(result, {
      t: 'Comment',
      v: content
    });
  };

  var ignore = function (input) {
    var scanner = new Scanner(input);
    var result = getComment(scanner);;
    test.isFalse(result);
    test.equal(scanner.pos, 0);
  };

  var fatal = function (input, messageContains) {
    var scanner = new Scanner(input);
    var error;
    try {
      getComment(scanner);
    } catch (e) {
      error = e;
    }
    test.isTrue(error);
    if (error)
      test.isTrue(messageContains && error.message.indexOf(messageContains) >= 0, error.message);
  };

  test.equal(getComment(new Scanner("")),
             { t: 'Comment', v: ' hello ' });

  ignore("<!DOCTYPE>");
  ignore("<!-a");
  ignore("<--");
  ignore("<!");
  ignore("abc");
  ignore("<a");

  fatal('', 'cannot contain');
  fatal('', 'must end at first');

  fatal('', 'cannot contain');
  fatal('', 'cannot contain');

  succeed('', '');
  succeed('', '-x');
  succeed('', 'x');
  succeed('', ' hello - - world ');
});

Tinytest.add("html-tools - doctype", function (test) {
  var succeed = function (input, expectedProps) {
    var scanner = new Scanner(input);
    var result = getDoctype(scanner);
    test.isTrue(result);
    test.equal(scanner.pos, result.v.length);
    test.equal(input.slice(0, result.v.length), result.v);
    var actualProps = _.extend({}, result);
    delete actualProps.t;
    delete actualProps.v;
    test.equal(actualProps, expectedProps);
  };

  var fatal = function (input, messageContains) {
    var scanner = new Scanner(input);
    var error;
    try {
      getDoctype(scanner);
    } catch (e) {
      error = e;
    }
    test.isTrue(error);
    if (messageContains)
      test.isTrue(error.message.indexOf(messageContains) >= 0, error.message);
  };

  test.equal(getDoctype(new Scanner("<!DOCTYPE html>x")),
             { t: 'Doctype',
               v: '<!DOCTYPE html>',
               name: 'html' });

  test.equal(getDoctype(new Scanner("<!DOCTYPE html SYSTEM 'about:legacy-compat'>x")),
             { t: 'Doctype',
               v: "<!DOCTYPE html SYSTEM 'about:legacy-compat'>",
               name: 'html',
               systemId: 'about:legacy-compat' });

  test.equal(getDoctype(new Scanner("<!DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.0//EN'>x")),
             { t: 'Doctype',
               v: "<!DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.0//EN'>",
               name: 'html',
               publicId: '-//W3C//DTD HTML 4.0//EN' });

  test.equal(getDoctype(new Scanner("<!DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.0//EN' 'http://www.w3.org/TR/html4/strict.dtd'>x")),
             { t: 'Doctype',
               v: "<!DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.0//EN' 'http://www.w3.org/TR/html4/strict.dtd'>",
               name: 'html',
               publicId: '-//W3C//DTD HTML 4.0//EN',
               systemId: 'http://www.w3.org/TR/html4/strict.dtd' });

  succeed('<!DOCTYPE html>', {name: 'html'});
  succeed('<!DOCTYPE htML>', {name: 'html'});
  succeed('<!DOCTYPE HTML>', {name: 'html'});
  succeed('<!doctype html>', {name: 'html'});
  succeed('<!doctYPE html>', {name: 'html'});
  succeed('<!DOCTYPE html \u000c>', {name: 'html'});
  fatal('<!DOCTYPE', 'Expected space');
  fatal('<!DOCTYPE ', 'Malformed DOCTYPE');
  fatal('<!DOCTYPE  ', 'Malformed DOCTYPE');
  fatal('<!DOCTYPE>', 'Expected space');
  fatal('<!DOCTYPE >', 'Malformed DOCTYPE');
  fatal('<!DOCTYPE\u0000', 'Expected space');
  fatal('<!DOCTYPE \u0000', 'Malformed DOCTYPE');
  fatal('<!DOCTYPE html\u0000>', 'Malformed DOCTYPE');
  fatal('<!DOCTYPE html', 'Malformed DOCTYPE');

  succeed('<!DOCTYPE html SYSTEM "about:legacy-compat">', {name: 'html', systemId: 'about:legacy-compat'});
  succeed('<!doctype HTML system "about:legacy-compat">', {name: 'html', systemId: 'about:legacy-compat'});
  succeed("<!DOCTYPE html SYSTEM 'about:legacy-compat'>", {name: 'html', systemId: 'about:legacy-compat'});
  succeed("<!dOcTyPe HtMl sYsTeM 'about:legacy-compat'>", {name: 'html', systemId: 'about:legacy-compat'});
  succeed('<!DOCTYPE  html\tSYSTEM\t"about:legacy-compat"   \t>', {name: 'html', systemId: 'about:legacy-compat'});
  fatal('<!DOCTYPE html SYSTE "about:legacy-compat">', 'Expected PUBLIC or SYSTEM');
  fatal('<!DOCTYPE html SYSTE', 'Expected PUBLIC or SYSTEM');
  fatal('<!DOCTYPE html SYSTEM"about:legacy-compat">', 'Expected space');
  fatal('<!DOCTYPE html SYSTEM');
  fatal('<!DOCTYPE html SYSTEM ');
  fatal('<!DOCTYPE html SYSTEM>');
  fatal('<!DOCTYPE html SYSTEM >');
  fatal('<!DOCTYPE html SYSTEM ">">');
  fatal('<!DOCTYPE html SYSTEM "\u0000about:legacy-compat">');
  fatal('<!DOCTYPE html SYSTEM "about:legacy-compat\u0000">');
  fatal('<!DOCTYPE html SYSTEM "');
  fatal('<!DOCTYPE html SYSTEM "">');
  fatal('<!DOCTYPE html SYSTEM \'');
  fatal('<!DOCTYPE html SYSTEM\'a\'>');
  fatal('<!DOCTYPE html SYSTEM about:legacy-compat>');

  succeed('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0//EN">',
          { name: 'html',
            publicId: '-//W3C//DTD HTML 4.0//EN'});
  succeed('<!DOCTYPE html PUBLIC \'-//W3C//DTD HTML 4.0//EN\'>',
          { name: 'html',
            publicId: '-//W3C//DTD HTML 4.0//EN'});
  succeed('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">',
          { name: 'html',
            publicId: '-//W3C//DTD HTML 4.0//EN',
            systemId: 'http://www.w3.org/TR/REC-html40/strict.dtd'});
  succeed('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0//EN" \'http://www.w3.org/TR/REC-html40/strict.dtd\'>',
          { name: 'html',
            publicId: '-//W3C//DTD HTML 4.0//EN',
            systemId: 'http://www.w3.org/TR/REC-html40/strict.dtd'});
  succeed('<!DOCTYPE html public \'-//W3C//DTD HTML 4.0//EN\' \'http://www.w3.org/TR/REC-html40/strict.dtd\'>',
          { name: 'html',
            publicId: '-//W3C//DTD HTML 4.0//EN',
            systemId: 'http://www.w3.org/TR/REC-html40/strict.dtd'});
  succeed('<!DOCTYPE html public \'-//W3C//DTD HTML 4.0//EN\'\t\'http://www.w3.org/TR/REC-html40/strict.dtd\'   >',
          { name: 'html',
            publicId: '-//W3C//DTD HTML 4.0//EN',
            systemId: 'http://www.w3.org/TR/REC-html40/strict.dtd'});
  fatal('<!DOCTYPE html public \'-//W3C//DTD HTML 4.0//EN\' \'http://www.w3.org/TR/REC-html40/strict.dtd\'');
  fatal('<!DOCTYPE html public \'-//W3C//DTD HTML 4.0//EN\' \'http://www.w3.org/TR/REC-html40/strict.dtd\'');
  fatal('<!DOCTYPE html public \'-//W3C//DTD HTML 4.0//EN\' \'http://www.w3.org/TR/REC-html40/strict.dtd');
  fatal('<!DOCTYPE html public \'-//W3C//DTD HTML 4.0//EN\' \'');
  fatal('<!DOCTYPE html public \'-//W3C//DTD HTML 4.0//EN\' ');
  fatal('<!DOCTYPE html public \'- ');
  fatal('<!DOCTYPE html public>');
  fatal('<!DOCTYPE html public "-//W3C//DTD HTML 4.0//EN""http://www.w3.org/TR/REC-html40/strict.dtd">');
});

Tinytest.add("html-tools - tokenize", function (test) {

  var fatal = function (input, messageContains) {
    var error;
    try {
      tokenize(input);
    } catch (e) {
      error = e;
    }
    test.isTrue(error);
    if (messageContains)
      test.isTrue(error.message.indexOf(messageContains) >= 0, error.message);
  };

  test.equal(tokenize(''), []);
  test.equal(tokenize('abc'), [{t: 'Chars', v: 'abc'}]);
  test.equal(tokenize('&'), [{t: 'Chars', v: '&'}]);
  test.equal(tokenize('&'), [{t: 'CharRef', v: '&', cp: [38]}]);
  test.equal(tokenize('ok fine'),
             [{t: 'Chars', v: 'ok'},
              {t: 'CharRef', v: ' ', cp: [32]},
              {t: 'Chars', v: 'fine'}]);

  test.equal(tokenize('ac'),
             [{t: 'Chars',
               v: 'a'},
              {t: 'Comment',
               v: 'b'},
              {t: 'Chars',
               v: 'c'}]);

  test.equal(tokenize(''), [{t: 'Tag', n: 'a'}]);

  fatal('<');
  fatal('<x');
  fatal('<x ');
  fatal('<x a');
  fatal('<x a ');
  fatal('<x a =');
  fatal('<x a = ');
  fatal('<x a = b');
  fatal('<x a = "b');
  fatal('<x a = \'b');
  fatal('<x a = b ');
  fatal('<x a = b /');
  test.equal(tokenize('<x a = b />'),
             [{t: 'Tag', n: 'x',
               attrs: { a: [{t: 'Chars', v: 'b'}] },
               isSelfClosing: true}]);

  test.equal(tokenize('X'),
             [{t: 'Tag', n: 'a'},
              {t: 'Chars', v: 'X'},
              {t: 'Tag', n: 'a', isEnd: true}]);

  fatal('<x a a>'); // duplicate attribute value
  test.equal(tokenize(''),
             [{t: 'Tag', n: 'a', attrs: { b: [] }}]);
  fatal('');
  fatal('');
  fatal('');

  // Slash does not end an unquoted attribute, interestingly
  test.equal(tokenize(''),
             [{t: 'Tag', n: 'a', attrs: { b: [{t: 'Chars', v: '/'}] }}]);

  test.equal(tokenize(''),
             [{t: 'Tag', n: 'a',
               attrs: { b: [{t: 'Chars', v: 'c'}],
                        d: [{t: 'Chars', v: 'e'}],
                        f: [{t: 'Chars', v: 'g'}],
                        h: [] }}]);

  fatal('');
  fatal('');
  fatal('');

  test.equal(tokenize(''), [{t: 'Tag', n: 'a', isSelfClosing: true}]);

  fatal('');
  fatal('');
  fatal('');
  fatal('<a b=c<>');

  test.equal(tokenize(''),
             [{t: 'Tag', n: 'a#',
               attrs: { b0: [{t: 'Chars', v: 'c@'}],
                        d1: [{t: 'Chars', v: 'e2'}],
                        'f#': [{t: 'Chars', v: 'g  '}],
                        h: [] }}]);

  test.equal(tokenize('

'),
             [{t: 'Tag', n: 'div', attrs: { 'class': [] }},
              {t: 'Tag', n: 'div', isEnd: true}]);

  test.equal(tokenize('
'),
             [{t: 'Tag', n: 'div', attrs: { 'class': [{t: 'Chars', v: '&'}] }}]);
  test.equal(tokenize('
'),
             [{t: 'Tag', n: 'div', attrs: { 'class': [{t: 'Chars', v: '&'}] }}]);
  test.equal(tokenize('
'),
             [{t: 'Tag', n: 'div', attrs: { 'class': [{t: 'CharRef', v: '&', cp: [38]}] }}]);

  test.equal(tokenize('
'),
             [{t: 'Tag', n: 'div', attrs: { 'class': [
               {t: 'Chars', v: 'aa&'},
               {t: 'CharRef', v: '𝕫', cp: [120171]},
               {t: 'CharRef', v: '∾̳', cp: [8766, 819]},
               {t: 'Chars', v: '&bb'}
             ] }}]);

  test.equal(tokenize('
'),
             [{t: 'Tag', n: 'div', attrs: { 'class': [
               {t: 'Chars', v: 'aa &'},
               {t: 'CharRef', v: '𝕫', cp: [120171]},
               {t: 'CharRef', v: '∾̳', cp: [8766, 819]},
               {t: 'Chars', v: '& bb'}
             ] }}]);

  test.equal(tokenize('<a b="\'`<>&">'),
             [{t: 'Tag', n: 'a', attrs: { b: [{t: 'Chars', v: '\'`<>&'}] }}]);
  test.equal(tokenize('<a b=\'"`<>&\'>'),
             [{t: 'Tag', n: 'a', attrs: { b: [{t: 'Chars', v: '"`<>&'}] }}]);

  fatal('&gt');
  fatal('&gtc');
  test.equal(tokenize(''),
             [{t: 'Tag', n: 'a', attrs: { b: [{t: 'Chars', v: '&gtc' }] }}]);
  test.equal(tokenize(''),
             [{t: 'Tag', n: 'a', attrs: { b: [{t: 'Chars', v: '&gtc' }] }}]);
  fatal('');
  fatal('');
  fatal('');

  fatal('<!');
  fatal('<!x>');

  fatal('');
  fatal('<{{a}}>');
  fatal(''); // end tag can't have attributes
  fatal(''); // end tag can't be self-closing
  fatal('');
});


html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, font, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td {
  margin: 0;
  padding: 0;
  border: 0;
  outline: 0;
  font-weight: inherit;
  font-style: inherit;
  font-size: 100%;
  font-family: inherit;
  vertical-align: baseline;
}
body {
  line-height: 1;
  color: black;
  background: white;
}
ol, ul {
  list-style: none;
}
a img {
  border: none;
}


" => If your app contains binary code, it may break terribly and you will be sad.\n\n");


<template name="pkg_stylus">
{{#markdown}}
## `stylus`

[Stylus](http://learnboost.github.com/stylus/) is a CSS pre-processor with a simple syntax and expressive
dynamic behavior. It allows for more compact stylesheets and
helps reduce code duplication in CSS files.

With the `stylus` package installed, `.styl` files in your application are
automatically compiled to CSS and the results are included in the client
CSS bundle.

The `stylus` package also includes `nib` support. Add `@import 'nib'` to
your `.styl` files to enable cross-browser mixins such as
`linear-gradient` and `border-radius`.

{{#note}}
If you want to `@import` a file, give it the extension `.import.styl`
to prevent Meteor from processing it independently.
{{/note}}

See <http://visionmedia.github.com/nib> for documentation of the nib extensions of Stylus.

{{/markdown}}
</template>


var buildOptions = {
minify: ! options.debug,
serverArch: buildArch
};
var deployResult;
if (useGalaxy) {

serverArch: buildArch,
webArchs: webArchs


Package.describe({
  version: "1.0.0"
});


Package.describe({
  summary: "Configure content security policies",
  version: "1.0.0"
});

Package.on_use(function (api) {
  api.imply(["browser-policy-common"], "server");
  api.add_files("browser-policy-content.js", "server");
  api.use(["underscore", "browser-policy-common", "webapp"], "server");
});


Package.describe({
  summary: "Extended and Extensible JSON library",
  version: '1.0.0'
});

Package.on_use(function (api) {
  api.use(['json', 'underscore']);
  api.export('EJSON');
  api.export('EJSONTest', {testOnly: true});
  api.add_files('ejson.js', ['client', 'server']);
  api.add_files('stringify.js', ['client', 'server']);
  api.add_files('base64.js', ['client', 'server']);
});

Package.on_test(function (api) {
  api.use('ejson', ['client', 'server']);
  api.use(['tinytest', 'underscore']);

  api.add_files('custom_models_for_tests.js', ['client', 'server']);
  api.add_files('base64_test.js', ['client', 'server']);
  api.add_files('ejson_test.js', ['client', 'server']);
});


// Returns the file path to the .meteor/cordova-plugins file, containing the

// Cordova plugins dependencies for this specific project.

return path.join(self.rootDir, '.meteor', 'cordova-plugins');

keep keep keep keep add keep keep keep keep keep

// Returns the set of web archs that are targeted by the project

if (! _.isEmpty(self.getCordovaPlatforms())) {


Facebook = {};

var querystring = Npm.require('querystring');

OAuth.registerService('facebook', 2, null, function(query) {

  var response = getTokenResponse(query);
  var accessToken = response.accessToken;
  var identity = getIdentity(accessToken);

  var serviceData = {
    accessToken: accessToken,
    expiresAt: (+new Date) + (1000 * response.expiresIn)
  };

  // include all fields from facebook
  // http://developers.facebook.com/docs/reference/login/public-profile-and-friend-list/
  var whitelisted = ['id', 'email', 'name', 'first_name',
      'last_name', 'link', 'username', 'gender', 'locale', 'age_range'];

  var fields = _.pick(identity, whitelisted);
  _.extend(serviceData, fields);

  return {
    serviceData: serviceData,
    options: {profile: {name: identity.name}}
  };
});

// checks whether a string parses as JSON
var isJSON = function (str) {
  try {
    JSON.parse(str);
    return true;
  } catch (e) {
    return false;
  }
};

// returns an object containing:
// - accessToken
// - expiresIn: lifetime of token in seconds
var getTokenResponse = function (query) {
  var config = ServiceConfiguration.configurations.findOne({service: 'facebook'});
  if (!config)
    throw new ServiceConfiguration.ConfigError();

  var responseContent;
  try {
    // Request an access token
    responseContent = HTTP.get(
      "https://graph.facebook.com/oauth/access_token", {
        params: {
          client_id: config.appId,
          redirect_uri: Meteor.absoluteUrl("_oauth/facebook?close"),
          client_secret: OAuth.openSecret(config.secret),
          code: query.code
        }
      }).content;
  } catch (err) {
    throw _.extend(new Error("Failed to complete OAuth handshake with Facebook. " + err.message),
                   {response: err.response});
  }

  // If 'responseContent' parses as JSON, it is an error.
  // XXX which facebook error causes this behvaior?
  if (isJSON(responseContent)) {
    throw new Error("Failed to complete OAuth handshake with Facebook. " + responseContent);
  }

  // Success!  Extract the facebook access token and expiration
  // time from the response
  var parsedResponse = querystring.parse(responseContent);
  var fbAccessToken = parsedResponse.access_token;
  var fbExpires = parsedResponse.expires;

  if (!fbAccessToken) {
    throw new Error("Failed to complete OAuth handshake with facebook " +
                    "-- can't find access token in HTTP response. " + responseContent);
  }
  return {
    accessToken: fbAccessToken,
    expiresIn: fbExpires
  };
};

var getIdentity = function (accessToken) {
  try {
    return HTTP.get("https://graph.facebook.com/me", {
      params: {access_token: accessToken}}).data;
  } catch (err) {
    throw _.extend(new Error("Failed to fetch identity from Facebook. " + err.message),
                   {response: err.response});
  }
};

Facebook.retrieveCredential = function(credentialToken, credentialSecret) {
  return OAuth.retrieveCredential(credentialToken, credentialSecret);
};


// Fiber-aware implementation of dynamic scoping, for use on the server

var Fiber = Npm.require('fibers');

var nextSlot = 0;

Meteor._nodeCodeMustBeInFiber = function () {
  if (!Fiber.current) {
    throw new Error("Meteor code must always run within a Fiber. " +
                    "Try wrapping callbacks that you pass to non-Meteor " +
                    "libraries with Meteor.bindEnvironment.");
  }
};

Meteor.EnvironmentVariable = function () {
  this.slot = nextSlot++;
};

_.extend(Meteor.EnvironmentVariable.prototype, {
  get: function () {
    Meteor._nodeCodeMustBeInFiber();

    return Fiber.current._meteor_dynamics &&
      Fiber.current._meteor_dynamics[this.slot];
  },

  // Most Meteor code ought to run inside a fiber, and the
  // _nodeCodeMustBeInFiber assertion helps you remember to include appropriate
  // bindEnvironment calls (which will get you the *right value* for your
  // environment variables, on the server).
  //
  // In some very special cases, it's more important to run Meteor code on the
  // server in non-Fiber contexts rather than to strongly enforce the safeguard
  // against forgetting to use bindEnvironment. For example, using `check` in
  // some top-level constructs like connect handlers without needing unnecessary
  // Fibers on every request is more important that possibly failing to find the
  // correct argumentChecker. So this function is just like get(), but it
  // returns null rather than throwing when called from outside a Fiber. (On the
  // client, it is identical to get().)
  getOrNullIfOutsideFiber: function () {
    if (!Fiber.current)
      return null;
    return this.get();
  },

  withValue: function (value, func) {
    Meteor._nodeCodeMustBeInFiber();

    if (!Fiber.current._meteor_dynamics)
      Fiber.current._meteor_dynamics = [];
    var currentValues = Fiber.current._meteor_dynamics;

    var saved = currentValues[this.slot];
    try {
      currentValues[this.slot] = value;
      var ret = func();
    } finally {
      currentValues[this.slot] = saved;
    }

    return ret;
  }
});

// Meteor application code is always supposed to be run inside a
// fiber. bindEnvironment ensures that the function it wraps is run from
// inside a fiber and ensures it sees the values of Meteor environment
// variables that are set at the time bindEnvironment is called.
//
// If an environment-bound function is called from outside a fiber (eg, from
// an asynchronous callback from a non-Meteor library such as MongoDB), it'll
// kick off a new fiber to execute the function, and returns undefined as soon
// as that fiber returns or yields (and func's return value is ignored).
//
// If it's called inside a fiber, it works normally (the
// return value of the function will be passed through, and no new
// fiber will be created.)
//
// `onException` should be a function or a string.  When it is a
// function, it is called as a callback when the bound function raises
// an exception.  If it is a string, it should be a description of the
// callback, and when an exception is raised a debug message will be
// printed with the description.
Meteor.bindEnvironment = function (func, onException, _this) {
  Meteor._nodeCodeMustBeInFiber();

  var boundValues = _.clone(Fiber.current._meteor_dynamics || []);

  if (!onException || typeof(onException) === 'string') {
    var description = onException || "callback of async function";
    onException = function (error) {
      Meteor._debug(
        "Exception in " + description + ":",
        error && error.stack || error
      );
    };
  }

  return function (/* arguments */) {
    var args = _.toArray(arguments);

    var runWithEnvironment = function () {
      var savedValues = Fiber.current._meteor_dynamics;
      try {
        // Need to clone boundValues in case two fibers invoke this
        // function at the same time
        Fiber.current._meteor_dynamics = _.clone(boundValues);
        var ret = func.apply(_this, args);
      } catch (e) {
        // note: callback-hook currently relies on the fact that if onException
        // throws and you were originally calling the wrapped callback from
        // within a Fiber, the wrapped call throws.
        onException(e);
      } finally {
        Fiber.current._meteor_dynamics = savedValues;
      }
      return ret;
    };

    if (Fiber.current)
      return runWithEnvironment();
    Fiber(runWithEnvironment).run();
  };
};


# OS Check. Put here because here is where we download the precompiled

// This file exists because it is the file in the tool that is not automatically
// transpiled by Babel

function babelRegister() {
  require("meteor-babel/register")({
    babelOptions: require("meteor-babel").getDefaultOptions(
      require("./babel-features.js")
    )
  });
}

babelRegister(); // #RemoveInProd this line is removed in isopack.js

// Install a global ES6-compliant Promise constructor that knows how to
// run all its callbacks in Fibers.
global.Promise = require("meteor-promise");

// Include helpers from NPM so that the compiler doesn't need to add boilerplate
// at the top of every file
require("meteor-babel").installRuntime();

// Installs source map support with a hook to add functions to look for source
// maps in custom places
require('./source-map-retriever-stack.js');

// Run the Meteor command line tool
require('./main.js');


// Meteor._localStorage is not an ideal name, but we can change it later.

// Let's test to make sure that localStorage actually works. For example, in
// Safari with private browsing on, window.localStorage exists but actually
// trying to use it throws.
// Accessing window.localStorage can also immediately throw an error in IE (#1291).

var key = '_localstorage_test_' + Random.id();
var retrieved;
try {
  if (window.localStorage) {
    window.localStorage.setItem(key, key);
    retrieved = window.localStorage.getItem(key);
    window.localStorage.removeItem(key);
  }
} catch (e) {
  // ... ignore
}
if (key === retrieved) {
  Meteor._localStorage = {
    getItem: function (key) {
      return window.localStorage.getItem(key);
    },
    setItem: function (key, value) {
      window.localStorage.setItem(key, value);
    },
    removeItem: function (key) {
      window.localStorage.removeItem(key);
    }
  };
}

if (!Meteor._localStorage) {
  Meteor._debug(
    "You are running a browser with no localStorage or userData "
      + "support. Logging in from one tab will not cause another "
      + "tab to be logged in.");

  Meteor._localStorage = {
    _data: {},

    setItem: function (key, val) {
      this._data[key] = val;
    },
    removeItem: function (key) {
      delete this._data[key];
    },
    getItem: function (key) {
      var value = this._data[key];
      if (value === undefined)
        return null;
      else
        return value;
    }
  };
}


// This file contains a package.json for the dependencies of the *BUNDLED
// SERVER* (not the command-line tool).

// We put this in a JS file so that it can contain comments. It is processed
// into a package.json file by generate-dev-bundle.sh.

var fibersVersion;
if (process.platform === "win32") {
  // We have a fork of fibers off of version 1.0.5 that searches farther for
  // the isolate thread. This problem is a result of antivirus programs messing
  // with the thread counts on Windows.
  // Duplicated in dev-bundle-tool-package.js
  fibersVersion = "https://github.com/meteor/node-fibers/tarball/d519f0c5971c33d99c902dad346b817e84bab001";
} else {
  fibersVersion = "1.0.5";
}

var packageJson = {
  name: "meteor-dev-bundle",
  // Version is not important but is needed to prevent warnings.
  version: "0.0.0",
  dependencies: {
    fibers: fibersVersion,
    "meteor-promise": "0.2.4",
    // Not yet upgrading Underscore from 1.5.2 to 1.7.0 (which should be done
    // in the package too) because we should consider using lodash instead
    // (and there are backwards-incompatible changes either way).
    underscore: "1.5.2",
    "source-map-support": "0.3.1",
    semver: "4.1.0"
  },
  // These are only used in dev mode (by shell.js) so end-users can avoid
  // needing to install them if they use `npm install --production`.
  devDependencies: {
    // 2.4.0 (more or less, the package.json change isn't committed) plus our PR
    // https://github.com/williamwicks/node-eachline/pull/4
    eachline: "https://github.com/meteor/node-eachline/tarball/ff89722ff94e6b6a08652bf5f44c8fffea8a21da",
    chalk: "0.5.1"
  }
};

process.stdout.write(JSON.stringify(packageJson, null, 2) + '\n');


// This is a replica of the core package-stats-opt-out package. It
// exists to make it so that we can add package-stats-opt-out to this
// app without needing to be using a release that has core packages in
// it (such as using a sandbox created with a `warehouse` argument).

Package.describe({
  summary: "a replica of a core package",
  version: "1.0.0"
});


// For Map and Set polyfills.
"core-js": "0.9.18",

// This file contains a package.json for the dependencies of the command-line
// tool.

// We put this in a JS file so that it can contain comments. It is processed
// into a package.json file by generate-dev-bundle.sh.

var fibersVersion;
if (process.platform === "win32") {
  // We have a fork of fibers off of version 1.0.5 that searches farther for
  // the isolate thread. This problem is a result of antivirus programs messing
  // with the thread counts on Windows.
  // Duplicated in dev-bundle-server-package.js
  fibersVersion = "https://github.com/meteor/node-fibers/tarball/d519f0c5971c33d99c902dad346b817e84bab001";
} else {
  fibersVersion = "1.0.5";
}

var packageJson = {
  name: "meteor-dev-bundle-tool",
  // Version is not important but is needed to prevent warnings.
  version: "0.0.0",
  dependencies: {
    fibers: fibersVersion,
    "meteor-babel": "0.3.4",
    "meteor-promise": "0.2.4",
    // Not yet upgrading Underscore from 1.5.2 to 1.7.0 (which should be done
    // in the package too) because we should consider using lodash instead
    // (and there are backwards-incompatible changes either way).
    underscore: "1.5.2",
    "source-map-support": "0.3.1",
    semver: "4.1.0",
    request: "2.47.0",
    fstream: "https://github.com/meteor/fstream/tarball/d11b9ec4a13918447c8af7559c243c190744dd1c",
    tar: "1.0.2",
    kexec: "0.2.0",
    "source-map": "0.1.40",
    "browserstack-webdriver": "2.41.1",
    "node-inspector": "0.7.4",
    chalk: "0.5.1",
    sqlite3: "3.0.2",
    netroute: "0.2.5",
    phantomjs: "1.9.12",
    "http-proxy": "1.11.1",
    "wordwrap": "0.0.2",
    "moment": "2.8.4",
    "rimraf": "2.2.8",
    // XXX: When we update this, see if it fixes this Github issue:
    // https://github.com/jgm/CommonMark/issues/276 . If it does, remove the
    // workaround from the tool.
    "commonmark": "0.15.0",
    // XXX We ought to be able to get this from the copy in js-analyze rather
    // than in the dev bundle.)
    esprima: "1.2.2",
    // 2.4.0 (more or less, the package.json change isn't committed) plus our PR
    // https://github.com/williamwicks/node-eachline/pull/4
    eachline: "https://github.com/meteor/node-eachline/tarball/ff89722ff94e6b6a08652bf5f44c8fffea8a21da",
    cordova: "5.0.0",
    pathwatcher: "4.1.0"
  }
};

if (process.platform === 'win32') {
  // Cordova is not supported on Windows
  delete packageJson.dependencies.cordova;
  // netroute is only needed for Cordova support
  delete packageJson.dependencies.netroute;
  // kexec doesn't work on Windows
  delete packageJson.dependencies.kexec;
}

process.stdout.write(JSON.stringify(packageJson, null, 2) + '\n');


#!/usr/bin/env node

/// # doctool.js
///
/// Usage: `doctool.js ...jsfiles...`
///
/// Reads each `.js` file and writes a `.md` file in the same directory.
/// The output file consists of the concatenation of the "doc comments"
/// in the input file, which are assumed to contain Markdown content,
/// including any section headings necessary to organize the file.
///
/// A "doc comment" must begin at the start of a line or after
/// whitespace.  There are two kinds of doc comments: `/** ... */`
/// (block) comments and `/// ...` (triple-slash) comments.
///
/// If a file begins with the magic string "///!README", the output
/// filename is changed to `README.md`.
///
/// Examples:
///
/// ```
/// /**
///  * This is a block comment.  The parser strips the sequence,
///  * [optional whitespace, `*`, optional single space] from
///  * every line that has it.
///  *
/// For lines that don't, no big deal.
///
///     Leading whitespace will be preserved here.
///
///  * We can create a bullet list in here:
///  *
///  * * This is a bullet
///  */
/// ```
///
/// ```
/// /** Single-line block comments are also ok. */
/// ```
///
/// ```
/// /**
/// A block comment whose first line doesn't have a `*` receives
/// no stripping of `*` characters on any line.
///
/// * This is a bullet
///
/// */
/// ```
///
/// ```
/// /// A triple-slash comment starts with `///` followed by an
/// /// optional space (i.e. one space is removed if present).
/// /// Multiple consecutive lines that start with `///` are
/// /// treated together as a single doc comment.
/// /** Separate doc comments get separate paragraphs. */
/// ```

var fs = require('fs');
var path = require('path');

process.argv.slice(2).forEach(function (fileName) {
  var text = fs.readFileSync(fileName, "utf8");

  var outFileName = fileName.replace(/\.js$/, '') + '.md';
  if (text.slice(0, 10) === '///!README') {
    outFileName = path.join(path.dirname(fileName), 'README.md');
    text = text.slice(10);
  }

  var docComments = [];
  for (;;) {
    // This regex breaks down as follows:
    //
    // 1. Start of line
    // 2. Optional whitespace (not newline!)
    // 3. `///` (capturing group 1) or `/**` (group 2)
    // 4. Looking ahead, NOT `/` or `*`
    var nextOpener = /^[ \t]*(?:(\/\/\/)|(\/\*\*))(?![\/\*])/m.exec(text);
    if (! nextOpener)
      break;
    text = text.slice(nextOpener.index + nextOpener[0].length);
    if (nextOpener[1]) {
      // triple-slash
      text = text.replace(/^[ \t]/, ''); // optional space
      var comment = text.match(/^[^\n]*/)[0];
      text = text.slice(comment.length);
      var match;
      while ((match = /^\n[ \t]*\/\/\/[ \t]?/.exec(text))) {
        // multiple lines in a row become one comment
        text = text.slice(match[0].length);
        var restOfLine = text.match(/^[^\n]*/)[0];
        text = text.slice(restOfLine.length);
        comment += '\n' + restOfLine;
      }
      if (comment.trim())
        docComments.push(['///', comment]);
    } else if (nextOpener[2]) {
      // block comment
      var rawComment = text.match(/^[\s\S]*?\*\//);
      if ((! rawComment) || (! rawComment[0]))
        continue;
      rawComment = rawComment[0];
      text = text.slice(rawComment.length);
      rawComment = rawComment.slice(0, -2); // remove final `*/`
      if (rawComment.slice(-1) === ' ')
        // make that ' */' for the benefit of single-line blocks
        rawComment = rawComment.slice(0, -1);

      var lines = rawComment.split('\n');

      var stripStars = false;
      if (lines[0].trim().length === 0) {
        // The comment has a newline after the `/**` (with possible whitespace
        // between).  This is like most comments, though occasionally people
        // may write `/** foo */` on one line.  Skip the blank line.
        lines.splice(0, 1);
        if (! lines.length)
          continue;
        // Now we determine whether this is block comment with a column of
        // asterisks running down the left side, so we can strip them.
        stripStars = /^[ \t]*\*/.test(lines[1]);
      } else {
        // Trim beginning of line after `/**`
        lines[0] = lines[0].replace(/^\s*/, '');
      }

      lines = lines.map(function (s) {
        // Strip either up to an asterisk and then an optional space,
        // or just an optional space, depending on `stripStars`.
        if (stripStars)
          return s.replace(/^[ \t]*\* ?/, '');
        else
          return s;
      });

      var result = lines.join('\n');

      if (result.trim())
        docComments.push(['/**', result]);
    }
  }

  if (docComments.length) {
    var output = docComments.map(function (x) { return x[1]; }).join('\n\n');
    var fileShortName = path.basename(fileName);
    output = '*This file is automatically generated from [`' +
      fileShortName + '`](' + fileShortName + ').*\n\n' + output;
    fs.writeFileSync(outFileName, output, 'utf8');
    console.log("Wrote " + docComments.length + " comments to " + outFileName);
  }

});


====================================================================
For the license for Meteor itself, see LICENSE.txt in the root of
the repository.  This file contains the licenses for externally
maintained libraries.
====================================================================

----------
unorm: https://github.com/walling/unorm
----------

The software dual licensed under the MIT and GPL licenses. MIT license:

Copyright (c) 2008-2013 Matsuza <matsuza@gmail.com>, Bjarke Walling <bwp@bwp.dk>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
GPL notice (please read the full GPL license online):

Copyright (C) 2008-2013 Matsuza <matsuza@gmail.com>, Bjarke Walling <bwp@bwp.dk>

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.


babelRegister(); // #RemoveInProd this line is removed in isopack.js

// Install a global ES6-compliant Promise constructor that knows how to

// Install a global ES2015-compliant Promise constructor that knows how to