var path = require('path');
var _ = require('underscore');
var watch = require('./watch.js');
var buildmessage = require('./buildmessage.js');
var archinfo = require(path.join(__dirname, 'archinfo.js'));
var linker = require('./linker.js');
var Unipackage = require('./unipackage.js').Unipackage;
var packageLoader = require('./package-loader.js');
var uniload = require('./uniload.js');
var bundler = require('./bundler.js');
var catalog = require('./catalog.js');
var utils = require('./utils.js');
var meteorNpm = require('./meteor-npm.js');
var release = require('./release.js');

var compiler = exports;

// Whenever you change anything about the code that generates unipackages, bump
// this version number. The idea is that the "format" field of the unipackage
// JSON file only changes when the actual specified structure of the
// unipackage/unibuild changes, but this version (which is build-tool-specific)
// can change when the the contents (not structure) of the built output
// changes. So eg, if we improve the linker's static analysis, this should be
// bumped.
//
// You should also update this whenever you update any of the packages used
// directly by the unipackage creation process (eg js-analyze) since they do not
// end up as watched dependencies. (At least for now, packages only used in
// target creation (eg minifiers and dev-bundle-fetcher) don't require you to
// update BUILT_BY, though you will need to quit and rerun "meteor run".)
compiler.BUILT_BY = 'meteor/13';

// XXX where should this go? I'll make it a random utility function
// for now
//
// 'dependencies' is the 'uses' attribute from a Unibuild. Call
// 'callback' with each unibuild (of architecture matching `arch`)
// referenced by that dependency list. This includes directly used
// unibuilds, and unibuilds that are transitively "implied" by used
// unibuilds. (But not unibuilds that are used by unibuilds that we use!)
//
// Options are:
//  - skipUnordered: ignore direct dependencies that are unordered
//  - acceptableWeakPackages: if set, include direct weak dependencies
//    that are on one of these packages (it's an object mapping
//    package name -> true). Otherwise skip all weak dependencies.
//  - constraintSolverOpts
//
// (Why does we need to list acceptable weak packages here rather than just
// implement a skipWeak flag and allow the caller to filter the ones they care
// about? Well, we want to avoid even calling packageLoader.getUnibuild on
// dependencies that aren't going to get included, because in the uniload case,
// the weak dependency might not even be there at all.)
//
// packageLoader is the PackageLoader that should be used to resolve
// the dependencies.
//
// Note that you generally want 'arch' to be a 'global' architecture describing
// the whole build (eg, bundle) that you're doing, not the specific architecture
// of the precise place you're calling this function from.  eg, if you're doing
// a bundle targeting os.osx.x86_64, you really want to always pass
// "os.osx.x86_64" even if the eachUsedUnibuild is inside (eg) the compilation
// of an "os" arch.  Otherwise you won't be able to find any unibuilds for
// packages that only have OS-specific os arches!
compiler.eachUsedUnibuild = function (
    dependencies, arch, packageLoader, options, callback) {
  if (typeof options === "function") {
    callback = options;
    options = {};
  }
  var acceptableWeakPackages = options.acceptableWeakPackages || {};

  var processedBuildId = {};
  var usesToProcess = [];
  _.each(dependencies, function (use) {
    if (options.skipUnordered && use.unordered)
      return;
    if (use.weak && !_.has(acceptableWeakPackages, use.package))
      return;
    usesToProcess.push(use);
  });

  while (!_.isEmpty(usesToProcess)) {
    var use = usesToProcess.shift();

    var unibuild = packageLoader.getUnibuild(use.package, arch);
    if (!unibuild) {
      // The package exists but there's no unibuild for us. A buildmessage has
      // already been issued. Recover by skipping.
      continue;
    }

    if (_.has(processedBuildId, unibuild.id))
      continue;
    processedBuildId[unibuild.id] = true;

    callback(unibuild, {
      unordered: !!use.unordered,
      weak: !!use.weak
    });

    _.each(unibuild.implies, function (implied) {
      usesToProcess.push(implied);
    });
  }
};

// Pick a set of versions to use to satisfy a package's build-time
// dependencies. Emits buildmessages if this is impossible.
//
// Output is an object with keys:
// - directDependencies: map from package name to version string, for the
//   package's direct, ordered, strong, non-implied dependencies.
// - pluginDependencies: map from plugin name to complete (transitive)
//   version information for all packages used to build the plugin, as
//   a map from package name to version string.
// - packageDependencies: map from package name to version string to complete
//   transitive dependency in this package. We need for the version lock file
//   and to deal with implies.
//
// XXX You may get different results from this function depending on
// when you call it (if, for example, the packages in the catalog
// change). Should we have some kind of analog to .meteor/versions and
// 'meteor update' for package build-time dependencies?
//
// XXX deal with _makeBuildTimePackageLoader callsites
//
// XXX this function is probably going to get called a huge number of
// times. For example, the Catalog calls it on every local package
// every time the local package list changes. We could memoize the
// result on packageSource (and presumably make this a method on
// PackgeSource), or we could have some kind of cache (the ideal place
// for such a cache might be inside the constraint solver, since it
// will know how/when to invalidate it).
var determineBuildTimeDependencies = function (packageSource,
                                               constraintSolverOpts) {
  var ret = {};
  constraintSolverOpts = constraintSolverOpts || {};
  buildmessage.assertInCapture();

  // There are some special cases where we know that the package has no source
  // files, which means it can't have any interesting build-time
  // dependencies. Specifically, the top-level wrapper package used by uniload
  // (via bundler.buildJsImage) works this way. This early return avoid calling
  // the constraint solver for uniload, which makes sense because it's supposed
  // to only look at the prebuilt packages.
  if (packageSource.noSources)
    return ret;

  // XXX If in any of these cases the constraint solver fails to find
  // a solution, we should emit a nice buildmessage and maybe find a
  // way to continue. For example, the pre-constraint-solver version
  // of this code had logic to detect if the user asked for a package
  // that just doesn't exist, and emit a message about that and then
  // continue with the build ignoring that dependency. It also had
  // code to do this for implies.

  // -- Direct & package dependencies --

  var dependencyMetadata =
    packageSource.getDependencyMetadata({
      logError: true,
      skipWeak: true,
      skipUnordered: true
    });

  if (! dependencyMetadata) {
    // If _computeDependencyMetadata failed, I guess we can try to
    // recover by returning an empty thing with no versions in
    // it. This will cause a lot of 'package not found' errors, so a
    // better approach would proabably be to actually have this
    // function return null and make the caller do a better job of
    // recovering.
    return ret;
  }

  var constraints = {};
  var constraints_array = [];
  _.each(dependencyMetadata, function (info, packageName) {
    constraints[packageName] = info.constraint;
    var version = null;
    if (info.constraint) {
      version =  utils.parseVersionConstraint(info.constraint) ;
    }
    constraints_array.push(_.extend({ packageName: packageName }, version));
  });

  var versions = packageSource.dependencyVersions.dependencies || {};
  ret.packageDependencies =
        catalog.complete.resolveConstraints(
             constraints_array,
             { previousSolution: versions },
             constraintSolverOpts);

  // We care about differentiating between all dependencies (which we save in
  // the version lock file) and the direct dependencies (which are packages that
  // we are exactly using) in order to optimize build id generation.
  ret.directDependencies = {};
  _.each(  ret.packageDependencies, function (version, packageName) {
    // Take only direct dependencies.
    if (_.has(constraints, packageName)) {
      ret.directDependencies[packageName] = version;
    }
  });

  // -- Dependencies of Plugins --

  ret.pluginDependencies = {};
  var pluginVersions = packageSource.dependencyVersions.pluginDependencies;
  _.each(packageSource.pluginInfo, function (info) {
    var constraints = {};
    var constraints_array = [];

    // info.uses is currently just an array of strings, and there's
    // no way to specify weak/unordered. Much like an app.
    _.each(info.use, function (spec) {
      var parsedSpec = utils.splitConstraint(spec);
      constraints[parsedSpec.package] = parsedSpec.constraint || null;
      var version = null;
      if (parsedSpec.constraint) {
        version =  utils.parseVersionConstraint(info.constraint) ;
      }
      constraints_array.push({packageName: parsedSpec.package,
                              version: version });
    });

    var pluginVersion = pluginVersions[info.name] || {};
    ret.pluginDependencies[info.name] =
      catalog.complete.resolveConstraints(
         constraints_array,
         { previousSolution: pluginVersion },
         constraintSolverOpts );
  });

  // Every time we run the constraint solver, we record the results. This has
  // two benefits -- first, it faciliatates repeatable builds, second,
  // memorizing results makes the constraint solver more efficient.
  if (ret.packageDependencies) {
    var constraintResults = {
      dependencies: ret.packageDependencies,
      pluginDependencies: ret.pluginDependencies
    };

    packageSource.recordDependencyVersions(
      constraintResults,
      release.current.getCurrentToolsVersion());
  }

  return ret;
};

compiler.determineBuildTimeDependencies = determineBuildTimeDependencies;

// inputSourceArch is a SourceArch to compile. Process all source files through
// the appropriate handlers and run the prelink phase on any resulting
// JavaScript. Create a new Unibuild and add it to 'unipackage'.
//
// packageLoader is a PackageLoader that can load our build-time
// direct dependencies at the correct versions. It is only used to
// load plugins so it does not need to be able to (and arguably should
// not be able to) load transitive dependencies of those packages.
//
// Returns a list of source files that were used in the compilation.
var compileUnibuild = function (unipackage, inputSourceArch, packageLoader,
                                nodeModulesPath, isPortable) {
  var isApp = ! inputSourceArch.pkg.name;
  var resources = [];
  var js = [];
  var sources = [];
  var watchSet = inputSourceArch.watchSet.clone();

  // Download whatever packages we may need for this compilation. (Since we're
  // compiling, we're always targeting the host; we can cross-link but we can't
  // cross-compile!)
  packageLoader.downloadMissingPackages({serverArch: archinfo.host()});

  // *** Determine and load active plugins

  // XXX we used to include our own extensions only if we were the
  // "use" role. now we include them everywhere because we don't have
  // a special "use" role anymore. it's not totally clear to me what
  // the correct behavior should be -- we need to resolve whether we
  // think about extensions as being global to a package or particular
  // to a unibuild.

  // (there's also some weirdness here with handling implies, because
  // the implies field is on the target unibuild, but we really only care
  // about packages.)
  var activePluginPackages = [unipackage];

  // We don't use plugins from weak dependencies, because the ability
  // to compile a certain type of file shouldn't depend on whether or
  // not some unrelated package in the target has a dependency. And we
  // skip unordered dependencies, because it's not going to work to
  // have circular build-time dependencies.
  //
  // Note that we avoid even calling containsPlugins if we know there are no
  // sources; specifically, this avoids calling containsPlugins in the uniload
  // case because uniload doesn't know how to check to see if a package has
  // plugins.
  //
  // eachUsedUnibuild takes care of pulling in implied dependencies for us (eg,
  // templating from standard-app-packages).
  //
  // We pass archinfo.host here, not self.arch, because it may be more specific,
  // and because plugins always have to run on the host architecture.
  if (!inputSourceArch.noSources) {
    compiler.eachUsedUnibuild(
      inputSourceArch.uses, archinfo.host(),
      packageLoader, {skipUnordered: true}, function (unibuild) {
        if (unibuild.pkg.name === unipackage.name)
          return;
        if (_.isEmpty(unibuild.pkg.plugins))
          return;
        activePluginPackages.push(unibuild.pkg);
      });
  }

  activePluginPackages = _.uniq(activePluginPackages);

  // *** Assemble the list of source file handlers from the plugins
  var allHandlers = {};
  var sourceExtensions = {};  // maps source extensions to isTemplate

  sourceExtensions['js'] = false;
  allHandlers['js'] = function (compileStep) {
    // This is a hardcoded handler for *.js files. Since plugins
    // are written in JavaScript we have to start somewhere.
    compileStep.addJavaScript({
      data: compileStep.read().toString('utf8'),
      path: compileStep.inputPath,
      sourcePath: compileStep.inputPath,
      // XXX eventually get rid of backward-compatibility "raw" name
      // XXX COMPAT WITH 0.6.4
      bare: compileStep.fileOptions.bare || compileStep.fileOptions.raw
    });
  };

  _.each(activePluginPackages, function (otherPkg) {
    _.each(otherPkg.getSourceHandlers(), function (sourceHandler, ext) {
      // XXX comparing function text here seems wrong.
      if (_.has(allHandlers, ext) &&
          allHandlers[ext].toString() !== sourceHandler.handler.toString()) {
        buildmessage.error(
          "conflict: two packages included in " +
            (inputSourceArch.pkg.name || "the app") + ", " +
            (allHandlers[ext].pkg.name || "the app") + " and " +
            (otherPkg.name || "the app") + ", " +
            "are both trying to handle ." + ext);
        // Recover by just going with the first handler we saw
        return;
      }
      // Is this handler only registered for, say, "web", and we're building,
      // say, "os"?
      if (sourceHandler.archMatching &&
          !archinfo.matches(inputSourceArch.arch, sourceHandler.archMatching)) {
        return;
      }
      allHandlers[ext] = sourceHandler.handler;
      sourceExtensions[ext] = !!sourceHandler.isTemplate;
    });
  });

  // *** Determine source files
  // Note: sourceExtensions does not include leading dots
  // Note: the getSourcesFunc function isn't expected to add its
  // source files to watchSet; rather, the watchSet is for other
  // things that the getSourcesFunc consulted (such as directory
  // listings or, in some hypothetical universe, control files) to
  // determine its source files.
  var sourceItems = inputSourceArch.getSourcesFunc(sourceExtensions, watchSet);

  if (nodeModulesPath) {
    // If this slice has node modules, we should consider the shrinkwrap file
    // to be part of its inputs. (This is a little racy because there's no
    // guarantee that what we read here is precisely the version that's used,
    // but it's better than nothing at all.)
    //
    // Note that this also means that npm modules used by plugins will get
    // this npm-shrinkwrap.json in their pluginDependencies (including for all
    // packages that depend on us)!  This is good: this means that a tweak to
    // an indirect dependency of the coffee-script npm module used by the
    // coffeescript package will correctly cause packages with *.coffee files
    // to be rebuilt.
    var shrinkwrapPath = nodeModulesPath.replace(
        /node_modules$/, 'npm-shrinkwrap.json');
    watch.readAndWatchFile(watchSet, shrinkwrapPath);
  }

  // *** Process each source file
  var addAsset = function (contents, relPath, hash) {
    // XXX hack
    if (! inputSourceArch.pkg.name)
      relPath = relPath.replace(/^(private|public)\//, '');

    resources.push({
      type: "asset",
      data: contents,
      path: relPath,
      servePath: path.join(inputSourceArch.pkg.serveRoot, relPath),
      hash: hash
    });

    sources.push(relPath);
  };

  _.each(sourceItems, function (source) {
    var relPath = source.relPath;
    var fileOptions = _.clone(source.fileOptions) || {};
    var absPath = path.resolve(inputSourceArch.pkg.sourceRoot, relPath);
    var filename = path.basename(relPath);
    var file = watch.readAndWatchFileWithHash(watchSet, absPath);
    var contents = file.contents;

    sources.push(relPath);

    if (contents === null) {
      buildmessage.error("File not found: " + source.relPath);
      // recover by ignoring
      return;
    }

    // Find the handler for source files with this extension.
    var handler = null;
    if (! fileOptions.isAsset) {
      var parts = filename.split('.');
      for (var i = 0; i < parts.length; i++) {
        var extension = parts.slice(i).join('.');
        if (_.has(allHandlers, extension)) {
          handler = allHandlers[extension];
          break;
        }
      }
    }

    if (! handler) {
      // If we don't have an extension handler, serve this file as a
      // static resource on the client, or ignore it on the server.
      //
      // XXX This is pretty confusing, especially if you've
      // accidentally forgotten a plugin -- revisit?
      addAsset(contents, relPath, file.hash);
      return;
    }

    // This object is called a #CompileStep and it's the interface
    // to plugins that define new source file handlers (eg,
    // Coffeescript).
    //
    // Fields on CompileStep:
    //
    // - arch: the architecture for which we are building
    // - inputSize: total number of bytes in the input file
    // - inputPath: the filename and (relative) path of the input
    //   file, eg, "foo.js". We don't provide a way to get the full
    //   path because you're not supposed to read the file directly
    //   off of disk. Instead you should call read(). That way we
    //   can ensure that the version of the file that you use is
    //   exactly the one that is recorded in the dependency
    //   information.
    // - pathForSourceMap: If this file is to be included in a source map,
    //   this is the name you should use for it in the map.
    // - rootOutputPath: on web targets, for resources such as
    //   stylesheet and static assets, this is the root URL that
    //   will get prepended to the paths you pick for your output
    //   files so that you get your own namespace, for example
    //   '/packages/foo'. null on non-web targets
    // - fileOptions: any options passed to "api.add_files"; for
    //   use by the plugin. The built-in "js" plugin uses the "bare"
    //   option for files that shouldn't be wrapped in a closure.
    // - declaredExports: An array of symbols exported by this unibuild, or null
    //   if it may not export any symbols (eg, test unibuilds). This is used by
    //   CoffeeScript to ensure that it doesn't close over those symbols, eg.
    // - read(n): read from the input file. If n is given it should
    //   be an integer, and you will receive the next n bytes of the
    //   file as a Buffer. If n is omitted you get the rest of the
    //   file.
    // - appendDocument({ section: "head", data: "my markup" })
    //   Browser targets only. Add markup to the "head" or "body"
    //   Web targets only. Add markup to the "head" or "body"
    //   section of the document.
    // - addStylesheet({ path: "my/stylesheet.css", data: "my css",
    //                   sourceMap: "stringified json sourcemap"})
    //   Web targets only. Add a stylesheet to the
    //   document. 'path' is a requested URL for the stylesheet that
    //   may or may not ultimately be honored. (Meteor will add
    //   appropriate tags to cause the stylesheet to be loaded. It
    //   will be subject to any stylesheet processing stages in
    //   effect, such as minification.)
    // - addJavaScript({ path: "my/program.js", data: "my code",
    //                   sourcePath: "src/my/program.js",
    //                   bare: true })
    //   Add JavaScript code, which will be namespaced into this
    //   package's environment (eg, it will see only the exports of
    //   this package's imports), and which will be subject to
    //   minification and so forth. Again, 'path' is merely a hint
    //   that may or may not be honored. 'sourcePath' is the path
    //   that will be used in any error messages generated (eg,
    //   "foo.js:4:1: syntax error"). It must be present and should
    //   be relative to the project root. Typically 'inputPath' will
    //   do handsomely. "bare" means to not wrap the file in
    //   a closure, so that its vars are shared with other files
    //   in the module.
    // - addAsset({ path: "my/image.png", data: Buffer })
    //   Add a file to serve as-is over HTTP (web targets) or
    //   to include as-is in the bundle (os targets).
    //   This time `data` is a Buffer rather than a string. For
    //   web targets, it will be served at the exact path you
    //   request (concatenated with rootOutputPath). For server
    //   targets, the file can be retrieved by passing path to
    //   Assets.getText or Assets.getBinary.
    // - error({ message: "There's a problem in your source file",
    //           sourcePath: "src/my/program.ext", line: 12,
    //           column: 20, func: "doStuff" })
    //   Flag an error -- at a particular location in a source
    //   file, if you like (you can even indicate a function name
    //   to show in the error, like in stack traces). sourcePath,
    //   line, column, and func are all optional.
    //
    // XXX for now, these handlers must only generate portable code
    // (code that isn't dependent on the arch, other than 'web'
    // vs 'os') -- they can look at the arch that is provided
    // but they can't rely on the running on that particular arch
    // (in the end, an arch-specific unibuild will be emitted only if
    // there are native node modules). Obviously this should
    // change. A first step would be a setOutputArch() function
    // analogous to what we do with native node modules, but maybe
    // what we want is the ability to ask the plugin ahead of time
    // how specific it would like to force unibuilds to be.
    //
    // XXX we handle encodings in a rather cavalier way and I
    // suspect we effectively end up assuming utf8. We can do better
    // than that!
    //
    // XXX addAsset probably wants to be able to set MIME type and
    // also control any manifest field we deem relevant (if any)
    //
    // XXX Some handlers process languages that have the concept of
    // include files. These are problematic because we need to
    // somehow instrument them to get the names and hashs of all of
    // the files that they read for dependency tracking purposes. We
    // don't have an API for that yet, so for now we provide a
    // workaround, which is that _fullInputPath contains the full
    // absolute path to the input files, which allows such a plugin
    // to set up its include search path. It's then on its own for
    // registering dependencies (for now..)
    //
    // XXX in the future we should give plugins an easy and clean
    // way to return errors (that could go in an overall list of
    // errors experienced across all files)
    var readOffset = 0;
    var compileStep = {
      inputSize: contents.length,
      inputPath: relPath,
      _fullInputPath: absPath, // avoid, see above..
      // XXX duplicates _pathForSourceMap() in linker
      pathForSourceMap: (
        inputSourceArch.pkg.name
          ? inputSourceArch.pkg.name + "/" + relPath
          : path.basename(relPath)),
      // null if this is an app. intended to be used for the sources
      // dictionary for source maps.
      packageName: inputSourceArch.pkg.name,
      rootOutputPath: inputSourceArch.pkg.serveRoot,
      arch: inputSourceArch.arch, // XXX: what is the story with arch?
      archMatches: function (pattern) {
        return archinfo.matches(inputSourceArch.arch, pattern);
      },
      fileOptions: fileOptions,
      declaredExports: _.pluck(inputSourceArch.declaredExports, 'name'),
      read: function (n) {
        if (n === undefined || readOffset + n > contents.length)
          n = contents.length - readOffset;
        var ret = contents.slice(readOffset, readOffset + n);
        readOffset += n;
        return ret;
      },
      appendDocument: function (options) {
        if (! archinfo.matches(inputSourceArch.arch, "web"))
          throw new Error("Document sections can only be emitted to " +
                          "web targets");
        if (options.section !== "head" && options.section !== "body")
          throw new Error("'section' must be 'head' or 'body'");
        if (typeof options.data !== "string")
          throw new Error("'data' option to appendDocument must be a string");
        resources.push({
          type: options.section,
          data: new Buffer(options.data, 'utf8')
        });
      },
      addStylesheet: function (options) {
        if (! archinfo.matches(inputSourceArch.arch, "web"))
          throw new Error("Stylesheets can only be emitted to " +
                          "web targets");
        if (typeof options.data !== "string")
          throw new Error("'data' option to addStylesheet must be a string");
        resources.push({
          type: "css",
          refreshable: true,
          data: new Buffer(options.data, 'utf8'),
          servePath: path.join(inputSourceArch.pkg.serveRoot, options.path),
          sourceMap: options.sourceMap
        });
      },
      addJavaScript: function (options) {
        if (typeof options.data !== "string")
          throw new Error("'data' option to addJavaScript must be a string");
        if (typeof options.sourcePath !== "string")
          throw new Error("'sourcePath' option must be supplied to addJavaScript. Consider passing inputPath.");
        if (options.bare && ! archinfo.matches(inputSourceArch.arch, "web"))
          throw new Error("'bare' option may only be used for web targets");
        js.push({
          source: options.data,
          sourcePath: options.sourcePath,
          servePath: path.join(inputSourceArch.pkg.serveRoot, options.path),
          bare: !! options.bare,
          sourceMap: options.sourceMap
        });
      },
      addAsset: function (options) {
        if (! (options.data instanceof Buffer))
          throw new Error("'data' option to addAsset must be a Buffer");
        addAsset(options.data, options.path);
      },
      error: function (options) {
        buildmessage.error(options.message || ("error building " + relPath), {
          file: options.sourcePath,
          line: options.line ? options.line : undefined,
          column: options.column ? options.column : undefined,
          func: options.func ? options.func : undefined
        });
      }
    };

    try {
      (buildmessage.markBoundary(handler))(compileStep);
    } catch (e) {
      e.message = e.message + " (compiling " + relPath + ")";
      buildmessage.exception(e);

      // Recover by ignoring this source file (as best we can -- the
      // handler might already have emitted resources)
    }
  });

  // *** Run Phase 1 link

  // Load jsAnalyze from the js-analyze package... unless we are the
  // js-analyze package, in which case never mind. (The js-analyze package's
  // default unibuild is not allowed to depend on anything!)
  var jsAnalyze = null;
  if (! _.isEmpty(js) && inputSourceArch.pkg.name !== "js-analyze") {
    jsAnalyze = uniload.load({
      packages: ["js-analyze"]
    })["js-analyze"].JSAnalyze;
  }

  var results = linker.prelink({
    inputFiles: js,
    useGlobalNamespace: isApp,
    // I was confused about this, so I am leaving a comment -- the
    // combinedServePath is either [pkgname].js or [pluginName]:plugin.js.
    // XXX: If we change this, we can get rid of source arch names!
    combinedServePath: isApp ? null :
      "/packages/" + inputSourceArch.pkg.name +
      (inputSourceArch.archName === "main" ? "" : (":" + inputSourceArch.archName)) + ".js",
    name: inputSourceArch.pkg.name || null,
    declaredExports: _.pluck(inputSourceArch.declaredExports, 'name'),
    jsAnalyze: jsAnalyze
  });

  // *** Determine captured variables
  var packageVariables = [];
  var packageVariableNames = {};
  _.each(inputSourceArch.declaredExports, function (symbol) {
    if (_.has(packageVariableNames, symbol.name))
      return;
    packageVariables.push({
      name: symbol.name,
      export: symbol.testOnly? "tests" : true
    });
    packageVariableNames[symbol.name] = true;
  });
  _.each(results.assignedVariables, function (name) {
    if (_.has(packageVariableNames, name))
      return;
    packageVariables.push({
      name: name
    });
    packageVariableNames[name] = true;
  });

  // *** Consider npm dependencies and portability
  var arch = inputSourceArch.arch;
  if (arch === "os" && ! isPortable) {
    // Contains non-portable compiled npm modules, so set arch correctly
    arch = archinfo.host();
  }
  if (! archinfo.matches(arch, "os")) {
    // npm modules only work on server architectures
    nodeModulesPath = undefined;
  }

  // *** Output unibuild object
  unipackage.addUnibuild({
    name: inputSourceArch.archName,
    arch: arch,
    uses: inputSourceArch.uses,
    implies: inputSourceArch.implies,
    watchSet: watchSet,
    nodeModulesPath: nodeModulesPath,
    prelinkFiles: results.files,
    packageVariables: packageVariables,
    resources: resources
  });

  return sources;
};

// Build a PackageSource into a Unipackage by running its source files through
// the appropriate compiler plugins. Once build has completed, any errors
// detected in the package will have been emitted to buildmessage.
//
// Options:
//  - officialBuild: defaults to false. If false, then we will compute a
//    build identifier (a hash of the package's dependency versions and
//    source files) and include it as part of the unipackage's version
//    string. If true, then we will use the version that is contained in
//    the package's source. You should set it to true when you are
//    building a package to publish as an official build with the
//    package server.
//  - buildTimeDependencies: optional. If present with keys
//    'directDependencies' and 'pluginDependencies', it will be used
//    instead of calling 'determineBuildTimeDependencies'. This is used
//    when we already have a resolved set of build-time dependencies and
//    want to use that instead of resolving them again, e.g. when
//    running 'meteor publish-for-arch'.
//  - ignoreProjectDeps: if we should, in some specific context that
//    glasser only half understands, ignore the current project deps
//
// Returns an object with keys:
// - unipackage: the built Unipackage
// - sources: array of source files (identified by their path on local
//   disk) that were used by the compilation (the source files you'd have to
//   ship to a different machine to replicate the build there)
compiler.compile = function (packageSource, options) {
  buildmessage.assertInCapture();
  var sources = [];
  var pluginWatchSet = packageSource.pluginWatchSet.clone();
  var plugins = {};

  options = _.extend({ officialBuild: false }, options);

  // Determine versions of build-time dependencies
  var buildTimeDeps = determineBuildTimeDependencies(packageSource, {
    ignoreProjectDeps: options.ignoreProjectDeps
  });

  // Build plugins
  _.each(packageSource.pluginInfo, function (info) {
    buildmessage.enterJob({
      title: "building plugin `" + info.name +
        "` in package `" + packageSource.name + "`",
      rootPath: packageSource.sourceRoot
    }, function () {

      var loader = new packageLoader.PackageLoader({
        versions: buildTimeDeps.pluginDependencies[info.name]
      });
      loader.downloadMissingPackages({serverArch: archinfo.host()});

      var buildResult = bundler.buildJsImage({
        name: info.name,
        packageLoader: loader,
        use: info.use,
        sourceRoot: packageSource.sourceRoot,
        sources: info.sources,
        npmDependencies: info.npmDependencies,
        // Plugins have their own npm dependencies separate from the
        // rest of the package, so they need their own separate npm
        // shrinkwrap and cache state.
        npmDir: path.resolve(path.join(packageSource.sourceRoot, '.npm',
                                       'plugin', info.name)),
        dependencyVersions: packageSource.dependencyVersions
      });

      // Add the plugin's sources to our list.
      _.each(info.sources, function (source) {
        sources.push(source);
      });

      // Add this plugin's dependencies to our "plugin dependency"
      // WatchSet. buildResult.watchSet will end up being the merged
      // watchSets of all of the unibuilds of the plugin -- plugins have
      // only one unibuild and this should end up essentially being just
      // the source files of the plugin.
      pluginWatchSet.merge(buildResult.watchSet);

      // Register the built plugin's code.
      if (!_.has(plugins, info.name))
        plugins[info.name] = {};
      plugins[info.name][buildResult.image.arch] = buildResult.image;
    });
  });

  // Grab any npm dependencies. Keep them in a cache in the package
  // source directory so we don't have to do this from scratch on
  // every build.
  //
  // Go through a specialized npm dependencies update process,
  // ensuring we don't get new versions of any (sub)dependencies. This
  // process also runs mostly safely multiple times in parallel (which
  // could happen if you have two apps running locally using the same
  // package).
  //
  // We run this even if we have no dependencies, because we might
  // need to delete dependencies we used to have.
  var isPortable = true;
  var nodeModulesPath = null;
  if (packageSource.npmCacheDirectory) {
    if (meteorNpm.updateDependencies(packageSource.name,
                                     packageSource.npmCacheDirectory,
                                     packageSource.npmDependencies)) {
      nodeModulesPath = path.join(packageSource.npmCacheDirectory,
                                  'node_modules');
      if (! meteorNpm.dependenciesArePortable(packageSource.npmCacheDirectory))
        isPortable = false;
    }
  }

  var unipackage = new Unipackage;
  unipackage.initFromOptions({
    name: packageSource.name,
    metadata: packageSource.metadata,
    version: packageSource.version,
    earliestCompatibleVersion: packageSource.earliestCompatibleVersion,
    isTest: packageSource.isTest,
    plugins: plugins,
    pluginWatchSet: pluginWatchSet,
    cordovaDependencies: packageSource.cordovaDependencies,
    buildTimeDirectDependencies: buildTimeDeps.directDependencies,
    buildTimePluginDependencies: buildTimeDeps.pluginDependencies,
    includeTool: packageSource.includeTool
  });

  // Compile unibuilds. Might use our plugins, so needs to happen second.
  var loader = new packageLoader.PackageLoader({
    versions: buildTimeDeps.packageDependencies,
    constraintSolverOpts: {
      ignoreProjectDeps: options.ignoreProjectDeps
    }
  });

  _.each(packageSource.architectures, function (unibuild) {
    var unibuildSources = compileUnibuild(unipackage, unibuild, loader,
                                          nodeModulesPath, isPortable);
    sources.push.apply(sources, unibuildSources);
  });

  // XXX what should we do if the PackageSource doesn't have a version?
  // (e.g. a plugin)
  if (! options.officialBuild && packageSource.version) {
    // XXX I have no idea if this should be using buildmessage.enterJob
    // or not. test what happens on error
    buildmessage.enterJob({
      title: "compute build identifier for package `" +
        packageSource.name + "`",
      rootPath: packageSource.sourceRoot
    }, function () {
      if (packageSource.version.indexOf("+") !== -1) {
        buildmessage.error("cannot compute build identifier for package `" +
                           packageSource.name + "` version " +
                           packageSource.version + "because it already " +
                           "has a build identifier");
      } else {
        unipackage.addBuildIdentifierToVersion({
          relativeTo: packageSource.sourceRoot
        });
      }
    });
  }

  return {
    sources: _.uniq(sources),
    unipackage: unipackage
  };
};

// Given an object mapping package name to version, return an object
// that includes all the packages that contain plugins, according to the
// catalog.
//
// XXX HACK: This IGNORES package versions that are not available in the
// catalog, which could happen if for example this is called during
// catalog initialization before +local versions have been updated with
// their real buildids. It so happens that this works out, because when
// we are calling it during catalog initialization, we are calling it
// for a package whose build-time dependencies have already been built,
// so any dependencies that contains plugins have real versions in the
// catalog already. Still, this seems very brittle and we should fix it.
var getPluginProviders = function (versions) {
  buildmessage.assertInCapture();
  var result = {};
  _.each(versions, function (version, name) {
    // Direct dependencies only create a build-order constraint if
    // they contain a plugin.
    var catalogVersion = catalog.complete.getVersion(name, version);
    if (catalogVersion && catalogVersion.containsPlugins) {
      result[name] = version;
    }
  });
  return result;
};

// Figure out what packages have to be compiled and available in the
// catalog before 'packageSource' can be compiled. Returns an array of
// objects with keys 'name', 'version' (the latter a version
// string). Yes, it is possible that multiple versions of some other
// package might be build-time dependencies (because of plugins).
compiler.getBuildOrderConstraints = function (
    packageSource, constraintSolverOpts) {
  constraintSolverOpts = constraintSolverOpts || {};
  buildmessage.assertInCapture();

  var versions = {}; // map from package name to version to true
  var addVersion = function (version, name) {
    if (name !== packageSource.name) {
      if (! _.has(versions, name))
        versions[name] = {};
      versions[name][version] = true;
    }
  };

  var buildTimeDeps = determineBuildTimeDependencies(
      packageSource, constraintSolverOpts);

  // Direct dependencies only impose a build-order constraint if they
  // contain plugins.
  _.each(getPluginProviders(buildTimeDeps.directDependencies), addVersion);
  _.each(buildTimeDeps.pluginDependencies, function (versions, pluginName) {
    _.each(versions, addVersion);
  });

  var ret = [];
  _.each(versions, function (versionArray, name) {
    _.each(_.keys(versionArray), function (version) {
      ret.push({ name: name, version: version });
    });
  });

  return ret;
};

// Check to see if a particular build of a package is up to date (that
// is, if the source files haven't changed and the build-time
// dependencies haven't changed, and if we're a sufficiently similar
// version of Meteor to what built it that we believe we'd generate
// identical code). True if we have dependency info and it
// says that the package is up-to-date. False if a source file or
// build-time dependency has changed.
compiler.checkUpToDate = function (
    packageSource, unipackage, constraintSolverOpts) {
  buildmessage.assertInCapture();

  if (unipackage.forceNotUpToDate) {
    return false;
  }

  // Do we think we'd generate different contents than the tool that
  // built this package?
  if (unipackage.builtBy !== compiler.BUILT_BY) {
    return false;
  }

  // Compute the unipackage's direct and plugin dependencies to
  // `buildTimeDeps`, by comparing versions (including build
  // identifiers). For direct dependencies, we only care if the set of
  // direct dependencies that provide plugins has changed.
  var buildTimeDeps = determineBuildTimeDependencies(
      packageSource, constraintSolverOpts);

  var sourcePluginProviders = getPluginProviders(
    buildTimeDeps.directDependencies
  );

  var unipackagePluginProviders = getPluginProviders(
    unipackage.buildTimeDirectDependencies
  );

  if (_.keys(sourcePluginProviders).length !==
      _.keys(unipackagePluginProviders).length) {
    return false;
  }

  var directDepsPackageLoader = new packageLoader.PackageLoader({
    versions: buildTimeDeps.directDependencies
  });
  var directDepsMatch = _.all(
    sourcePluginProviders,
    function (version, packageName) {
      var loadedPackage = directDepsPackageLoader.getPackage(packageName);
      // XXX Check that `versionWithBuildId` is the same as `version`
      // except for the build id?
      return (loadedPackage &&
              unipackagePluginProviders[packageName] ===
              loadedPackage.version);
    }
  );
  if (! directDepsMatch) {
    return false;
  }

  if (_.keys(buildTimeDeps.pluginDependencies).length !==
      _.keys(unipackage.buildTimePluginDependencies).length) {
    return false;
  }

  var pluginDepsMatch = _.all(
    buildTimeDeps.pluginDependencies,
    function (pluginDeps, pluginName) {
      // If we don't know what the dependencies are, then surely we are not up
      // to date.
      if (!pluginDeps)
        return false;

      // For each plugin, check that the resolved build-time deps for
      // that plugin match the unipackage's build time deps for it.
      var packageLoaderForPlugin = new packageLoader.PackageLoader({
        versions: buildTimeDeps.pluginDependencies[pluginName]
      });
      var unipackagePluginDeps = unipackage.buildTimePluginDependencies[pluginName];
      if (! unipackagePluginDeps ||
          _.keys(pluginDeps).length !== _.keys(unipackagePluginDeps).length) {
        return false;
      }
      return _.all(pluginDeps, function (version, packageName) {
        var loadedPackage = packageLoaderForPlugin.getPackage(packageName);
        return loadedPackage &&
          unipackagePluginDeps[packageName] === loadedPackage.version;
      });
    }
  );

  if (! pluginDepsMatch) {
    return false;
  }

  var watchSet = new watch.WatchSet();
  watchSet.merge(unipackage.pluginWatchSet);
  _.each(unipackage.unibuilds, function (unibuild) {
    watchSet.merge(unibuild.watchSet);
  });

  if (! watch.isUpToDate(watchSet)) {
    return false;
  }

  // XXX We don't actually pay attention to includeTool here. Changes that would
  // affect the output of includeTool never cause us to rebuild. We think we
  // will just force a rebuild any time we're actually publishing meteor-tool.
  //
  // We aren't bothering to do this because the code to check-up-to-date would
  // be pretty intricate (it has to check that none of the tools files from git
  // changed as well as *all transitive dependencies* of the packages we
  // include), and there's not much of an advantage to ensuring that the built
  // tool is up to date unless we're about to publish it anyway, since we don't
  // actually run the built tool during development. (And there would be a
  // runtime performance overhead to this extra check.)

  return true;
};


keep keep keep replace replace replace replace keep keep keep keep keep


keep keep keep keep keep replace 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');
});


<template name="pkg_oauth_encryption">
{{#markdown}}
## `oauth-encryption`

Encrypts sensitive account credential information stored in the
database.  See packages/oauth-encryption/README.md for details.

{{/markdown}}
</template>