///  This class provides a set of utility functions for printing to the terminal
///  in the Meteor tool.
///
///  When you intend for your messages to be read by humans, you should use the
///  following functions to print to the terminal. They will automatically line
///  wrap output to either the width of the terminal, or 80 characters. They
///  will also end in a new line.
///
////   - Console.info : Print to stdout.
///    - Console.error: Print to stderr.
///    - Console.warn: Prints to stderr, if warnings are enabled.
///    - Console.debug: Prints to stdout, if debug is enabled.
///
/// Sometimes, there is a phrase that shouldn't be split up over multiple
/// lines (for example, 'meteor update'). When applicable, please use the
/// following functions (Some of them add aditional formatting, especially when
/// pretty-print is turned on):
///
///    - Console.command: things to enter on the command-line, such as
///      'meteor update' or 'cd foobar'.
///    - Console.url: URLs, such as 'www.meteor.com'
///    - Console.path: filepaths outside of Console.command.
///    - Console.noWrap: anything else that you don't want line-wrapped.
///
/// Here is a contrived example:
///   Console.info(
///     "For more information, please run", Console.command("meteor show"),
///     "or check out the new releases at", Console.url("www.meteor.com"),
///     "or look at", Console.path(filepath), ". You are currently running",
///     "Console.noWrap("Meteor version 1.5") + ".");
///
/// The Console.info/Console.error/Console.warn/Console.debug functions also
/// take in Console.options, as a last (optional) argument. These allow you to
/// set an indent or use a bulletpoint. You can check out their API below. If
/// possible, you might also want to use one of the existing wrapper functions,
/// such as Console.labelWarning or Console.arrowInfo.
///
/// Output intended for machines (or pre-formatted in specific ways) should NOT
/// be line-wrapped. Do not wrap these things: JSON output, error stack traces,
/// logs from other programs, etc. For those, you should use the 'raw'
/// version of the API:
///
///    - Console.rawInfo: Like Console.info, but without formatting.
///    - Console.rawError: Like Console.error, but without formatting.
///    - Console.rawWarn: Like Console.warn, but without formatting.
///    - Console.rawDebug: Like Console.debug, but without formatting.
///
/// DO NOT use Console.command/Console.url/Console.path with the raw functions!
/// (They will change your output in ways that you probably do not want). These
/// don't auto-linewrap, end in a newline, or take in Console.options.
///
/// Here is are some examples:
///     Console.rawInfo(JSON.stringify(myData, null, 2));
///     Console.rawError(err.stack + "\n");
///
/// In addition to printing functions, the Console class provides progress bar
/// support, that is mostly handled through buildmessage.js.
var _ = require('underscore');
var Fiber = require('fibers');
var Future = require('fibers/future');
var readline = require('readline');
var util = require('util');
var buildmessage = require('./buildmessage.js');
// XXX: Are we happy with chalk (and its sub-dependencies)?
var chalk = require('chalk');
var cleanup = require('./cleanup.js');
var utils = require('./utils.js');
var wordwrap = require('wordwrap');

var PROGRESS_DEBUG = !!process.env.METEOR_PROGRESS_DEBUG;
var FORCE_PRETTY=undefined;
var CARRIAGE_RETURN =
  (process.platform === 'win32' ? new Array(249).join('\b') : '\r');

if (process.env.METEOR_PRETTY_OUTPUT) {
  FORCE_PRETTY = process.env.METEOR_PRETTY_OUTPUT != '0';
}

if (! process.env.METEOR_COLOR) {
  chalk.enabled = false;
}

var STATUSLINE_MAX_LENGTH = 60;  // XXX unused?
var STATUS_MAX_LENGTH = 40;

var PROGRESS_MAX_WIDTH = 40;
var PROGRESS_BAR_FORMAT = '[:bar] :percent :etas';
var TEMP_STATUS_LENGTH = STATUS_MAX_LENGTH + 12;

var STATUS_INTERVAL_MS = 50;

// Message to show when we don't know what we're doing
// XXX: ? FALLBACK_STATUS = 'Pondering';
var FALLBACK_STATUS = '';

// If there is a part of the larger text, and we really want to make sure that
// it doesn't get split up, we will replace the space with a utf character that
// we are not likely to use anywhere else. This one looks like the a BLACK SUN
// WITH RAYS. We intentionally want to NOT use a space-like character: it should
// be obvious that something has gone wrong if this ever gets printed.
var SPACE_REPLACEMENT = '\u2600';
// In Javascript, replace only replaces the first occurance and this is the
// proposed alternative.
var replaceAll = function (str, search, replace) {
 return str.split(search).join(replace);
};

var spacesArray = new Array(200).join(' ');
var spacesString = function (length) {
  if (length > spacesArray.length) {
    spacesArray = new Array(length * 2).join(' ');
  }
  return spacesArray.substring(0, length);
};
var ARROW = "=> ";

var toFixedLength = function (text, length) {
  text = text || "";

  // pad or truncate `text` to length
  var pad = length - text.length;
  if (pad < 0) {
    // Truncate
    text = text.substring(0, length - 3) + "...";
  } else if (pad > 0) {
    // Pad
    text = text + spacesString(pad);
  }
  return text;
};

// No-op progress display, that means we don't have to handle the 'no progress
// display' case
var ProgressDisplayNone = function () {
};

_.extend(ProgressDisplayNone.prototype, {
  depaint: function () {
    // No-op
  },

  repaint: function () {
    // No-op
  }
});

// Status display only, primarily for use with emacs
// No fancy terminal support available, but we have a TTY.
// Print messages that will be overwritten because they
// end in `\r`.
// Status message mode is where we see status messages but not the
// fancy progress bar.  It's used when we detect a "pseudo-TTY"
// of the type used by Emacs, and possibly SSH.
//
// XXX DELETE THIS MODE since the progress bar now uses "\r".
// But first we have to throttle progress bar updates so that
// Emacs doesn't get overwhelemd (we should throttle them anyway).
// There's also a bug when using the progress bar in Emacs where
// the cursor doesn't seem to return to column 0.
var ProgressDisplayStatus = function (console) {
  var self = this;

  self._console = console;
  self._stream = console._stream;

  self._status = null;
  self._wroteStatusMessage = false;
};

_.extend(ProgressDisplayStatus.prototype, {
  depaint: function () {
    var self = this;
    // For the non-progress-bar status mode, we may need to
    // clear some characters that we printed with a trailing `\r`.
    if (self._wroteStatusMessage) {
      var spaces = spacesString(TEMP_STATUS_LENGTH + 1);
      self._stream.write(spaces + CARRIAGE_RETURN);
      self._wroteStatusMessage = false;
    }
  },

  repaint: function () {
    // We don't repaint after a log message (is that right?)
  },

  updateStatus: function (status) {
    var self = this;

    if (status == self._status) {
      return;
    }

    self._status = status;
    self._render();
  },

  _render: function () {
    var self = this;

    var text = self._status;
    if (text) {
      text = toFixedLength(text, STATUS_MAX_LENGTH);
    }

    if (text) {
      // the number of characters besides `text` here must
      // be accounted for in TEMP_STATUS_LENGTH.
      self._stream.write('  (  ' + text + '  ... )' + CARRIAGE_RETURN);
      self._wroteStatusMessage = true;
    }
  }
});

var SpinnerRenderer = function () {
  var self = this;
  self.frames = ['-', '\\', '|', '/'];
  self.start = +(new Date);
  self.interval = 250;
  //// I looked at some Unicode indeterminate progress indicators, such as:
  ////
  //// spinner = "▁▃▄▅▆▇▆▅▄▃".split('');
  //// spinner = "▉▊▋▌▍▎▏▎▍▌▋▊▉".split('');
  //// spinner = "▏▎▍▌▋▊▉▊▋▌▍▎▏▁▃▄▅▆▇▆▅▄▃".split('');
  //// spinner = "▉▊▋▌▍▎▏▎▍▌▋▊▉▇▆▅▄▃▁▃▄▅▆▇".split('');
  //// spinner = "⠉⠒⠤⣀⠤⠒".split('');
  ////
  //// but none of them really seemed like an improvement. I think
  //// the case for using unicode would be stronger in a determinate
  //// progress indicator.
  ////
  //// There are also some four-frame options such as ◐◓◑◒ at
  ////   http://stackoverflow.com/a/2685827/157965
  //// but all of the ones I tried look terrible in the terminal.
};

SpinnerRenderer.prototype.asString = function () {
  var self = this;
  var now = +(new Date);

  var t = now - self.start;
  var frame = Math.floor(t / self.interval) % self.frames.length;
  return self.frames[frame];
};

// Renders a progressbar.  Based on the npm 'progress' module, but tailored to our needs (i.e. renders to string)
var ProgressBarRenderer = function (format, options) {
  var self = this;

  options = options || {};

  self.fmt = format;
  self.curr = 0;
  self.total = 100;
  self.maxWidth = options.maxWidth || self.total;
  self.chars = {
    complete   : '=',
    incomplete : ' '
  };
};

_.extend(ProgressBarRenderer.prototype, {
  asString: function (availableSpace) {
    var self = this;

    var ratio = self.curr / self.total;
    ratio = Math.min(Math.max(ratio, 0), 1);

    var percent = ratio * 100;
    var incomplete, complete, completeLength;
    var elapsed = new Date - self.start;
    var eta = (percent == 100) ? 0 : elapsed * (self.total / self.curr - 1);

    /* populate the bar template with percentages and timestamps */
    var str = self.fmt
      .replace(':current', self.curr)
      .replace(':total', self.total)
      .replace(':elapsed', isNaN(elapsed) ? '0.0' : (elapsed / 1000).toFixed(1))
      .replace(':eta', (isNaN(eta) || ! isFinite(eta)) ? '0.0' : (eta / 1000).toFixed(1))
      .replace(':percent', percent.toFixed(0) + '%');

    /* compute the available space (non-zero) for the bar */
    var width = Math.min(self.maxWidth, availableSpace - str.replace(':bar', '').length);

    /* NOTE: the following assumes the user has one ':bar' token */
    completeLength = Math.round(width * ratio);
    complete = Array(completeLength + 1).join(self.chars.complete);
    incomplete = Array(width - completeLength + 1).join(self.chars.incomplete);

    /* fill in the actual progress bar */
    str = str.replace(':bar', complete + incomplete);

    return str;
  }
});

var ProgressDisplayFull = function (console) {
  var self = this;

  self._console = console;
  self._stream = console._stream;

  self._status = '';

  var options = {
    complete: '=',
    incomplete: ' ',
    maxWidth: PROGRESS_MAX_WIDTH,
    total: 100
  };
  self._progressBarRenderer = new ProgressBarRenderer(PROGRESS_BAR_FORMAT, options);
  self._progressBarRenderer.start = new Date();

  self._spinnerRenderer = new SpinnerRenderer();

  self._fraction = undefined;

  self._printedLength = 0;
};

_.extend(ProgressDisplayFull.prototype, {
  depaint: function () {
    var self = this;

    self._stream.write(spacesString(self._printedLength) + CARRIAGE_RETURN);
  },

  updateStatus: function (status) {
    var self = this;

    if (status == self._status) {
      return;
    }

    self._status = status;
    self._render();
  },

  updateProgress: function (fraction, startTime) {
    var self = this;

    self._fraction = fraction;
    if (fraction !== undefined) {
      self._progressBarRenderer.curr = Math.floor(fraction * self._progressBarRenderer.total);
    }
    if (startTime) {
      self._progressBarRenderer.start = startTime;
    }
    self._render();
  },

  repaint: function () {
    var self = this;
    self._render();
  },

  _render: function () {
    var self = this;

    var text = self._status;

    // XXX: Throttle these updates?
    // XXX: Or maybe just jump to the correct position?
    var progressGraphic = '';

    // The cursor appears in position 0; we indent it a little to avoid this
    // This also means it appears less important, which is good
    var indentColumns = 3;

    var streamColumns = this._console.width();
    var statusColumns;
    var progressColumns;
    if (! streamColumns) {
      statusColumns = STATUS_MAX_LENGTH;
      progressColumns = 0;
    } else {
      statusColumns = Math.min(STATUS_MAX_LENGTH, streamColumns - indentColumns);
      progressColumns = Math.min(PROGRESS_MAX_WIDTH, streamColumns - indentColumns - statusColumns);
    }

    if (self._fraction !== undefined && progressColumns > 16) {
      // 16 is a heuristic number that allows enough space for a meaningful progress bar
      progressGraphic = "  " + self._progressBarRenderer.asString(progressColumns - 2);
    } else if (progressColumns > 3) {
      // 3 = 2 spaces + 1 spinner character
      progressGraphic = "  " + self._spinnerRenderer.asString();
    } else {
      // Don't show any progress graphic - no room!
    }

    if (text || progressGraphic) {
      // XXX: Just update the graphic, to avoid text flicker?

      var line = spacesString(indentColumns);
      var length = indentColumns;

      if (self._status) {
        var fixedLength = toFixedLength(self._status, statusColumns);
        line += chalk.bold(fixedLength);
        length += statusColumns;
      } else {
        line += spacesString(statusColumns);
        length += statusColumns;
      }

      line += progressGraphic + CARRIAGE_RETURN;
      length += progressGraphic.length;

      self.depaint();
      self._stream.write(line);
      self._printedLength = length;
    }
  }
});

var StatusPoller = function (console) {
  var self = this;

  // The current progress we are watching
  self._watching = null;

  self._console = console;

  self._pollFiber = null;
  self._throttledStatusPoll = new utils.Throttled({
    interval: STATUS_INTERVAL_MS
  });
  self._startPoller();
  self._stop = false;
};

_.extend(StatusPoller.prototype, {
  _startPoller: function () {
    var self = this;

    if (self._pollFiber) {
      throw new Error("Already started");
    }

    self._pollFiber = Fiber(function () {
      while (! self._stop) {
        utils.sleepMs(STATUS_INTERVAL_MS);

        self.statusPoll();
      }
    });
    self._pollFiber.run();
  },

  stop: function () {
    var self = this;

    self._stop = true;
  },

  statusPoll: function () {
    var self = this;
    if (self._throttledStatusPoll.isAllowed()) {
      self._statusPoll();
    }
  },

  _statusPoll: function () {
    var self = this;

    // XXX: Early exit here if we're not showing status at all?

    var rootProgress = buildmessage.getRootProgress();
    if (PROGRESS_DEBUG) {
      // It can be handy for dev purposes to see all the executing tasks
      rootProgress.dump(process.stdout, {skipDone: true});
    }

    var reportState = function (state, startTime) {
      var progressDisplay = self._console._progressDisplay;
      // Do the % computation, if it is going to be used
      if (progressDisplay.updateProgress) {
        if (state.end === undefined || state.end == 0) {
          progressDisplay.updateProgress(undefined, startTime);
        } else {
          var fraction = state.done ? 1.0 : (state.current / state.end);

          if (! isNaN(fraction) && fraction >= 0) {
            progressDisplay.updateProgress(fraction, startTime);
          } else {
            progressDisplay.updateProgress(0, startTime);
          }
        }
      }
    };

    var watching = (rootProgress ? rootProgress.getCurrentProgress() : null);

    if (self._watching === watching) {
      // We need to do this to keep the spinner spinning
      // XXX: Should we _only_ do this when we're showing the spinner?
      reportState(watching.getState(), watching.startTime);
      return;
    }

    self._watching = watching;

    var title = (watching != null ? watching._title : null) || FALLBACK_STATUS;

    var progressDisplay = self._console._progressDisplay;
    progressDisplay.updateStatus && progressDisplay.updateStatus(title);

    if (watching) {
      watching.addWatcher(function (state) {
        if (watching != self._watching) {
          // No longer active
          // XXX: De-register with watching? (we don't bother right now because dead tasks tell no status)
          return;
        }

        reportState(state, watching.startTime);
      });
    }
  }
});

var Console = function (options) {
  var self = this;

  options = options || {};

  // The progress display we are showing on-screen
  self._progressDisplay = new ProgressDisplayNone(self);

  self._statusPoller = null;

  self._throttledYield = new utils.ThrottledYield();

  self.verbose = false;

  // Legacy helpers
  self.stdout = {};
  self.stderr = {};

  self._stream = process.stdout;

  self._pretty = (FORCE_PRETTY !== undefined ? FORCE_PRETTY : false);
  self._progressDisplayEnabled = false;

  self._logThreshold = LEVEL_CODE_INFO;
  var logspec = process.env.METEOR_LOG;
  if (logspec) {
    logspec = logspec.trim().toLowerCase();
    if (logspec == 'debug') {
      self._logThreshold = LEVEL_CODE_DEBUG;
    }
  }

  cleanup.onExit(function (sig) {
    self.enableProgressDisplay(false);
  });
};

var LEVEL_CODE_ERROR = 4;
var LEVEL_CODE_WARN = 3;
var LEVEL_CODE_INFO = 2;
var LEVEL_CODE_DEBUG = 1;

var LEVEL_ERROR = { code: LEVEL_CODE_ERROR };
var LEVEL_WARN = { code: LEVEL_CODE_WARN };
var LEVEL_INFO = { code: LEVEL_CODE_INFO };
var LEVEL_DEBUG = { code: LEVEL_CODE_DEBUG };

// We use a special class to represent the options that we send to the Console
// because it allows us to call 'instance of' on the last argument of variadic
// functions. This allows us to keep the signature of our custom output
// functions (ex: info) roughly the same as the originals.
var ConsoleOptions = function (o) {
  var self = this;
  self.options = o;
}

_.extend(Console.prototype, {
  LEVEL_ERROR: LEVEL_ERROR,
  LEVEL_WARN: LEVEL_WARN,
  LEVEL_INFO: LEVEL_INFO,
  LEVEL_DEBUG: LEVEL_DEBUG,

  setPretty: function (pretty) {
    var self = this;
    // If we're being forced, do nothing.
    if (FORCE_PRETTY !== undefined)
      return;
    // If no change, do nothing.
    if (self._pretty === pretty)
      return;
    self._pretty = pretty;
    self._updateProgressDisplay();
  },

  // Runs f with the progress display visible (ie, with progress display enabled
  // and pretty). Resets both flags to their original values after f runs.
  withProgressDisplayVisible: function (f) {
    var self = this;
    var originalPretty = self._pretty;
    var originalProgressDisplayEnabled = self._progressDisplayEnabled;

    // Turn both flags on.
    self._pretty = self._progressDisplayEnabled = true;

    // Update the screen if anything changed.
    if (! originalPretty || ! originalProgressDisplayEnabled)
      self._updateProgressDisplay();

    try {
      return f();
    } finally {
      // Reset the flags.
      self._pretty = originalPretty;
      self._progressDisplayEnabled = originalProgressDisplayEnabled;
      // Update the screen if anything changed.
      if (! originalPretty || ! originalProgressDisplayEnabled)
        self._updateProgressDisplay();
    }
  },

  setVerbose: function (verbose) {
    var self = this;
    self.verbose = verbose;
  },

  // Get the current width of the Console.
  width: function () {
    var width = 80;
    var stream = process.stdout;
    if (stream && stream.isTTY && stream.columns) {
      width = stream.columns;
    }

    // On Windows cmd.exe splits long lines into smaller chunks by inserting the
    // '\r\n' symbols into the stream, this is what cmd.exe does instead of
    // reflowing the text. We cannot control it. For some unknown reason, even
    // when the output line is less than number of columns (usually 80), cmd.exe
    // would still insert new-line chars. These chars break our repainting that
    // relies on the previous chars to be erasable with '\b' (end-line chars
    // can't be erased this way). This is why we report a smaller number than it
    // is in reality, for safety.
    if (process.platform === 'win32')
      width -= 5;

    return width;
  },

  // This can be called during long lived operations; it will keep the spinner spinning.
  // (This code used to be in Patience.nudge)
  //
  // It's frustrating when you write code that takes a while, either because it
  // uses a lot of CPU or because it uses a lot of network/IO.  In Node,
  // consuming lots of CPU without yielding is especially bad.
  // Other IO/network tasks will stall, and you can't even kill the process!
  //
  // Within any code that may burn CPU for too long, call `Console.nudge()`.
  // If it's been a while since your last yield, your Fiber will sleep momentarily.
  // It will also update the spinner if there is one and it's been a while.
  // The caller should be OK with yielding --- it has to be in a Fiber and it can't be
  // anything that depends for correctness on not yielding.  You can also call nudge(false)
  // if you just want to update the spinner and not yield, but you should avoid this.
  nudge: function (canYield) {
    var self = this;
    if (self._statusPoller) {
      self._statusPoller.statusPoll();
    }
    if (canYield === undefined || canYield === true) {
      self._throttledYield.yield();
    }
  },

  // Initializes and returns a new ConsoleOptions object. Takes in the following
  // Console options to pass to _wrapText eventually.
  //
  //   - bulletPoint: start the first line with a given string, then offset the
  //     subsequent lines by the length of that string. For example, if the
  //     bulletpoint is " => ", we would get:
  //     " => some long message starts here
  //          and then continues here."
  //   - indent: offset the entire string by a specific number of
  //     characters. For example:
  //     "  This entire message is indented
  //        by two characters."
  //
  // Passing in both options will offset the bulletPoint by the indentation,
  // like so:
  //  "  this message is indented by two."
  //  "  => this mesage indented by two and
  //        and also starts with an arrow."
  //
  options: function (o) {
    // (This design pattern allows us to call 'instance of' on the
    // ConsoleOptions in parseVariadicInput, by ensuring that the object created
    // with Console.options is, in fact, a new object.
    return new ConsoleOptions(o);
  },

  // Deal with the arguments to a variadic print function that also takes an
  // optional ConsoleOptions argument at the end.
  //
  // Returns an object with keys:
  //  - options: The options that were passed in, or an empty object.
  //  - message: Arguments to the original function, parsed as a string.
  //
  _parseVariadicInput: function (args) {
    var self = this;
    var msgArgs;
    var options;
    // If the last argument is an instance of ConsoleOptions, then we should
    // separate it out, and only send the first N-1 arguments to be parsed as a
    // message.
    if (_.last(args) instanceof ConsoleOptions) {
      msgArgs = _.initial(args);
      options = _.last(args).options;
    } else {
      msgArgs = args;
      options = {};
    }
    var message = self._format(msgArgs);
    return { message: message, options: options };
  },

  isLevelEnabled: function (levelCode) {
    return (this.verbose || this._logThreshold <= levelCode);
  },

  isDebugEnabled: function () {
    return this.isLevelEnabled(LEVEL_CODE_DEBUG);
  },

  // Don't pretty-fy this output by trying to, for example, line-wrap it. Just
  // print it to the screen as it is.
  rawDebug: function(...args) {
    var self = this;
    if (! self.isDebugEnabled()) {
      return;
    }

    var message = self._format(args);
    self._print(LEVEL_DEBUG, message);
  },

  // By default, Console.debug automatically line wraps the output.
  //
  // Takes in an optional Console.options({}) argument at the end, with the
  // following keys:
  //   - bulletPoint: start the first line with a given string, then offset the
  //     subsequent lines by the length of that string. See _wrap for more details.
  //   - indent: offset the entire string by a specific number of
  //     characters. See _wrap for more details.
  //
  debug: function(...args) {
    var self = this;
    if (! self.isDebugEnabled()) { return; }

    var message = self._prettifyMessage(args);
    self._print(LEVEL_DEBUG, message);
  },

  isInfoEnabled: function () {
    return this.isLevelEnabled(LEVEL_CODE_INFO);
  },

  // Don't pretty-fy this output by trying to, for example, line-wrap it. Just
  // print it to the screen as it is.
  rawInfo: function(...args) {
    var self = this;
    if (! self.isInfoEnabled()) {
      return;
    }

    var message = self._format(args);
    self._print(LEVEL_INFO, message);
  },

  // Generally, we want to process the output for legibility, for example, by
  // wrapping it. For raw output (ex: stack traces, user logs, etc), use the
  // rawInfo function. For more information about options, see: debug.
  info: function(...args) {
    var self = this;
    if (! self.isInfoEnabled()) { return; }

    var message = self._prettifyMessage(args);
    self._print(LEVEL_INFO, message);
  },

  isWarnEnabled: function () {
    return this.isLevelEnabled(LEVEL_CODE_WARN);
  },

  rawWarn: function(...args) {
    var self = this;
    if (! self.isWarnEnabled()) {
      return;
    }

    var message = self._format(args);
    self._print(LEVEL_WARN, message);
  },

  // Generally, we want to process the output for legibility, for example, by
  // wrapping it. For raw output (ex: stack traces, user logs, etc), use the
  // rawWarn function. For more information about options, see: debug.
  warn: function(...args) {
    var self = this;
    if (! self.isWarnEnabled()) { return; }

    var message = self._prettifyMessage(args);
    self._print(LEVEL_WARN, message);
  },

  rawError: function(...args) {
    var self = this;

    var message = self._format(args);
    self._print(LEVEL_ERROR, message);
  },

  // Generally, we want to process the output for legibility, for example, by
  // wrapping it. For raw output (ex: stack traces, user logs, etc), use the
  // rawError function. For more information about options, see: debug.
  error: function(...args) {
    var self = this;

    var message = self._prettifyMessage(args);
    self._print(LEVEL_ERROR, message);
  },

  // Prints a special ANSI sequence that "clears" the screen (on most terminal
  // emulators just scrolls the contents down and resets the position).
  // References: http://en.wikipedia.org/wiki/ANSI_escape_code#CSI_codes
  clear: function () {
    var self = this;

    self.rawInfo('\u001b[2J\u001b[0;0H');
  },

  _prettifyMessage: function (msgArguments) {
    var self = this;
    var parsedArgs = self._parseVariadicInput(msgArguments);
    var wrapOpts = {
      indent: parsedArgs.options.indent,
      bulletPoint: parsedArgs.options.bulletPoint
    };

    var wrappedMessage = self._wrapText(parsedArgs.message, wrapOpts);
    wrappedMessage += "\n";
    return wrappedMessage;
  },

  _print: function(level, message) {
    var self = this;

    // We need to hide the progress bar/spinner before printing the message
    var progressDisplay = self._progressDisplay;
    progressDisplay.depaint();

    // stdout/stderr is determined by the log level
    // XXX: We should probably just implement Loggers with observers
    var dest = process.stdout;
    if (level) {
      switch (level.code) {
        case LEVEL_CODE_ERROR:
          dest = process.stderr;
          break;
        case LEVEL_CODE_WARN:
          dest = process.stderr;
          break;
      }
    }

    // Pick the color/weight if in pretty mode
    var style = null;
    if (level && self._pretty) {
      switch (level.code) {
        case LEVEL_CODE_ERROR:
          style = chalk.bold.red;
          break;
        case LEVEL_CODE_WARN:
          style = chalk.red;
          break;
      }
    }

    if (style) {
      dest.write(style(message));
    } else {
      dest.write(message);
    }

    // XXX: Pause before showing the progress display, to prevent
    // flicker/spewing messages
    // Repaint the progress display
    progressDisplay.repaint();
  },

  // A wrapper around Console.info. Prints the message out in green (if pretty),
  // with the CHECKMARK as the bullet point in front of it.
  success: function (message) {
    var self = this;

    if (! self._pretty) {
      return self.info(message);
    }
    var checkmark = chalk.green('\u2713'); // CHECKMARK
    return self.info(
        chalk.green(message),
        self.options({ bulletPoint: checkmark  + " "}));
  },

  // Wrapper around Console.info. Prints the message out in red (if pretty)
  // with the BALLOT X as the bullet point in front of it.
  failInfo: function (message) {
    var self = this;
    return self._fail(message, "info");
  },

  // Wrapper around Console.warn. Prints the message out in red (if pretty)
  // with the ascii x as the bullet point in front of it.
  failWarn: function (message) {
    var self = this;
    return self._fail(message, "warn");
  },

  // Print the message in red (if pretty) with an x bullet point in front of it.
  _fail: function (message, printFn) {
    var self = this;

    if (! self._pretty) {
      return printFn(message);
    }

    var xmark = chalk.red('\u2717');
    return self[printFn](
        chalk.red(message),
        self.options({ bulletPoint: xmark + " " }));
  },

  // Wrapper around Console.warn that prints a large "WARNING" label in front.
  labelWarn: function (message) {
    var self = this;
    return self.warn(message, self.options({ bulletPoint: "WARNING: " }));
  },

  // Wrappers around Console functions to prints an "=> " in front. Optional
  // indent to indent the arrow.
  arrowError: function (message, indent) {
    var self = this;
    return self._arrowPrint("error", message, indent);
  },
  arrowWarn: function (message, indent) {
    var self = this;
    return self._arrowPrint("warn", message, indent);
  },
  arrowInfo: function (message, indent) {
    var self = this;
    return self._arrowPrint("info", message, indent);
  },
  _arrowPrint: function(printFn, message, indent) {
    var self = this;
    indent = indent || 0;
    return self[printFn](
      message,
      self.options({ bulletPoint: ARROW, indent: indent }));
  },

  // A wrapper around console.error. Given an error and some background
  // information, print out the correct set of messages depending on verbose
  // level, etc.
  printError: function (err, info) {
    var self = this;

    var message = err.message;
    if (! message) {
      message = "Unexpected error";
      if (self.verbose) {
        message += " (" + err.toString() + ")";
      }
    }

    if (info) {
      message = info + ": " + message;
    }

    self.error(message);
    if (self.verbose && err.stack) {
      self.rawInfo(err.stack + "\n");
    }
  },

  // A wrapper to print out buildmessage errors.
  printMessages: function (messages) {
    var self = this;

    if (messages.hasMessages()) {
      self.error("\n" + messages.formatMessages());
    }
  },

  // Wrap commands in this function -- it ensures that commands don't get line
  // wrapped (ie: print 'meteor' at the end of the line, and 'create --example'
  // at the beginning of the next one).
  //
  // To use, wrap commands that you send into print functions with this
  // function, like so: Console.info(text + Console.command("meteor create
  // --example leaderboard") + moretext).
  //
  // If pretty print is on, this will also bold the commands.
  command: function (message) {
    var self = this;
    var unwrapped = self.noWrap(message);
    return self.bold(unwrapped);
  },

  // Underline the URLs (if pretty print is on).
  url: function (message) {
    var self = this;
    // If we are going to print URLs with spaces, we should turn spaces into
    // things browsers understand.
    var unspaced =
          replaceAll(message, ' ', '%20');
    // There is no need to call noWrap here, since that only handles spaces (and
    // we have done that). If it ever handles things other than spaces, we
    // should make sure to call it here.
    return self.underline(unspaced);
  },

  // Format a filepath to not wrap. This does NOT automatically escape spaces
  // (ie: add a slash in front so the user could copy paste the file path into a
  // terminal).
  path: function (message) {
    var self = this;
    // Make sure that we don't wrap this.
    var unwrapped = self.noWrap(message);
    return self.bold(unwrapped);
  },

  // Do not wrap this substring when you send it into a non-raw print function.
  // DO NOT print the result of this call with a raw function.
  noWrap: function (message) {
    var noBlanks = replaceAll(message, ' ', SPACE_REPLACEMENT);
    return noBlanks;
  },

  // A wrapper around the underline functionality of chalk.
  underline: function (message) {
    var self = this;

    if (! self._pretty) {
      return message;
    }
    return chalk.underline(message);
  },

  // A wrapper around the bold functionality of chalk.
  bold: function (message) {
    var self = this;

    if (! self._pretty) {
      return message;
    }
    return chalk.bold(message);
  },

  // Prints a two column table in a nice format (The first column is printed
  // entirely, the second only as space permits).
  //  options:
  //      - level: Allows to print to stderr, instead of stdout. Set the print
  //        level with Console.LEVEL_INFO, Console.LEVEL_ERROR, etc.
  //      - ignoreWidth: ignore the width of the terminal, and go over the
  //        character limit instead of trailing off with '...'. Useful for
  //        printing directories, for examle.
  //      - indent: indent the entire table by a given number of spaces.
  printTwoColumns : function (rows, options) {
    var self = this;
    options = options || {};

    var longest = '';
    _.each(rows, function (row) {
      var col0 = row[0] || '';
      if (col0.length > longest.length)
        longest = col0;
    });

    var pad = longest.replace(/./g, ' ');
    var width = self.width();
    var indent =
      options.indent ? Array(options.indent + 1).join(' ') : "";

    var out = '';
    _.each(rows, function (row) {
      var col0 = row[0] || '';
      var col1 = row[1] || '';
      var line = indent + self.bold(col0) + pad.substr(col0.length);
      line += "  " + col1;
      if (! options.ignoreWidth && line.length > width) {
        line = line.substr(0, width - 3) + '...';
      }
      out += line + "\n";
    });

    var level = options.level || self.LEVEL_INFO;
    out += "\n";
    self._print(level, out);

    return out;
  },

  // Format logs according to the spec in utils.
  _format: function (logArguments) {
    return util.format.apply(util, logArguments);
  },

  // Wraps long strings to the length of user's terminal. Inserts linebreaks
  // between words when nearing the end of the line. Returns the wrapped string
  // and takes the following arguments:
  //
  // text: the text to wrap
  // options:
  //   - bulletPoint: (see: Console.options)
  //   - indent: (see: Console.options)
  //
  _wrapText: function (text, options) {
    var self = this;
    options = options || {};

    // Compute the maximum offset on the bulk of the message.
    var maxIndent = 0;
    if (options.indent && options.indent > 0) {
      maxIndent = maxIndent + options.indent;
    }
    if (options.bulletPoint) {
      maxIndent = maxIndent + options.bulletPoint.length;
    }

    // Get the maximum width, or if we are not running in a terminal (self-test,
    // for example), default to 80 columns.
    var max = self.width();

    var wrappedText;
    if (process.env.METEOR_NO_WORDWRAP) {
      var indent =
        options.indent ? Array(options.indent + 1).join(' ') : "";
      if (options.bulletPoint) {
        wrappedText = options.bulletPoint + text;
      } else {
        wrappedText = text;
      }
      wrappedText = _.map(wrappedText.split('\n'), function (s) {
        if (s === "")
          return "";
        return indent + s;
      }).join('\n');

    } else {
      // Wrap the text using the npm wordwrap library.
      wrappedText = wordwrap(maxIndent, max)(text);

      // Insert the start string, if applicable.
      if (options.bulletPoint) {
        // Save the initial indent level.
        var initIndent = options.indent ?
              wrappedText.substring(0, options.indent) : "";
        // Add together the initial indent (if any), the bullet point and the
        // remainder of the message.
        wrappedText = initIndent + options.bulletPoint +
          wrappedText.substring(maxIndent);
      }
    }

    // If we have previously replaces any spaces, now is the time to bring them
    // back.
    wrappedText = replaceAll(wrappedText, SPACE_REPLACEMENT, ' ');
    return wrappedText;
  },

  // Enables the progress bar, or disables it when called with (false)
  enableProgressDisplay: function (enabled) {
    var self = this;

    // No arg => enable
    if (enabled === undefined) {
      enabled = true;
    }

    if (self._progressDisplayEnabled === enabled)
      return;

    self._progressDisplayEnabled = enabled;
    self._updateProgressDisplay();
  },

  // In response to a change in setPretty or enableProgressDisplay,
  // configure the appropriate progressDisplay
  _updateProgressDisplay: function () {
    var self = this;

    var newProgressDisplay;

    if (! self._progressDisplayEnabled) {
      newProgressDisplay = new ProgressDisplayNone();
    } else if ((! self._stream.isTTY) || (! self._pretty)) {
      // No progress bar if not in pretty / on TTY.
      newProgressDisplay = new ProgressDisplayNone(self);
    } else if (self._stream.isTTY && ! self._stream.columns) {
      // We might be in a pseudo-TTY that doesn't support
      // clearLine() and cursorTo(...).
      // It's important that we only enter status message mode
      // if self._pretty, so that we don't start displaying
      // status messages too soon.
      // XXX See note where ProgressDisplayStatus is defined.
      newProgressDisplay = new ProgressDisplayStatus(self);
    } else {
      // Otherwise we can do the full progress bar
      newProgressDisplay = new ProgressDisplayFull(self);
    }

    // Start/stop the status poller, so we never block exit
    if (self._progressDisplayEnabled) {
      if (! self._statusPoller) {
        self._statusPoller = new StatusPoller(self);
      }
    } else {
      if (self._statusPoller) {
        self._statusPoller.stop();
        self._statusPoller = null;
      }
    }

    self._setProgressDisplay(newProgressDisplay);
  },

  _setProgressDisplay: function (newProgressDisplay) {
    var self = this;

    // XXX: Optimize case of no-op transitions? (same mode -> same mode)

    var oldProgressDisplay = self._progressDisplay;
    oldProgressDisplay.depaint();

    self._progressDisplay = newProgressDisplay;
  }
});

// options:
//   - echo (boolean): defaults to true
//   - prompt (string)
//   - stream: defaults to process.stdout (you might want process.stderr)
Console.prototype.readLine = function (options) {
  var self = this;

  var fut = new Future();

  options = _.extend({
    echo: true,
    stream: self._stream
  }, options);

  var silentStream = {
    write: function () {
    },
    on: function () {
    },
    end: function () {
    },
    isTTY: options.stream.isTTY,
    removeListener: function () {
    }
  };

  var previousProgressDisplay = self._progressDisplay;
  self._setProgressDisplay(new ProgressDisplayNone());

  // Read a line, throwing away the echoed characters into our dummy stream.
  var rl = readline.createInterface({
    input: process.stdin,
    output: options.echo ? options.stream : silentStream,
    // `terminal: options.stream.isTTY` is the default, but emacs shell users
    // don't want fancy ANSI.
    terminal: options.stream.isTTY && process.env.EMACS !== 't'
  });

  if (! options.echo) {
    options.stream.write(options.prompt);
  } else {
    rl.setPrompt(options.prompt);
    rl.prompt();
  }

  rl.on('line', function (line) {
    rl.close();
    if (! options.echo)
      options.stream.write("\n");
    self._setProgressDisplay(previousProgressDisplay);
    fut['return'](line);
  });

  return fut.wait();
};

exports.Console = new Console;
exports.Console.CARRIAGE_RETURN = CARRIAGE_RETURN;


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

var selftest = require('../selftest.js');
var testUtils = require('../test-utils.js');
var Sandbox = selftest.Sandbox;
var files = require('../files.js');

var commandTimeoutSecs = testUtils.accountsCommandTimeoutSecs;

var loggedInError = selftest.markStack(function(run) {
  run.waitSecs(commandTimeoutSecs);
  run.matchErr("You must be logged in to claim sites.");
  run.expectExit(1);
});

var waitAndError = selftest.markStack(function(run, errmsg) {
  run.waitSecs(commandTimeoutSecs);
  run.matchErr(errmsg);
  run.expectExit(1);
});

selftest.define("claim", ['net', 'slow'], function () {
  var s = new Sandbox;

  // Can't claim sites while logged out.
  // Nonexistent site.
  var run = s.run('claim', testUtils.randomAppName(20));
  loggedInError(run);

  // Can't claim sites without specifying a site
  run = s.run('claim');
  run.matchErr('not enough arguments');
  run.expectExit(1);

  // Existing site.
  run = s.run('claim', 'mother-test');
  loggedInError(run);

  // Claim will not work on non-legacy sites.
  // belongs to me.
  testUtils.login(s, "test", "testtest");
  var appName = testUtils.createAndDeployApp(s);
  run = s.run('claim', appName);
  waitAndError(run, "That site already belongs to you.");

  // belongs to not me.
  testUtils.logout(s);
  testUtils.login(s, "testtest", "testtest");
  run = s.run('claim', appName);
  waitAndError(run, "Sorry, that site belongs to someone else.");

  // belongs to not me, but I am authorized.
  testUtils.logout(s);
  testUtils.login(s, "test", "testtest");
  run = s.run('authorized', appName, '--add', 'testtest');
  run.waitSecs(commandTimeoutSecs);
  run.match('added');
  run.expectExit(0);
  testUtils.logout(s);
  testUtils.login(s, "testtest", "testtest");
  run = s.run('claim', appName);
  waitAndError(run, "That site already belongs to you.");

  testUtils.cleanUpApp(s, appName);
});


$windows_scripts = split-path -parent $MyInvocation.MyCommand.Definition
$scripts_path = split-path -parent $windows_scripts
$CHECKOUT_DIR = split-path -parent $scripts_path

# extract the bundle version from the meteor bash script
$BUNDLE_VERSION = select-string -Path ($CHECKOUT_DIR + "\meteor") -Pattern 'BUNDLE_VERSION=(\S+)'  | % { $_.Matches[0].Groups[1].Value } | select-object -First 1
$BUNDLE_VERSION = $BUNDLE_VERSION.Trim()

# extract the bundle version we have on FS
$CURRENT_VERSION = select-string -Path ($CHECKOUT_DIR + "\dev_bundle\.bundle_version.txt") -Pattern '(.*)' | % { $_.Matches[0].Captures[0].Value } | select-object -First 1

If ($CURRENT_VERSION -eq $BUNDLE_VERSION) {
  exit 0
}

# fail
exit 1


// Include helpers from NPM so that the compiler doesn't need to add boilerplate

// Installs source map support with a hook to add functions to look for source

// Globally install ES2015-complaint Symbol, Map, and Set, patching the

// native implementations if they are available.

// 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');


Package.describe({
  summary: "Parses Meteor Smart Package version strings",
  version: "3.0.3"
});

Package.onUse(function (api) {
  api.export('PackageVersion');
  api.use('underscore');
  api.addFiles(['semver410.js',
                'package-version-parser.js']);
});

Package.onTest(function (api) {
  api.use('package-version-parser');
  api.use(['tinytest']);
  api.addFiles('package-version-parser-tests.js', 'server');
});


// Only one configuration should ever exist for each service.
// A unique index helps avoid various race conditions which could
// otherwise lead to an inconsistent database state (when there are multiple
// configurations for a single service, which configuration is correct?)
try {
    ServiceConfiguration.configurations._ensureIndex(
        { "service": 1 },
        { unique: true }
    );
} catch (err) {
    console.error(
        "The service-configuration package persists configuration in the " +
        "meteor_accounts_loginServiceConfiguration collection in MongoDB. As " +
        "each service should have exactly one configuration, Meteor " +
        "automatically creates a MongoDB index with a unique constraint on the " +
        " meteor_accounts_loginServiceConfiguration collection. The " +
        "_ensureIndex command which creates that index is failing.\n\n" +
        "Meteor versions before 1.0.4 did not create this index. If you recently " +
        "upgraded and are seeing this error message for the first time, please " +
        "check your meteor_accounts_loginServiceConfiguration collection for " +
        "multiple configuration entries for the same service and delete " +
        "configuration entries until there is no more than one configuration " +
        "entry per service.\n\n" +
        "If the meteor_accounts_loginServiceConfiguration collection looks " +
        "fine, the _ensureIndex command is failing for some other reason.\n\n" +
        "For more information on this history of this issue, please see " +
        "https://github.com/meteor/meteor/pull/3514.\n"
    );
    throw err;
}


/// Implements the process of managing a package's .npm directory,
/// in which we call `npm install` to install npm dependencies,
/// and a variety of related commands. Notably, we use `npm shrinkwrap`
/// to ensure we get consistent versions of npm sub-dependencies.
var Future = require('fibers/future');

var cleanup = require('./cleanup.js');
var files = require('./files.js');
var os = require('os');
var _ = require('underscore');
var httpHelpers = require('./http-helpers.js');
var buildmessage = require('./buildmessage.js');
var utils = require('./utils.js');
var runLog = require('./run-log.js');

var meteorNpm = exports;

// if a user exits meteor while we're trying to create a .npm
// directory, we will have temporary directories that we clean up
var tmpDirs = [];
cleanup.onExit(function () {
  _.each(tmpDirs, function (dir) {
    if (files.exists(dir))
      files.rm_recursive(dir);
  });
});

// Exception used internally to gracefully bail out of a npm run if
// something goes wrong
var NpmFailure = function () {};

// Creates a temporary directory in which the new contents of the
// package's .npm directory will be assembled. If all is successful,
// renames that directory back to .npm. Returns true if there are NPM
// dependencies and they are installed without error.
//
// @param npmDependencies {Object} dependencies that should be
//     installed, eg {tar: '0.1.6', gcd: '0.0.0'}. If falsey or empty,
//     will remove the .npm directory instead.
meteorNpm.updateDependencies = function (packageName,
                                         packageNpmDir,
                                         npmDependencies,
                                         quiet) {
  // we make sure to put it beside the original package dir so that
  // we can then atomically rename it. we also make sure to
  // randomize the name, in case we're bundling this package
  // multiple times in parallel.
  var newPackageNpmDir = packageNpmDir + '-new-' + utils.randomToken();

  if (! npmDependencies || _.isEmpty(npmDependencies)) {
    // No NPM dependencies? Delete the .npm directory if it exists (because,
    // eg, we used to have NPM dependencies but don't any more).  We'd like to
    // do this in as atomic a way as possible in case multiple meteor
    // instances are trying to make this update in parallel, so we rename the
    // directory to something before doing the rm -rf.
    try {
      files.rename(packageNpmDir, newPackageNpmDir);
    } catch (e) {
      if (e.code !== 'ENOENT')
        throw e;
      // It didn't exist, which is exactly what we wanted.
      return false;
    }
    files.rm_recursive(newPackageNpmDir);
    return false;
  }

  try {
    // v0.6.0 had a bug that could cause .npm directories to be
    // created without npm-shrinkwrap.json
    // (https://github.com/meteor/meteor/pull/927). Running your app
    // in that state causes consistent "Corrupted .npm directory"
    // errors.
    //
    // If you've reached that state, delete the empty directory and
    // proceed.
    if (files.exists(packageNpmDir) &&
        ! files.exists(files.pathJoin(packageNpmDir, 'npm-shrinkwrap.json'))) {
      files.rm_recursive(packageNpmDir);
    }

    if (files.exists(packageNpmDir)) {
      // we already nave a .npm directory. update it appropriately with some
      // ceremony involving:
      // `npm install`, `npm install name@version`, `npm shrinkwrap`
      updateExistingNpmDirectory(
        packageName, newPackageNpmDir, packageNpmDir, npmDependencies, quiet);
    } else {
      // create a fresh .npm directory with `npm install
      // name@version` and `npm shrinkwrap`
      createFreshNpmDirectory(
        packageName, newPackageNpmDir, packageNpmDir, npmDependencies, quiet);
    }
  } catch (e) {
    if (e instanceof NpmFailure) {
      // Something happened that was out of our control, but wasn't
      // exactly unexpected (eg, no such npm package, no internet
      // connection). Handle it gracefully.
      return false;
    }

    // Some other exception -- let it propagate.
    throw e;
  } finally {
    if (files.exists(newPackageNpmDir))
      files.rm_recursive(newPackageNpmDir);
    tmpDirs = _.without(tmpDirs, newPackageNpmDir);
  }

  return true;
};

// Return true if all of a package's npm dependencies are portable
// (that is, if the node_modules can be copied anywhere and we'd
// expect it to work, rather than containing native extensions that
// were built just for our architecture), else
// false. updateDependencies should first be used to bring
// packageNpmDir up to date.
meteorNpm.dependenciesArePortable = function (packageNpmDir) {
  // We use a simple heuristic: we check to see if a package (or any
  // of its transitive depedencies) contains any *.node files. .node
  // is the extension that signals to Node that it should load a file
  // as a shared object rather than as JavaScript, so this should work
  // in the vast majority of cases.

  var search = function (dir) {
    return _.find(files.readdir(dir), function (itemName) {
      if (itemName.match(/\.node$/))
        return true;
      var item = files.pathJoin(dir, itemName);
      if (files.lstat(item).isDirectory())
        return search(item);
    }) || false;
  };

  return ! search(files.pathJoin(packageNpmDir, 'node_modules'));
};

var makeNewPackageNpmDir = function (newPackageNpmDir) {
  // keep track so that we can remove them on process exit
  tmpDirs.push(newPackageNpmDir);
  files.mkdir_p(newPackageNpmDir);

  // create node_modules -- prevent npm install from installing
  // to an existing node_modules dir higher up in the filesystem
  files.mkdir(files.pathJoin(newPackageNpmDir, 'node_modules'));

  // create .gitignore -- node_modules shouldn't be in git since we
  // recreate it as needed by using `npm install`. since we use `npm
  // shrinkwrap` we're guaranteed to have the same version installed
  // each time.
  files.writeFile(
    files.pathJoin(newPackageNpmDir, '.gitignore'),
    ['node_modules',
     ''/*git diff complains without trailing newline*/].join('\n'));
};

var updateExistingNpmDirectory = function (packageName, newPackageNpmDir,
                                           packageNpmDir, npmDependencies,
                                           quiet) {
  // sanity check on contents of .npm directory
  if (!files.stat(packageNpmDir).isDirectory())
    throw new Error("Corrupted .npm directory -- should be a directory: " +
                    packageNpmDir);
  if (!files.exists(files.pathJoin(packageNpmDir, 'npm-shrinkwrap.json')))
    throw new Error(
      "Corrupted .npm directory -- can't find npm-shrinkwrap.json in " +
        packageNpmDir);

  // We need to rebuild all node modules when the Node version
  // changes, in case there are some binary ones. Technically this is
  // racey, but it shouldn't fail very often.
  var nodeModulesDir = files.pathJoin(packageNpmDir, 'node_modules');
  if (files.exists(nodeModulesDir)) {
    var oldNodeVersion;
    try {
      oldNodeVersion = files.readFile(
        files.pathJoin(packageNpmDir, 'node_modules', '.node_version'), 'utf8');
    } catch (e) {
      if (e.code !== 'ENOENT')
        throw e;
      // Use the Node version from the last release where we didn't
      // drop this file.
      oldNodeVersion = 'v0.8.24';
    }

    if (oldNodeVersion !== currentNodeCompatibilityVersion())
      files.rm_recursive(nodeModulesDir);
  }

  // Make sure node_modules is present (fix for #1761). Prevents npm install
  // from installing to an existing node_modules dir higher up in the
  // filesystem.  node_modules may be absent due to a change in Node version or
  // when `meteor add`ing a cloned package for the first time (node_modules is
  // excluded by .gitignore)
  if (! files.exists(nodeModulesDir))
    files.mkdir(nodeModulesDir);

  var installedDependenciesTree = getInstalledDependenciesTree(packageNpmDir);
  var installedDependencies = treeToDependencies(installedDependenciesTree);
  var shrinkwrappedDependenciesTree =
    getShrinkwrappedDependenciesTree(packageNpmDir);
  var shrinkwrappedDependencies = treeToDependencies(
    shrinkwrappedDependenciesTree);

  // If we already have the right things installed, life is good.
  // XXX this check is wrong: what if we just pulled a commit that
  //     changes a sub-module in npm-shrinkwrap.json? See #1648 But
  //     while it might be "correct" to just drop this check we should
  //     be careful not to make the common case of no changes too
  //     slow.
  if (_.isEqual(installedDependencies, npmDependencies)) {
    // OK, so what we have installed matches the top-level dependencies
    // specified in `Npm.depends`. But what if we just pulled a change in
    // npm-shrinkwrap.json to an indirectly used module version? We'll have to
    // compare more carefully.  First, normalize the tree (strip irrelevant
    // fields and normalize to 'version').
    var minimizedInstalled = minimizeDependencyTree(installedDependenciesTree);
    // If what we have installed is the same as what we have shrinkwrapped, then
    // we're done.
    if (_.isEqual(minimizedInstalled, shrinkwrappedDependenciesTree)) {
      return;
    }
  }

  if (! quiet)
    logUpdateDependencies(packageName, npmDependencies);

  var preservedShrinkwrap = {dependencies: {}};
  _.each(shrinkwrappedDependencies, function (version, name) {
    if (npmDependencies[name] === version) {
      // We're not changing this dependency, so copy over its shrinkwrap.
      preservedShrinkwrap.dependencies[name] =
        shrinkwrappedDependenciesTree.dependencies[name];
    }
  });

  makeNewPackageNpmDir(newPackageNpmDir);

  if (!_.isEmpty(preservedShrinkwrap.dependencies)) {
    // There are some unchanged packages here. Install from shrinkwrap.
    files.writeFile(files.pathJoin(newPackageNpmDir, 'npm-shrinkwrap.json'),
                     JSON.stringify(preservedShrinkwrap, null, /*legible*/2));

    // construct a matching package.json to make `npm install` happy
    constructPackageJson(packageName, newPackageNpmDir,
                         treeToDependencies(preservedShrinkwrap));

    // `npm install`
    installFromShrinkwrap(newPackageNpmDir);

    // delete package.json and npm-shrinkwrap.json
    files.unlink(files.pathJoin(newPackageNpmDir, 'package.json'));
    files.unlink(files.pathJoin(newPackageNpmDir, 'npm-shrinkwrap.json'));
  }

  // we may have just installed the shrinkwrapped packages. but let's not
  // trust that it actually worked: let's do the rest based on what we
  // actually have installed now.
  var newInstalledDependencies = getInstalledDependencies(newPackageNpmDir);

  // `npm install name@version` for modules that need updating
  _.each(npmDependencies, function (version, name) {
    if (newInstalledDependencies[name] !== version) {
      installNpmModule(name, version, newPackageNpmDir);
    }
  });

  completeNpmDirectory(packageName, newPackageNpmDir, packageNpmDir,
                       npmDependencies);
};

var createFreshNpmDirectory = function (packageName, newPackageNpmDir,
                                        packageNpmDir, npmDependencies, quiet) {
  if (! quiet)
    logUpdateDependencies(packageName, npmDependencies);

  makeNewPackageNpmDir(newPackageNpmDir);
  // install dependencies
  _.each(npmDependencies, function (version, name) {
    installNpmModule(name, version, newPackageNpmDir);
  });

  completeNpmDirectory(packageName, newPackageNpmDir, packageNpmDir,
                       npmDependencies);
};

// Shared code for updateExistingNpmDirectory and createFreshNpmDirectory.
var completeNpmDirectory = function (packageName, newPackageNpmDir,
                                     packageNpmDir, npmDependencies) {
  // temporarily construct a matching package.json to make `npm
  // shrinkwrap` happy
  constructPackageJson(packageName, newPackageNpmDir, npmDependencies);

  // Create a shrinkwrap file.
  shrinkwrap(newPackageNpmDir);

  // now delete package.json
  files.unlink(files.pathJoin(newPackageNpmDir, 'package.json'));

  createReadme(newPackageNpmDir);
  createNodeVersion(newPackageNpmDir);
  files.renameDirAlmostAtomically(newPackageNpmDir, packageNpmDir);
};

var createReadme = function (newPackageNpmDir) {
  // This file gets checked in to version control by users, so resist the
  // temptation to make unnecessary tweaks to it.
  files.writeFile(
    files.pathJoin(newPackageNpmDir, 'README'),
"This directory and the files immediately inside it are automatically generated\n" +
"when you change this package's NPM dependencies. Commit the files in this\n" +
"directory (npm-shrinkwrap.json, .gitignore, and this README) to source control\n" +
"so that others run the same versions of sub-dependencies.\n" +
"\n" +
"You should NOT check in the node_modules directory that Meteor automatically\n" +
"creates; if you are using git, the .gitignore file tells git to ignore it.\n"
  );
};

var createNodeVersion = function (newPackageNpmDir) {
  files.writeFile(
    files.pathJoin(newPackageNpmDir, 'node_modules', '.node_version'),
    currentNodeCompatibilityVersion());
};

// This value should change whenever we think that the Node C ABI has changed
// (ie, when we need to be sure to reinstall npm packages because they might
// have native components that need to be rebuilt). It does not need to change
// for every patch release of Node! Notably, it needed to change between 0.8.*
// and 0.10.*.  If Node does make a patch release of 0.10 that breaks
// compatibility, you can just change this from "0.10.*" to "0.10.35" or
// whatever.
var currentNodeCompatibilityVersion = function () {
  var version = process.version;
  version = version.replace(/\.(\d+)$/, '.*');
  return version + '\n';
};

var runNpmCommand = function (args, cwd) {
  var npmPath;
  var nodeBinDir;

  if (os.platform() === "win32") {
    npmPath = files.convertToOSPath(
      files.pathJoin(files.getDevBundle(), "bin", "npm.cmd"));
    nodeBinDir = files.convertToOSPath(
      files.pathJoin(files.getDevBundle(), "bin"));
  } else {
    npmPath = files.pathJoin(files.getDevBundle(), "bin", "npm");
    nodeBinDir = files.pathJoin(files.getDevBundle(), "bin");
  }

  if (meteorNpm._printNpmCalls) // only used by test-bundler.js
    process.stdout.write('cd ' + cwd + ' && ' + npmPath + ' ' +
                         args.join(' ') + ' ...\n');

  if (cwd)
    cwd = files.convertToOSPath(cwd);

  var env = process.env;
  // It looks like some npm commands (such as build commands, specifically on
  // Windows) rely on having a global node binary present.
  // Sometimes users have a global node installed, so it is not
  // a problem, but a) it can be outdated and b) it can not be installed.
  // To solve this problem, we set the PATH env variable to have the path
  // containing the node binary we are running in right now as the highest
  // priority.
  // This hack is confusing as npm is supposed to do it already.
  var pathDecomposed = (env.PATH || "").split(files.pathOsDelimiter);
  pathDecomposed.unshift(nodeBinDir);
  env.PATH = pathDecomposed.join(files.pathOsDelimiter);
  var opts = { cwd: cwd, env: env, maxBuffer: 10 * 1024 * 1024 };

  var future = new Future;
  var child_process = require('child_process');
  child_process.execFile(
    npmPath, args, opts, function (err, stdout, stderr) {
    if (meteorNpm._printNpmCalls)
      process.stdout.write(err ? 'failed\n' : 'done\n');

    future.return({
      success: ! err,
      stdout: stdout,
      stderr: stderr
    });
  });

  return future.wait();
}

var constructPackageJson = function (packageName, newPackageNpmDir,
                                     npmDependencies) {
  var packageJsonContents = JSON.stringify({
    // name and version are unimportant but required for `npm install`.
    // we used to put packageName in here, but it doesn't work when that
    // has colons.
    name: 'packages-for-meteor-smartpackage-' + utils.randomToken(),
    version: '0.0.0',
    dependencies: npmDependencies
  });
  var packageJsonPath = files.pathJoin(newPackageNpmDir, 'package.json');
  files.writeFile(packageJsonPath, packageJsonContents);
};

// Gets a JSON object from `npm ls --json` (getInstalledDependenciesTree) or
// `npm-shrinkwrap.json` (getShrinkwrappedDependenciesTree).
//
// @returns {Object} eg {
//   "name": "packages",
//   "version": "0.0.0",
//   "dependencies": {
//     "sockjs": {
//       "version": "0.3.4",
//       "dependencies": {
//         "node-uuid": {
//           "version": "1.3.3"
//         }
//       }
//     }
//   }
// }
var getInstalledDependenciesTree = function (dir) {
  var result = runNpmCommand(["ls", "--json"], dir);

  if (result.success)
    return JSON.parse(result.stdout);

  // XXX include this in the buildmessage.error instead
  runLog.log(result.stderr);
  buildmessage.error("couldn't read npm version lock information");
  // Recover by returning false from updateDependencies
  throw new NpmFailure;
};

var getShrinkwrappedDependenciesTree = function (dir) {
  var shrinkwrapFile = files.readFile(files.pathJoin(dir, 'npm-shrinkwrap.json'));
  return JSON.parse(shrinkwrapFile);
};

// Maps a "dependency object" (a thing you find in `npm ls --json` or
// npm-shrinkwrap.json with keys like "version" and "from") to the
// canonical version that matches what users put in the `Npm.depends`
// clause.  ie, either the version or the tarball URL.
//
// If more logic is added here, it should probably go in minimizeModule too.
var canonicalVersion = function (depObj) {
  if (utils.isUrlWithSha(depObj.from))
    return depObj.from;
  else
    return depObj.version;
};

// map the structure returned from `npm ls` or shrinkwrap.json into
// the structure of npmDependencies (e.g. {gcd: '0.0.0'}), so that
// they can be diffed. This only returns top-level dependencies.
var treeToDependencies = function (tree) {
  return _.object(
    _.map(
      tree.dependencies, function (properties, name) {
        return [name, canonicalVersion(properties)];
      }));
};

var getInstalledDependencies = function (dir) {
  return treeToDependencies(getInstalledDependenciesTree(dir));
};

// (appears to not be called)
var getShrinkwrappedDependencies = function (dir) {
  return treeToDependencies(getShrinkwrappedDependenciesTree(dir));
};

var installNpmModule = function (name, version, dir) {
  ensureConnected();

  var installArg = utils.isUrlWithSha(version)
    ? version : (name + "@" + version);

  // We don't use npm.commands.install since we couldn't figure out
  // how to silence all output (specifically the installed tree which
  // is printed out with `console.log`)
  //
  // We used to use --force here, because the NPM cache is broken! See
  // https://github.com/npm/npm/issues/3265 Basically, switching
  // back and forth between a tarball fork of version X and the real
  // version X could confuse NPM. But the main reason to use tarball
  // URLs is to get a fork of the latest version with some fix, so
  // it was easy to trigger this!
  //
  // We now use a forked version of npm with our PR
  // https://github.com/npm/npm/pull/5137 to work around this.
  var result = runNpmCommand(["install", installArg], dir);

  if (! result.success) {
    var pkgNotFound = "404 '" + utils.quotemeta(name) +
          "' is not in the npm registry";
    var versionNotFound = "version not found: " + utils.quotemeta(name) +
          '@' + utils.quotemeta(version);
    if (result.stderr.match(new RegExp(pkgNotFound))) {
      buildmessage.error("there is no npm package named '" + name + "'");
    } else if (result.stderr.match(new RegExp(versionNotFound))) {
      buildmessage.error(name + " version " + version + " " +
                         "is not available in the npm registry");
    } else {
      // XXX include this in the buildmessage.error instead
      runLog.log(result.stderr);
      buildmessage.error("couldn't install npm package");
    }

    // Recover by returning false from updateDependencies
    throw new NpmFailure;
  }

  if (process.platform !== "win32") {
    // If we are on a unixy file system, we should not build a package that
    // can't be used on Windows.

    var pathsWithColons = files.findPathsWithRegex(".", new RegExp(":"),
      { cwd: files.pathJoin(dir, "node_modules") });

    if (pathsWithColons.length) {
      var firstTen = pathsWithColons.slice(0, 10);
      if (pathsWithColons.length > 10) {
        firstTen.push("... " + (pathsWithColons.length - 10) +
          " paths omitted.");
      }

      buildmessage.error(
"Some filenames in your package have invalid characters.\n" +
"The following file paths in the NPM module '" + name + "' have colons, ':', which won't work on Windows:\n" +
firstTen.join("\n"));

      throw new NpmFailure;
    }
  }
};

var installFromShrinkwrap = function (dir) {
  if (! files.exists(files.pathJoin(dir, "npm-shrinkwrap.json")))
    throw new Error(
      "Can't call `npm install` without a npm-shrinkwrap.json file present");

  ensureConnected();

  // `npm install`, which reads npm-shrinkwrap.json.
  var result = runNpmCommand(["install"], dir);

  if (! result.success) {
    // XXX include this in the buildmessage.error instead
    runLog.log(result.stderr);
    buildmessage.error("couldn't install npm packages from npm-shrinkwrap");
    // Recover by returning false from updateDependencies
    throw new NpmFailure;
  }
};

// ensure we can reach http://npmjs.org before we try to install
// dependencies. `npm install` times out after more than a minute.
var ensureConnected = function () {
  try {
    httpHelpers.getUrl("http://registry.npmjs.org");
  } catch (e) {
    buildmessage.error("Can't install npm dependencies. " +
                       "Are you connected to the internet?");
    // Recover by returning false from updateDependencies
    throw new NpmFailure;
  }
};

// `npm shrinkwrap`
var shrinkwrap = function (dir) {
  // We don't use npm.commands.shrinkwrap for two reasons:
  // 1. As far as we could tell there's no way to completely silence the output
  //    (the `silent` flag isn't piped in to the call to npm.commands.ls)
  // 2. In various (non-deterministic?) cases we observed the
  //    npm-shrinkwrap.json file not being updated
  var result = runNpmCommand(["shrinkwrap"], dir);

  if (! result.success) {
    // XXX include this in the buildmessage.error instead
    runLog.log(result.stderr);
    buildmessage.error("couldn't run `npm shrinkwrap`");
    // Recover by returning false from updateDependencies
    throw new NpmFailure;
  }

  minimizeShrinkwrap(dir);
};

// The shrinkwrap file format contains a lot of extra data that can
// change as you re-run the NPM-update process without actually
// affecting what is installed. This step trims everything but the
// most important bits from the file, so that the file doesn't change
// unnecessary.
//
// This is based on an analysis of install.js in the npm module:
//   https://github.com/isaacs/npm/blob/master/lib/install.js
// It appears that the only things actually read from a given
// dependency are its sub-dependencies and a single version, which is
// read by the readWrap function; and furthermore, we can just put all
// versions in the "version" field.
var minimizeShrinkwrap = function (dir) {
  var topLevel = getShrinkwrappedDependenciesTree(dir);
  var minimized = minimizeDependencyTree(topLevel);

  files.writeFile(
    files.pathJoin(dir, 'npm-shrinkwrap.json'),
    // Matches the formatting done by 'npm shrinkwrap'.
    JSON.stringify(minimized, null, 2) + '\n');
};

// Reduces a dependency tree (as read from a just-made npm-shrinkwrap.json or
// from npm ls --json) to just the versions we want. Returns an object that does
// not share state with its input
var minimizeDependencyTree = function (tree) {
  var minimizeModule = function (module) {
    var version;
    if (module.resolved &&
        !module.resolved.match(/^https:\/\/registry.npmjs.org\//)) {
      version = module.resolved;
    } else if (utils.isUrlWithSha(module.from)) {
      version = module.from;
    } else {
      version = module.version;
    }
    var minimized = {version: version};

    if (module.dependencies) {
      minimized.dependencies = {};
      _.each(module.dependencies, function (subModule, name) {
        minimized.dependencies[name] = minimizeModule(subModule);
      });
    }
    return minimized;
  };

  var newTopLevelDependencies = {};
  _.each(tree.dependencies, function (module, name) {
    newTopLevelDependencies[name] = minimizeModule(module);
  });
  return {dependencies: newTopLevelDependencies};
};

var logUpdateDependencies = function (packageName, npmDependencies) {
  runLog.log(packageName + ': updating npm dependencies -- ' +
             _.keys(npmDependencies).join(', ') + '...');
};

exports.runNpmCommand = runNpmCommand;