// All Tomorrow's Parties -- data model
// Loaded on both the client and the server

///////////////////////////////////////////////////////////////////////////////
// Parties

/*
  Each party is represented by a document in the Parties collection:
    owner: user id
    x, y: Number (screen coordinates in the interval [0, 1])
    title, description: String
    public: Boolean
    invited: Array of user id's that are invited (only if !public)
    rsvps: Array of objects like {user: userId, rsvp: "yes"} (or "no"/"maybe")
*/
Parties = new Mongo.Collection("parties");

Parties.allow({
  insert: function (userId, party) {
    return false; // no cowboy inserts -- use createParty method
  },
  update: function (userId, party, fields, modifier) {
    if (userId !== party.owner)
      return false; // not the owner

    var allowed = ["title", "description", "x", "y"];
    if (_.difference(fields, allowed).length)
      return false; // tried to write to forbidden field

    // A good improvement would be to validate the type of the new
    // value of the field (and if a string, the length.) In the
    // future Meteor will have a schema system to makes that easier.
    return true;
  },
  remove: function (userId, party) {
    // You can only remove parties that you created and nobody is going to.
    return party.owner === userId && attending(party) === 0;
  }
});

attending = function (party) {
  return (_.groupBy(party.rsvps, 'rsvp').yes || []).length;
};

var NonEmptyString = Match.Where(function (x) {
  check(x, String);
  return x.length !== 0;
});

var Coordinate = Match.Where(function (x) {
  check(x, Number);
  return x >= 0 && x <= 1;
});

createParty = function (options) {
  var id = Random.id();
  Meteor.call('createParty', _.extend({ _id: id }, options));
  return id;
};

Meteor.methods({
  // options should include: title, description, x, y, public
  createParty: function (options) {
    check(options, {
      title: NonEmptyString,
      description: NonEmptyString,
      x: Coordinate,
      y: Coordinate,
      public: Match.Optional(Boolean),
      _id: Match.Optional(NonEmptyString)
    });

    if (options.title.length > 100)
      throw new Meteor.Error(413, "Title too long");
    if (options.description.length > 1000)
      throw new Meteor.Error(413, "Description too long");
    if (! this.userId)
      throw new Meteor.Error(403, "You must be logged in");

    var id = options._id || Random.id();
    Parties.insert({
      _id: id,
      owner: this.userId,
      x: options.x,
      y: options.y,
      title: options.title,
      description: options.description,
      public: !! options.public,
      invited: [],
      rsvps: []
    });
    return id;
  },

  invite: function (partyId, userId) {
    check(partyId, String);
    check(userId, String);
    var party = Parties.findOne(partyId);
    if (! party || party.owner !== this.userId)
      throw new Meteor.Error(404, "No such party");
    if (party.public)
      throw new Meteor.Error(400,
                             "That party is public. No need to invite people.");
    if (userId !== party.owner && ! _.contains(party.invited, userId)) {
      Parties.update(partyId, { $addToSet: { invited: userId } });

      var from = contactEmail(Meteor.users.findOne(this.userId));
      var to = contactEmail(Meteor.users.findOne(userId));
      if (Meteor.isServer && to) {
        // This code only runs on the server. If you didn't want clients
        // to be able to see it, you could move it to a separate file.
        Email.send({
          from: "noreply@example.com",
          to: to,
          replyTo: from || undefined,
          subject: "PARTY: " + party.title,
          text:
"Hey, I just invited you to '" + party.title + "' on All Tomorrow's Parties." +
"\n\nCome check it out: " + Meteor.absoluteUrl() + "\n"
        });
      }
    }
  },

  rsvp: function (partyId, rsvp) {
    check(partyId, String);
    check(rsvp, String);
    if (! this.userId)
      throw new Meteor.Error(403, "You must be logged in to RSVP");
    if (! _.contains(['yes', 'no', 'maybe'], rsvp))
      throw new Meteor.Error(400, "Invalid RSVP");
    var party = Parties.findOne(partyId);
    if (! party)
      throw new Meteor.Error(404, "No such party");
    if (! party.public && party.owner !== this.userId &&
        !_.contains(party.invited, this.userId))
      // private, but let's not tell this to the user
      throw new Meteor.Error(403, "No such party");

    var rsvpIndex = _.indexOf(_.pluck(party.rsvps, 'user'), this.userId);
    if (rsvpIndex !== -1) {
      // update existing rsvp entry

      if (Meteor.isServer) {
        // update the appropriate rsvp entry with $
        Parties.update(
          {_id: partyId, "rsvps.user": this.userId},
          {$set: {"rsvps.$.rsvp": rsvp}});
      } else {
        // minimongo doesn't yet support $ in modifier. as a temporary
        // workaround, make a modifier that uses an index. this is
        // safe on the client since there's only one thread.
        var modifier = {$set: {}};
        modifier.$set["rsvps." + rsvpIndex + ".rsvp"] = rsvp;
        Parties.update(partyId, modifier);
      }

      // Possible improvement: send email to the other people that are
      // coming to the party.
    } else {
      // add new rsvp entry
      Parties.update(partyId,
                     {$push: {rsvps: {user: this.userId, rsvp: rsvp}}});
    }
  }
});

///////////////////////////////////////////////////////////////////////////////
// Users

displayName = function (user) {
  if (user.profile && user.profile.name)
    return user.profile.name;
  return user.emails[0].address;
};

var contactEmail = function (user) {
  if (user.emails && user.emails.length)
    return user.emails[0].address;
  if (user.services && user.services.facebook && user.services.facebook.email)
    return user.services.facebook.email;
  return null;
};


noReleaseCheck: options['no-release-check'] || process.env.METEOR_NO_RELEASE_CHECK,

Plugin.registerMinifier({
  extensions: ["js"],
}, function () {
  var minifier = new Minifier();
  return minifier;
});

function Minifier () {};

Minifier.prototype.processFilesForBundle = function (files) { };


--release        Specify the release of Meteor to use.

--verbose        Print all output from builds logs.

--no-lint        Don't run linters used by the app on every rebuild.

--allow-incompatible-update   Allow packages in your project to be upgraded or

downgraded to versions that are potentially incompatible with

the current versions, if required to satisfy all package

--no-release-check            Don't run the release updater to check for new releases.

const _ = require('underscore');

const files = require('../fs/files.js');
const buildmessage = require('../utils/buildmessage.js');
const utils = require('../utils/utils.js');
const runLog = require('./run-log.js');
const release = require('../packaging/release.js');

const Console = require('../console/console.js').Console;

const Proxy = require('./run-proxy.js').Proxy;
const Selenium = require('./run-selenium.js').Selenium;
const AppRunner = require('./run-app.js').AppRunner;
const MongoRunner = require('./run-mongo.js').MongoRunner;
const Updater = require('./run-updater.js').Updater;

class Runner {
  constructor({
    appHost,
    appPort,
    banner,
    disableOplog,
    cordovaRunner,
    mongoUrl,
    onFailure,
    oplogUrl,
    projectContext,
    proxyHost,
    proxyPort,
    quiet,
    rootUrl,
    selenium,
    seleniumBrowser,
    ...optionsForAppRunner
  }) {
    const self = this;
    self.projectContext = projectContext;

    if (typeof proxyPort === 'undefined') {
      throw new Error('no proxyPort?');
    }

    const listenPort = proxyPort;
    const mongoPort = parseInt(listenPort, 10) + 1;
    self.specifiedAppPort = appPort;
    self.regenerateAppPort();

    self.stopped = false;
    self.quiet = quiet;
    self.banner = banner || files.convertToOSPath(
      files.prettyPath(self.projectContext.projectDir)
    );

    if (rootUrl) {
      self.rootUrl = rootUrl;
    } else {
      self.rootUrl = utils.formatUrl({
        protocol: 'http',
        hostname: proxyHost || "localhost",
        port: listenPort,
      });
    }

    self.proxy = new Proxy({
      listenPort,
      listenHost: proxyHost,
      proxyToPort: self.appPort,
      proxyToHost: appHost,
      onFailure
    });

    self.mongoRunner = null;
    if (mongoUrl) {
      oplogUrl = disableOplog ? null : oplogUrl;
    } else {
      self.mongoRunner = new MongoRunner({
        projectLocalDir: self.projectContext.projectLocalDir,
        port: mongoPort,
        onFailure,
        // For testing mongod failover, run with 3 mongod if the env var is
        // set. Note that data is not preserved from one run to the next.
        multiple: !!process.env.METEOR_TEST_MULTIPLE_MONGOD_REPLSET
      });

      mongoUrl = self.mongoRunner.mongoUrl();
      oplogUrl = disableOplog ? null : self.mongoRunner.oplogUrl();
    }

    self.updater = new Updater();

    self.appRunner = new AppRunner({
      ...optionsForAppRunner,
      projectContext: self.projectContext,
      port: self.appPort,
      listenHost: appHost,
      mongoUrl,
      oplogUrl,
      rootUrl: self.rootUrl,
      proxy: self.proxy,
      noRestartBanner: self.quiet,
      cordovaRunner: cordovaRunner
    });

    self.selenium = null;
    if (selenium) {
      self.selenium = new Selenium({
        runner: self,
        browser: seleniumBrowser
      });
    }
  }

  // XXX leave a pidfile and check if we are already running
  start() {
    const self = this;

    self.proxy.start();

    // print the banner only once we've successfully bound the port
    if (! self.quiet && ! self.stopped) {
      runLog.log("[[[[[ " + self.banner + " ]]]]]\n");
      runLog.log("Started proxy.",  { arrow: true });
    }

    var unblockAppRunner = self.appRunner.makeBeforeStartPromise();
    self._startMongoAsync().then(unblockAppRunner);

    if (! self.stopped) {
      self.updater.start();
    }

    if (! self.stopped) {
      buildmessage.enterJob({ title: "starting your app" }, function () {
        self.appRunner.start();
      });
      if (! self.quiet && ! self.stopped) {
        runLog.log("Started your app.",  { arrow: true });
      }
    }

    if (! self.stopped && ! self.quiet) {
      runLog.log("");
      runLog.log("App running at: " + self.rootUrl,  { arrow: true });

      if (process.platform === "win32") {
        runLog.log("   Type Control-C twice to stop.");
        runLog.log("");
      }
    }

    if (self.selenium && ! self.stopped) {
      buildmessage.enterJob({ title: "starting Selenium" }, function () {
        self.selenium.start();
      });
      if (! self.quiet && ! self.stopped) {
        runLog.log("Started Selenium.", { arrow: true });
      }
    }

    // XXX It'd be nice to (cosmetically) handle failure better. Right
    // now we overwrite the "starting foo..." message with the
    // error. It'd be better to overwrite it with "failed to start
    // foo" and then print the error.
  }

  async _startMongoAsync() {
    if (! this.stopped && this.mongoRunner) {
      this.mongoRunner.start();
      if (! this.stopped && ! this.quiet) {
        runLog.log("Started MongoDB.", { arrow: true });
      }
    }
  }

  // Idempotent
  stop() {
    const self = this;
    if (self.stopped) {
      return;
    }

    self.stopped = true;
    self.proxy.stop();
    self.updater.stop();
    self.mongoRunner && self.mongoRunner.stop();
    self.appRunner.stop();
    self.selenium && self.selenium.stop();
    // XXX does calling this 'finish' still make sense now that runLog is a
    // singleton?
    runLog.finish();
  }

  // Call this whenever you want to regenerate the app's port (if it is not
  // explicitly specified by the user).
  //
  // Rationale: if we randomly chose a port that's in use and the app failed to
  // listen on it, we should try a different port when we restart the app!
  regenerateAppPort() {
    const self = this;
    if (self.specifiedAppPort) {
      self.appPort = self.specifiedAppPort;
    } else {
      self.appPort = require('../utils/utils.js').randomPort();
    }
    if (self.proxy) {
      self.proxy.proxyToPort = self.appPort;
    }
    if (self.appRunner) {
      self.appRunner.port = self.appPort;
    }
  }
}

// Run the app and all of its associated processes. Runs (and does not
// return) until an unrecoverable failure happens. Logs to
// stdout. Returns a suggested exit code.
//
// If 'once' is set, run the app process exactly once and pass through
// its exit code. Return an exit code of 255 if the app process was
// killed by a signal and 254 if the app process could not start
// (build failure, invalid program name, database couldn't start, and
// so on).
//
// If the 'once' option is not set, the default, restart the app
// process if it crashes or if source files change. (Non-app
// processes, such as the database, are always restarted as
// necessary.) The function will only return if there is an
// unrecoverable error, which generally means an error that could not
// be fixed by source code changes (such as the database refusing to
// run), but also currently includes Meteor version mismatches. So the
// exit code will always be 254 because in all other cases we'll
// persevere.
//
// Options:
//
// - proxyPort: the port to connect to to access the application (we will
//   run a proxy here that proxies to the actual app process). required
// - buildOptions: 'buildOptions' argument to bundler.bundle()
// - settingsFile: path to file containing deploy-time settings
// - once: see above
// - banner: replace the application path that is normally printed on
//   startup with an arbitrary string (eg, 'Tests')
// - rootUrl: tell the app that traffic at this URL will be routed to
//   it at '/' (used by the app to construct absolute URLs)
// - disableOplog: don't use oplog tailing
// - mongoUrl: don't start a mongo process; instead use the mongo at
//   this mongo URL
// - oplogUrl: URL of the mongo oplog to use. if mongoUrl isn't
//   set (we're starting a mongo) a default will be provided, but can
//   be overridden. if mongoUrl is set, you must set this or you don't
//   get oplog tailing.
// - recordPackageUsage: (default true) if set to false, don't send
//   information about packages used by this app to the package stats
//   server.
exports.run = function (options) {
  var runOptions = _.clone(options);
  var once = runOptions.once;
  delete runOptions.once;

  var promise = new Promise(function (resolve) {
    runOptions.onFailure = function () {
      // Ensure that runner stops now. You might think this is unnecessary
      // because the runner is stopped immediately after promise.await(), but if
      // the failure happens while runner.start() is still running, we want the
      // rest of start to stop, and it's not like resolve() magically makes
      // us jump to a promise.await() that hasn't happened yet!.
      runner.stop();
      resolve({ outcome: 'failure' });
    };

    runOptions.onRunEnd = function (result) {
      if (once ||
          result.outcome === "conflicting-versions" ||
          result.outcome === "wrong-release" ||
          result.outcome === "outdated-cordova-platforms" ||
          result.outcome === "outdated-cordova-plugins" ||
          (result.outcome === "terminated" &&
           result.signal === undefined && result.code === undefined)) {
        resolve(result);
        return false;  // stop restarting
      }
      runner.regenerateAppPort();
      return true;  // restart it
    };
  });

  runOptions.watchForChanges = ! once;
  runOptions.quiet = false;

  // Ensure process.env.NODE_ENV matches the build mode, with the following precedence:
  // 1. Passed in build mode (if development or production)
  // 2. Existing process.env.NODE_ENV (if it's valid)
  // 3. Default to development (in both cases) otherwise

  // NOTE: because this code only runs when using `meteor run` or `meteor test[-packages`,
  // We *don't* end up defaulting NODE_ENV in this way when bundling/deploying.
  // In those cases, it will default to "production" in packages/meteor/*_env.js

  // We *override* NODE_ENV if build mode is one of these values
  let buildMode = runOptions.buildOptions.buildMode;
  if (buildMode === "development" || buildMode === "production") {
    process.env.NODE_ENV = buildMode;
  }

  let nodeEnv = process.env.NODE_ENV;
  // We *never* override buildMode (it can be "test")
  if (!buildMode) {
    if (nodeEnv === "development" || nodeEnv === "production") {
      runOptions.buildOptions.buildMode = nodeEnv;
    } else {
      runOptions.buildOptions.buildMode = "development";
    }
  }

  if (!nodeEnv) {
    process.env.NODE_ENV = "development";
  }

  var runner = new Runner(runOptions);
  runner.start();
  var result = promise.await();
  runner.stop();

  if (result.outcome === "conflicting-versions") {
    Console.error(
      "The constraint solver could not find a set of package versions to",
      "use that would satisfy the constraints of .meteor/versions and",
      ".meteor/packages. This could be caused by conflicts in",
      ".meteor/versions, conflicts in .meteor/packages, and/or",
      "inconsistent changes to the dependencies in local packages.");
    return 254;
  }

  if (result.outcome === "outdated-cordova-plugins") {
    Console.error("Your app's Cordova plugins have changed.");
    Console.error("Restart meteor to use the new set of plugins.");
    return 254;
  }

  if (result.outcome === "outdated-cordova-platforms") {
    Console.error("Your app's platforms have changed.");
    Console.error("Restart meteor to use the new set of platforms.");
    return 254;
  }

  if (result.outcome === "wrong-release") {
    if (once) {
      // We lost a race where the user ran 'meteor update' and 'meteor
      // run --once' simultaneously.
      throw new Error("wrong release?");
    }

    // If the user did not specify a --release on the command line,
    // and simultaneously runs `meteor update` during this run, just
    // exit and let them restart the run. (We can do something fancy
    // like allowing this to work if the tools version didn't change,
    // or even springboarding if the tools version does change, but
    // this (which prevents weird errors) is a start.)
    var from = release.current.getDisplayName();
    var to = result.displayReleaseNeeded;
    Console.error(
      "Your app has been updated to " + to + " from " + from + ".",
      "Restart meteor to use the new release.");
    return 254;
  }

  if (result.outcome === "failure" ||
      (result.outcome === "terminated" &&
       result.signal === undefined && result.code === undefined)) {
    // Fatal problem with something other than the app process. An
    // explanation should already have been logged.
    return 254;
  }

  if (once && result.outcome === "bundle-fail") {
    Console.arrowError("Build failed:\n\n" +
                       result.errors.formatMessages());
    return 254;
  }

  if (once && result.outcome === "terminated") {
    if (result.signal) {
      Console.error("Killed (" + result.signal + ")");
      return 255;
    } else if (typeof result.code === "number") {
      // We used to print 'Your application is exiting' here, but that
      // seems unnecessarily chatty? once mode is otherwise silent
      return result.code;
    } else {
      // If there is neither a code nor a signal, it means that we
      // failed to start the process. We logged the reason. Probably a
      // bad program name.
      return 254;
    }
  }

  throw new Error("unexpected outcome " + result.outcome);
};


LIBRARY "WiXHelper"

EXPORTS

;  DROP_QCloudDatabase
;  CREATE_QCloudDatabase

Download_MeteorPackage
Extract_MeteorFiles
BulkRemoveMeteorFiles


// _assign is like _.extend or the upcoming Object.assign.
// Copy src's own, enumerable properties onto tgt and return
// tgt.
var _hasOwnProperty = Object.prototype.hasOwnProperty;
var _assign = function (tgt, src) {
  for (var k in src) {
    if (_hasOwnProperty.call(src, k))
      tgt[k] = src[k];
  }
  return tgt;
};

HTMLTools.TemplateTag = function (props) {
  if (! (this instanceof HTMLTools.TemplateTag))
    // called without `new`
    return new HTMLTools.TemplateTag;

  if (props)
    _assign(this, props);
};

_assign(HTMLTools.TemplateTag.prototype, {
  constructorName: 'HTMLTools.TemplateTag',
  toJS: function (visitor) {
    return visitor.generateCall(this.constructorName,
                                _assign({}, this));
  }
});


self.banner = banner || files.convertToOSPath(


var _ = require('underscore');

var config = require('../meteor-services/config.js');
var httpHelpers = require('../utils/http-helpers.js');
var release = require('./release.js');
var files = require('../fs/files.js');
var utils = require('../utils/utils.js');
var buildmessage = require('../utils/buildmessage.js');
var compiler = require('../isobuild/compiler.js');
var authClient = require('../meteor-services/auth-client.js');
var catalog = require('./catalog/catalog.js');
var projectContextModule = require('../project-context.js');
var colonConverter = require('../utils/colon-converter.js');
var Profile = require('../tool-env/profile.js').Profile;

// Opens a DDP connection to a package server. Loads the packages needed for a
// DDP connection, then calls DDP connect to the package server URL in config,
// using a current user-agent header composed by http-helpers.js.
var openPackageServerConnection = function (packageServerUrl) {
  var serverUrl = packageServerUrl || config.getPackageServerUrl();
  return authClient.openServiceConnection(serverUrl);
};

// We don't let the user upload a blank README for UX reasons, but we would
// prefer that the server move to a world with 'readme' files for everything in
// the future. As a way to breach these interfaces, for now, we are going to
// upload blank documentation files when null docs are requested.
//
// This function generates a Readme object for a blank readme file, as well as
// the file itself.
var generateBlankReadme = function () {
  return {
    contents: "",
    excerpt: "",
    hash: files.blankHash
  };
};

// Save a readme file to a temporary path.
var saveReadmeToTmp = function (readmeInfo) {
  var tempReadmeDir = files.mkdtemp('readme');
  var readmePath = files.pathJoin(tempReadmeDir, "Readme.md");
  files.writeFileAtomically(readmePath, readmeInfo.contents);
  return readmePath;
};

// Given a connection, makes a call to the package server.  (Checks to see if
// the connection is connected, and reconnects if needed -- a workaround for
// the fact that connections in the tool do not reconnect)
exports.callPackageServer = function (conn, ...args) {
  // XXX This is broken since it doesn't actually replace the conn in the
  // caller, so it'll happen on every subsequent call
  if (!conn.connected) {
    conn.close();
    conn = exports.loggedInPackagesConnection();
  }
  return conn.call(...args);
};

var callPackageServerBM = exports.callPackageServerBM = function (...args) {
  buildmessage.assertInJob();
  try {
    return exports.callPackageServer.apply(null, args);
  } catch (e) {
    buildmessage.error(e.reason || e.message);
    return null;
  }
};

// Requests and returns one page of new package data that we haven't cached on
// disk. We assume that data is cached chronologically, so essentially, we are
// asking for a diff from the last time that we did this.
// Takes in:
// - conn: the connection to use (does not have to be logged in)
// - syncToken: a syncToken object to be sent to the server that
//   represents the last time that we talked to the server.
// - options:
//    - useShortPages (Boolean). Ask the server for pages of ~3 records
//      instead of ~100, for testing pagination.
//
// Returns an object, containing the following fields:
//  - syncToken: a new syncToken object, that we can pass to the server in the future.
//  - collections: an object keyed by the name of server collections, with the
//    records as an array of javascript objects.
var loadRemotePackageData = function (conn, syncToken, options) {
  options = options || {};

  // Did we get disconnected between retries somehow? Then we should open a new
  // connection. We shouldn't use the callPackageServer method here though,
  // since we don't need to authenticate.
  if (!conn.connected) {
    conn.close();
    conn =  openPackageServerConnection();
  }

  var syncOpts = {};
  if (options && options.useShortPages) {
    syncOpts.shortPagesForTest = options.useShortPages;
  }
  if (options && options.compressCollections) {
    syncOpts.compressCollections = options.compressCollections;
  }
  return conn.call('syncNewPackageData', syncToken, syncOpts);
};

// Contacts the package server to get the latest diff and writes changes to
// disk.
//
// Takes in the dataStore, which is an example of the remote catalog. Contacts
// the package server and updates the sql database with the most recent
// information.
//
// Returns null if contacting the server times out, or an object with the
// following keys:
//     resetData : true if we should reset the database, otherwise false.
//     connectionFailed: true if we failed to connect to the server.
//
// options can include:
//  - packageStorageFile: String. The file to write the data to (overrides
//    `config.getPackageStorage()`)
//  - packageServerUrl: String. The package server (overrides
//    `config.getPackageServerUrl()`)
//  - useShortPages: Boolean. Request short pages of ~3 records from the
//    server, instead of ~100 that it would send otherwise
exports.updateServerPackageData = function (dataStore, options) {
  return buildmessage.enterJob('updating package catalog', function () {
    return _updateServerPackageData(dataStore, options);
  });
};

var _updateServerPackageData = function (dataStore, options) {
  var self = this;
  options = options || {};
  if (dataStore === null) {
    throw Error("Data store expected");
  }

  var done = false;
  var ret = {resetData: false};

  // For now, we don't have a great progress metric, so just use a spinner
  var useProgressbar = false;

  var start = undefined;
  // Guess that we're about an hour behind, as an opening guess
  var state = { current: 0, end: 60 * 60 * 1000, done: false};
  useProgressbar && buildmessage.reportProgress(state);

  var conn = openPackageServerConnection(options.packageServerUrl);

  // Provide some progress indication for connection
  // XXX though it is just a hack
  state.current = 1;
  useProgressbar && buildmessage.reportProgress(state);

  var getSomeData = function () {
    var syncToken = dataStore.getSyncToken() || {format: "1.1"};

    if (!start) {
      start = {};
      start.builds = syncToken.builds;
      start.versions = syncToken.versions;
      state.end = (Date.now() - start.builds) + (Date.now() - start.versions);
    }
    // XXX: This is a hack... syncToken should have a % done
    state.current =
      (syncToken.builds - start.builds) +
      (syncToken.versions - start.versions);
    useProgressbar && buildmessage.reportProgress(state);

    var compress = !!process.env.METEOR_CATALOG_COMPRESS_RPCS;

    // (loadRemotePackageData may throw)
    var remoteData = loadRemotePackageData(conn, syncToken, {
      useShortPages: options.useShortPages,
      compressCollections: compress
    });

    // Is the remote server telling us to ignore everything we've heard before?
    // OK, we can do that.
    if (remoteData.resetData) {
      dataStore.reset();
      // The caller may want to take this as a cue to delete packages from the
      // tropohouse.
      ret.resetData = true;
    }

    if (remoteData.collectionsCompressed) {
      var zlib = require('zlib');
      var colsGzippedBuffer = new Buffer(
        remoteData.collectionsCompressed, 'base64');
      var colsJSON = new Promise((resolve, reject) => {
        zlib.gunzip(colsGzippedBuffer, (err, res) => {
          err ? reject(err) : resolve(res);
        });
      }).await();
      remoteData.collections = JSON.parse(colsJSON);
      delete remoteData.collectionsCompressed;
    }

    // We always write to the data store; the fact there is no data is itself
    // data!  e.g. the last-refresh timestamp
    var syncComplete =
          _.isEqual(remoteData.collections, {}) || remoteData.upToDate;
    dataStore.insertData(remoteData, syncComplete);

    // If there is no new data from the server, don't bother writing things to
    // disk (unless we were just told to reset everything).
    if (!remoteData.resetData && _.isEqual(remoteData.collections, {})) {
      done = true;
      return;
    }

    if (remoteData.upToDate) {
      done = true;
    }
  };

  try {
    while (!done) {
      getSomeData();
    }
  } finally {
    conn.close();
  }

  return ret;
};

_updateServerPackageData = Profile('package-client _updateServerPackageData',
                                   _updateServerPackageData);

// Returns a logged-in DDP connection to the package server, or null if
// we cannot log in. If an error unrelated to login occurs
// (e.g. connection to package server times out), then it will be
// thrown.
exports.loggedInPackagesConnection = function () {
  return authClient.loggedInConnection(
    config.getPackageServerUrl(),
    config.getPackageServerDomain(),
    "package-server"
  );
};

// XXX this is missing a few things. In retrospect a better approach here might
//     be to actually make "save source somewhere else" or perhaps "add source
//     to tarball" be part of the package build itself...
var bundleSource = function (isopack, includeSources, packageDir) {
  buildmessage.assertInJob();

  var name = isopack.name;

  var tempDir = files.mkdtemp('build-source-package-');
  var packageTarName = name + '-' + isopack.version + '-source';
  var dirToTar = files.pathJoin(tempDir, 'source',
    colonConverter.convert(packageTarName));
  // XXX name probably needs to be escaped for windows?
  // XXX note that publish-for-arch thinks it knows how this tarball is laid
  //     out, which is a bit of a shame
  var sourcePackageDir = files.pathJoin(dirToTar, colonConverter.convert(name));
  if (! files.mkdir_p(sourcePackageDir)) {
    buildmessage.error('Failed to create temporary source directory: ' +
                       sourcePackageDir);
    return null;
  }

  // We copy source files into a temp directory and then tar up the temp
  // directory. It would be great if we could avoid the copy, but as far
  // as we can tell, this is the only way to get a tarball with the
  // directory structure that we want (<package name>-<version-source/
  // at the top level).
  _.each(includeSources, function (f) {
    const from = files.pathJoin(packageDir, f);
    const to = files.pathJoin(sourcePackageDir, f);
    if (files.exists(from)) {
      files.copyFile(from, to);
    }
  });

  // Write a package map to `.versions` inside the source tarball.  Note that
  // this differs in two ways from the `.versions` file that is maintained
  // inside standalone packages by 'meteor publish':
  //  (a) It only contains the direct, directly implied, and linked-into-plugin
  //      dependencies of the package, not all transitive dependencies.
  //  (b) It is ALWAYS put into the source tarball, even if the package came
  //      from inside an app, whereas the package-source-tree .versions file
  //      is only used for standalone packages
  var packageMapFilename = files.pathJoin(sourcePackageDir, '.versions');
  if (files.exists(packageMapFilename)) {
    throw Error(".versions file already exists? " + packageMapFilename);
  }
  var pluginProviderPackageMap = isopack.pluginProviderPackageMap;
  if (! pluginProviderPackageMap) {
    throw Error("no pluginProviderPackageMap on isopack?");
  }
  var packageMapFile = new projectContextModule.PackageMapFile({
    filename: packageMapFilename
  });
  packageMapFile.write(pluginProviderPackageMap);

  // We put this inside the temp dir because mkdtemp makes sure that the
  // temp dir gets cleaned up on process exit, so we don't have to worry
  // about cleaning up our tarball (or our copied source files)
  // ourselves.
  var sourceTarball = files.pathJoin(tempDir, packageTarName + '.tgz');
  files.createTarball(dirToTar, sourceTarball);

  var tarballHash = files.fileHash(sourceTarball);
  var treeHash = files.treeHash(dirToTar);

  return {
    sourceTarball: sourceTarball,
    tarballHash: tarballHash,
    treeHash: treeHash
  };
};

// Uploads a file at a filepath to the HTTP put URL.
//
// Returns true on success and false on failure.
var uploadFile = function (putUrl, filepath) {
  buildmessage.assertInJob();
  var size = files.stat(filepath).size;
  var rs = files.createReadStream(filepath);
  try {
    // Use getUrl instead of request, to throw on 4xx/5xx.
    httpHelpers.getUrl({
      method: 'PUT',
      url: putUrl,
      headers: {
        'content-length': size,
        'content-type': 'application/octet-stream',
        'x-amz-acl': 'public-read'
      },
      bodyStream: rs,
      bodyStreamLength: size
    });
  } catch (err) {
    // XXX: getUrl's error handling is terrible and we should fix it there.
    buildmessage.error(typeof err === "string" ? err : err.error.toString());
    return false;
  } finally {
    rs.close();
  }
  return true;
};

exports.uploadFile = uploadFile;

export function bundleBuild(isopack, isopackCache) {
  buildmessage.assertInJob();

  var tempDir = files.mkdtemp('bp-');
  var packageTarName = isopack.tarballName();
  var tarInputDir = files.pathJoin(tempDir, packageTarName);

  // Note that we do need to do this even though we already have the isopack on
  // disk in an IsopackCache, because we don't want to include
  // isopack-buildinfo.json. (We don't include it because we're not passing
  // includeIsopackBuildInfo to saveToPath here.)
  isopack.saveToPath(tarInputDir, {
    // When publishing packages that don't use new registerCompiler plugins,
    // make sure that old Meteors can use it too
    includePreCompilerPluginIsopackVersions: true,
    isopackCache,
  });

  var buildTarball = files.pathJoin(tempDir, packageTarName + '.tgz');

  files.createTarball(tarInputDir, buildTarball);

  var tarballHash = files.fileHash(buildTarball);
  var treeHash = files.treeHash(tarInputDir, {
    // We don't include any package.json from an npm module in the tree hash,
    // because npm isn't super consistent about what it puts in there (eg, does
    // it include the "readme" field)? This ends up leading to spurious
    // differences. The tree hash will still notice any actual CODE changes in
    // the npm packages.
    ignore: function (relativePath) {
      var pieces = relativePath.split(files.pathSep);
      return pieces.length && _.last(pieces) === 'package.json'
        && _.contains(pieces, 'npm');
    }
  });

  return {
    buildTarball: buildTarball,
    tarballHash: tarballHash,
    treeHash: treeHash
  };
}

function createBuiltPackage(isopack, isopackCache) {
  buildmessage.assertInJob();
  var name = isopack.name;

  // Note: we really want to do this before createPackageBuild, because the URL
  // we get from createPackageBuild will expire!
  var bundleResult;
  buildmessage.enterJob("bundling build for " + name, function () {
    bundleResult = bundleBuild(isopack, isopackCache);
  });
  if (buildmessage.jobHasMessages()) {
    return;
  }

  return bundleResult;
}

var publishBuiltPackage = function (conn, isopack, bundleResult) {
  buildmessage.assertInJob();
  var name = isopack.name;

  var uploadInfo;
  buildmessage.enterJob('creating package build for ' + name, function () {
    uploadInfo = callPackageServerBM(conn, 'createPackageBuild', {
      packageName: isopack.name,
      version: isopack.version,
      buildArchitectures: isopack.buildArchitectures()
    });
  });
  if (buildmessage.jobHasMessages()) {
    return;
  }

  buildmessage.enterJob("uploading build", function () {
    uploadFile(uploadInfo.uploadUrl,
               bundleResult.buildTarball);
  });
  if (buildmessage.jobHasMessages()) {
    return;
  }

  buildmessage.enterJob('publishing package build for ' + name, function () {
    callPackageServerBM(conn, 'publishPackageBuild',
                        uploadInfo.uploadToken,
                        bundleResult.tarballHash,
                        bundleResult.treeHash);
  });
  if (buildmessage.jobHasMessages()) {
    return;
  }
};

export function createAndPublishBuiltPackage(conn, isopack, isopackCache) {
  publishBuiltPackage(
    conn,
    isopack,
    createBuiltPackage(isopack, isopackCache),
  );
}

// Handle an error thrown on trying to connect to the package server.
exports.handlePackageServerConnectionError = function (error) {
  authClient.handleConnectionError(error, "package server");
};

// Update the package metdata in the server catalog. Chane the docs,
// descriptions and the Git URL to new values.
//
// options:
// - packageSource: the packageSource for this package.
// - readmeInfo: null, or an object containing docs information for this package.
// - connection: the open, logged-in connection over which we should talk to the
//   package server. DO NOT CLOSE this connection here.
//
// Return true on success and an error code otherwise.
exports.updatePackageMetadata = function (options) {
  buildmessage.assertInJob();

  var packageSource = options.packageSource;
  var conn = options.connection;
  var readmeInfo = options.readmeInfo;

  var name = packageSource.name;
  var version = packageSource.version;

  if (! version) {
    buildmessage.error(
      "Package cannot be updated because it doesn't have a version");
    return;
  }

  // For now, documentation is optional on the client, so we have to give people
  // a way to remove it with 'documentation: null'.
  if (! readmeInfo) {
    readmeInfo = generateBlankReadme();
  }

  var dataToUpdate = {
    git: packageSource.metadata.git || "",
    description: packageSource.metadata.summary,
    longDescription: readmeInfo.excerpt
  };

  // Check that the metadata fits under the established limits, and give helpful
  // feedback.
  if (! dataToUpdate["description"]) {
    buildmessage.error("Please provide a short description to use in 'meteor search'");
    return;
  }

  if (dataToUpdate["description"] &&
      dataToUpdate["description"].length > 100) {
    buildmessage.error("Summary must be under 100 chars.");
    return;
  }

  if (dataToUpdate["longDescription"].length > 1500) {
    buildmessage.error(
      "Longform package description is too long. Meteor uses the section of " +
      "the Markdown documentation file between the first and second " +
      "headings. That section must be less than 1500 characters long.");
    return;
  }

  // Update the general metadata.
  var versionIdentifier = { packageName: name, version: version };
  buildmessage.enterJob('updating metadata', function () {
    callPackageServerBM(
      conn, "changeVersionMetadata", versionIdentifier, dataToUpdate);
  });
  if (buildmessage.jobHasMessages()) {
    return;
  }

  // Upload the new Readme.
  buildmessage.enterJob('uploading documentation', function () {
    var readmePath = saveReadmeToTmp(readmeInfo);
    var uploadInfo =
          callPackageServerBM(conn, "createReadme", versionIdentifier);
    if (! uploadInfo) {
      return;
    }
    if (! uploadFile(uploadInfo.url, readmePath)) {
      return;
    }
    callPackageServerBM(
      conn, "publishReadme", uploadInfo.uploadToken, { hash: readmeInfo.hash });
  });
  if (buildmessage.jobHasMessages()) {
    return;
  }

};

// Publish the package information into the server catalog. Create new records
// for the package (if needed), the version and the build; upload source and
// isopack.
//
// options:
// - packageSource: the packageSource for this package.
// - connection: the open, logged-in connection over which we should talk to the
//   package server. DO NOT CLOSE this connection here.
// - projectContext: the (probably temporary) ProjectContext to use. Must have\
//   already built local packages
// - new: this package is new, we should call createPackage to create a new
//   package record.
// - existingVersion: we expect the version to exist already, and for us
//   to merely be providing a new build of the same source
// - doNotPublishBuild: do not publish the build of this package.
//
// Return true on success and an error code otherwise.
exports.publishPackage = function (options) {
  buildmessage.assertInJob();
  var packageSource = options.packageSource;
  var conn = options.connection;
  var projectContext = options.projectContext;

  var name = packageSource.name;
  var version = packageSource.version;

  if (options.new && options.existingVersion) {
    throw Error("is it new or does it exist?!?");
  }

  // Check that the package name is valid.
  utils.validatePackageName(name, { useBuildmessage: true });
  if (buildmessage.jobHasMessages()) {
    return;
  }

  // Check that we have a version.
  if (! version) {
    buildmessage.error(
      "Package cannot be published because it doesn't have a version");
    return;
  }

  // Check that the version description is under the character limit. (We check
  // all string limits on the server, but this is the one that is mostly likely
  // to be wrong)
  if (! packageSource.metadata.summary) {
    buildmessage.error(
      "Please describe what your package does. Set a summary " +
        "in Package.describe in package.js.");
    return;
  }

  if (packageSource.metadata.summary.length > 100) {
    buildmessage.error("Summary must be under 100 chars.");
    return;
  }

  // Check that we are an authorized maintainer of this package.
  if (!options['new']) {
    var packRecord = catalog.official.getPackage(name);
    if (! packRecord) {
      buildmessage.error(
        'There is no package named ' + name +
          '. If you are creating a new package, use the --create flag.');
      return;
    }

    if (!exports.amIAuthorized(name, conn, false)) {
      buildmessage.error(
        'You are not an authorized maintainer of ' + name + '.  Only ' +
          'authorized maintainers may publish new versions.');
    }
  }

  // Check that our documentation exists (or we know that it doesn't) and has
  // been filled out.
  var readmeInfo = buildmessage.enterJob(
    "processing documentation",
    function () {
      return packageSource.processReadme();
  });
  if (buildmessage.jobHasMessages()) {
    return;
  }
  if (readmeInfo && (readmeInfo.hash === files.blankHash)) {
    buildmessage.error(
      "Your documentation file is blank, so users may have trouble figuring " +
      "out how to use your package. Please fill it out, or " +
      "set 'documentation: null' in your Package.describe");
    return;
  }

  if (readmeInfo && readmeInfo.excerpt.length > 1500) {
    buildmessage.error(
      "Longform package description is too long. Meteor uses the section of " +
      "the Markdown documentation file between the first and second " +
      "headings. That section must be less than 1500 characters long.");
    return;
  }

  // We don't let the user upload a blank README for UX reasons, but we would
  // prefer that the server move to a world with 'readme' files for everything
  // in the future. This helps unite these interfaces, and makes our code easier
  // to reason about in the future.
  if (! readmeInfo) {
    readmeInfo = generateBlankReadme();
  }
  var readmePath = saveReadmeToTmp(readmeInfo);

  var packageDeps = packageSource.getDependencyMetadata();

  // Check that the package does not have any unconstrained references.
  _.each(packageDeps, function(refs, label) {
    if (refs.constraint == null) {
      if (packageSource.isCore && files.inCheckout() &&
          projectContext.localCatalog.getPackage(label)) {
        // Core package is using or implying another core package,
        // without a version number.  We fill in the version number.
        // (Well, we're assuming that the other package is core and
        // not some other sort of local package.)
        var versionString =
              projectContext.localCatalog.getLatestVersion(label).version;
        // modify the constraint on this dep that will be sent to troposphere
        refs.constraint = versionString;
      } else if (label === "meteor") {
        // HACK: We are willing to publish a package with a "null"
        // constraint on the "meteor" package to troposphere.  This
        // happens for non-core packages when not running from a
        // checkout, because all packages implicitly depend on the
        // "meteor" package, but do not necessarily specify an
        // explicit version for it, and we don't have a great way to
        // choose one here.
        // XXX come back to this, especially if we are incrementing the
        // major version of "meteor".  hopefully we will have more data
        // about the package system by then.
      } else {
        buildmessage.error(
          "You must specify a version constraint for package " + label);
      }
    }
  });
  if (buildmessage.jobHasMessages()) {
    return;
  }

  var isopack = projectContext.isopackCache.getIsopack(name);
  if (! isopack) {
    throw Error("no isopack " + name);
  }

  // If we aren't able to include legacy builds in this version, make sure that
  // it has a fake dependency on isobuild:isopack-2 so that old versions of
  // Isobuild won't accidentally use it.
  if (!isopack.canWriteLegacyBuilds()
      && !_.has(packageDeps, 'isobuild:isopack-2')) {
    packageDeps['isobuild:isopack-2'] = {
      constraint: '1.0.0',
      // Arbitrary arch here; nothing other than meteor show really pays
      // attention to reference arch anymore, because we no longer pay attention
      // to the arch with the constraint in Version Solver.
      references: [{arch: 'os'}],
    };
  }

  var sourceFiles = isopack.getSourceFilesUnderSourceRoot(
    packageSource.sourceRoot);
  if (! sourceFiles) {
    throw Error("isopack doesn't know what its source files are?");
  }

  // We need to have built the test package to get all of its sources, even
  // though we're not publishing a BUILD for the test package.
  if (packageSource.testName) {
    var testIsopack = projectContext.isopackCache.getIsopack(
      packageSource.testName);
    if (! testIsopack) {
      throw Error("no testIsopack " + packageSource.testName);
    }
    var testSourceFiles = testIsopack.getSourceFilesUnderSourceRoot(
      packageSource.sourceRoot);
    if (! testSourceFiles) {
      throw Error("test isopack doesn't know what its source files are?");
    }
    sourceFiles = _.union(sourceFiles, testSourceFiles);
  }

  var sourceBundleResult;
  buildmessage.enterJob("bundling source for " + name, function () {
    sourceBundleResult = bundleSource(
      isopack, sourceFiles, packageSource.sourceRoot);
  });
  if (buildmessage.jobHasMessages()) {
    return;
  }

  // Create the package. Check that the metadata exists.
  if (options.new) {
    buildmessage.enterJob("creating package " + name, function () {
      callPackageServerBM(conn, 'createPackage', {
        name: packageSource.name
      });
    });
    if (buildmessage.jobHasMessages()) {
      return;
    }
  }

  if (options.existingVersion) {
    var existingRecord = catalog.official.getVersion(name, version);
    if (! existingRecord) {
      buildmessage.error("Version does not exist.");
      return;
    }
    if (existingRecord.source.treeHash !== sourceBundleResult.treeHash) {
      buildmessage.error("Package source differs from the existing version.");
      return;
    }

    if (! options.doNotPublishBuild) {
      createAndPublishBuiltPackage(
        conn, isopack, projectContext.isopackCache);

      if (buildmessage.jobHasMessages()) {
        return;
      }
    }

    // XXX check that we're actually providing something new?
  } else {
    var uploadInfo;
    buildmessage.enterJob("pre-publishing package " + name, function () {
      var uploadRec = {
        packageName: packageSource.name,
        version: version,
        description: packageSource.metadata.summary,
        longDescription: readmeInfo.excerpt,
        git: packageSource.metadata.git,
        compilerVersion: compiler.BUILT_BY,
        containsPlugins: packageSource.containsPlugins(),
        debugOnly: packageSource.debugOnly,
        prodOnly: packageSource.prodOnly,
        testOnly: packageSource.testOnly,
        exports: packageSource.getExports(),
        releaseName: release.current.name,
        dependencies: packageDeps
      };
      uploadInfo = callPackageServerBM(conn, 'createPackageVersion', uploadRec);
    });
    if (buildmessage.jobHasMessages()) {
      return;
    }

    // XXX If package version already exists, print a nice error message
    // telling them to try 'meteor publish-for-arch' if they want to
    // publish a new build.

    // Documentation is smaller than the source. Upload it first, to minimize
    // the chances of PUT URLs expiring. (XXX: in the far future, parallelize this)
    buildmessage.enterJob("uploading documentation", function () {
      uploadFile(uploadInfo.readmeUrl, readmePath);
    });
    if (buildmessage.jobHasMessages()) {
      return;
    }

    buildmessage.enterJob("uploading source", function () {
      uploadFile(uploadInfo.uploadUrl, sourceBundleResult.sourceTarball);
    });
    if (buildmessage.jobHasMessages()) {
      return;
    }

    if (! options.doNotPublishBuild) {
      var bundleResult = createBuiltPackage(
        isopack,
        projectContext.isopackCache,
      );

      if (buildmessage.jobHasMessages()) {
        return;
      }
    }

    var hashes = {
      tarballHash: sourceBundleResult.tarballHash,
      treeHash: sourceBundleResult.treeHash,
      readmeHash: readmeInfo.hash
    };
    buildmessage.enterJob("publishing package version", function () {
      callPackageServerBM(
        conn, 'publishPackageVersion', uploadInfo.uploadToken, hashes);
    });
    if (buildmessage.jobHasMessages()) {
      return;
    }

    if (! options.doNotPublishBuild) {
      publishBuiltPackage(conn, isopack, bundleResult);
      if (buildmessage.jobHasMessages()) {
        return;
      }
    }
  }

  return;
};

  // Call the server to ask if we are authorized to update this release or
// package. This is a way to save time before sending data to the server. It
// will mostly ignore most errors (just in case we have a flaky network connection or
// something) and let the method deal with those.
//
// If this returns FALSE, then we are NOT authorized.
// Otherwise, return true.
exports.amIAuthorized = function (name, conn, isRelease) {
  var methodName = "amIAuthorized" +
    (isRelease ? "Release" : "Package");

  try {
    exports.callPackageServer(conn, methodName, name);
  } catch (err) {
    if (err.error === 401) {
      return false;
    }

    // We don't know what this error is. Probably we can't contact the server,
    // or the like. It would be a pity to fail all operations with the server
    // just because a preliminary check fails, so return true for now.
    return true;
  }
  return true;
};


package com.cordova.empty;

import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;

import org.json.JSONArray;
import org.json.JSONException;

public class Empty extends CordovaPlugin {
    public Object onMessage(String id, Object data) {
        System.out.println("change");
        return null;
    }
    @Override
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
        return false;
    }
}


# accounts-password

A login service that enables secure password-based login. See the [project page](https://www.meteor.com/accounts) on Meteor Accounts for more details.


var unblockAppRunner = self.appRunner.makeBeforeStartPromise();

keep keep keep keep replace keep keep keep

if (!self.noReleaseCheck && ! self.stopped) {


ServerTypeof = {
  require: typeof require,
  exports: typeof exports
};


var _ = require('underscore');
var selftest = require('../tool-testing/selftest.js');
var files = require('../fs/files.js');
import { getUrl } from '../utils/http-helpers.js';
import { sleepMs } from '../utils/utils.js';

var Sandbox = selftest.Sandbox;

var MONGO_LISTENING =
  { stdout: " [initandlisten] waiting for connections on port" };

function startRun(sandbox) {
  var run = sandbox.run();
  run.match("myapp");
  run.match("proxy");
  run.tellMongo(MONGO_LISTENING);
  run.match("MongoDB");
  return run;
};

// Tests the actual cache logic used by coffeescript.
selftest.define("compiler plugin caching - coffee", () => {
  var s = new Sandbox({ fakeMongo: true });

  s.createApp("myapp", "caching-coffee");
  s.cd("myapp");
  // Ask them to print out when they build a file (instead of using it from the
  // cache) as well as when they load cache from disk.
  s.set('METEOR_COFFEESCRIPT_CACHE_DEBUG', 't');
  var run = startRun(s);

  // First program built (server or web.browser) compiles everything.
  run.match('CACHE(coffeescript): Ran (#1) on: ' + JSON.stringify(
    ['/f1.coffee', '/f2.coffee', '/f3.coffee', '/packages/local-pack/p.coffee']
  ));
  // Second program doesn't need to compile anything because compilation works
  // the same on both programs.
  run.match("CACHE(coffeescript): Ran (#2) on: []");
  // App prints this:
  run.match("Coffeescript X is 2 Y is 1 FromPackage is 4");

  s.write("f2.coffee", "share.Y = 'Y is 3'\n");
  // Only recompiles f2.
  run.match('CACHE(coffeescript): Ran (#3) on: ["/f2.coffee"]');
  // And other program doesn't even need to do f2.
  run.match("CACHE(coffeescript): Ran (#4) on: []");
  // Program prints this:
  run.match("Coffeescript X is 2 Y is 3 FromPackage is 4");

  // Force a rebuild of the local package without actually changing the
  // coffeescript file in it. This should not require us to coffee.compile
  // anything (for either program).
  s.append("packages/local-pack/package.js", "\n// foo\n");
  run.match("CACHE(coffeescript): Ran (#5) on: []");
  run.match("CACHE(coffeescript): Ran (#6) on: []");
  run.match("Coffeescript X is 2 Y is 3 FromPackage is 4");

  // But writing to the actual source file in the local package should
  // recompile.
  s.write("packages/local-pack/p.coffee", "FromPackage = 'FromPackage is 5'");
  run.match('CACHE(coffeescript): Ran (#7) on: ["/packages/local-pack/p.coffee"]');
  run.match('CACHE(coffeescript): Ran (#8) on: []');
  run.match("Coffeescript X is 2 Y is 3 FromPackage is 5");

  // We never should have loaded cache from disk, since we only made
  // each compiler once and there were no cache files at this point.
  run.forbid('CACHE(coffeescript): Loaded');

  // Kill the run. Change one coffee file and re-run.
  run.stop();
  s.write("f2.coffee", "share.Y = 'Y is edited'\n");
  run = startRun(s);

  // This time there's a cache to load!
  run.match('CACHE(coffeescript): Loaded /packages/local-pack/p.coffee');
  run.match('CACHE(coffeescript): Loaded /f1.coffee');
  run.match('CACHE(coffeescript): Loaded /f3.coffee');
  // And we only need to re-compiler the changed file, even though we restarted.
  run.match('CACHE(coffeescript): Ran (#1) on: ["/f2.coffee"]');
  run.match('CACHE(coffeescript): Ran (#2) on: []');

  run.match('Coffeescript X is 2 Y is edited FromPackage is 5');

  run.stop();
});

// Tests the actual cache logic used by less and stylus.
['less', 'stylus'].forEach((packageName) => {
  const extension = packageName === 'stylus' ? 'styl' : packageName;

  selftest.define("compiler plugin caching - " + packageName, () => {
    var s = new Sandbox({ fakeMongo: true });

    s.createApp("myapp", "caching-" + packageName);
    s.cd("myapp");
    // Ask them to print out when they build a file (instead of using it from
    // the cache) as well as when they load cache from disk.
    s.set(`METEOR_${ packageName.toUpperCase() }_CACHE_DEBUG`, "t");
    var run = startRun(s);

    const cacheMatch = selftest.markStack((message) => {
      run.match(`CACHE(${ packageName }): ${ message }`);
    });

    // First program built (web.browser) compiles everything.
    cacheMatch('Ran (#1) on: ' + JSON.stringify([
      // Though files in imports directories are compiled, they are marked
      // as lazy so they will not be loaded unless imported.
      "/imports/dotdot." + extension,
      "/subdir/nested-root." + extension,
      "/top." + extension
    ]));
    // There is no render execution in the server program, because it has
    // archMatching:'web'.  We'll see this more clearly when the next call later
    // is "#2" --- we didn't miss a call!
    // App prints this:
    run.match("Hello world");

    // Check that the CSS is what we expect.
    var checkCSS = selftest.markStack((borderStyleMap) => {
      var builtBrowserProgramDir = files.pathJoin(
        s.cwd, '.meteor', 'local', 'build', 'programs', 'web.browser');
      var cssFile = _.find(
        files.readdir(
          files.pathJoin(s.cwd, '.meteor/local/build/programs/web.browser')),
        path => path.match(/\.css$/)
      );
      selftest.expectTrue(cssFile);
      var actual = s.read(
        files.pathJoin('.meteor/local/build/programs/web.browser', cssFile));
      actual = actual.replace(/\s+/g, ' ');  // simplify whitespace
      var expected = _.map(borderStyleMap, (style, className) => {
        return '.' + className + " { border-style: " + style + "; }";
      }).join(' ');
      selftest.expectEqual(actual, expected);
    });
    var expectedBorderStyles = {
      el0: "dashed", el1: "dotted", el2: "solid", el3: "groove", el4: "ridge"};
    checkCSS(expectedBorderStyles);

    // Force a rebuild of the local package without actually changing the
    // preprocessor file in it. This should not require us to render anything.
    s.append("packages/local-pack/package.js", "\n// foo\n");
    cacheMatch('Ran (#2) on: []');
    run.match("Hello world");

    function setVariable(variableName, value) {
      switch (packageName) {
      case 'less':
        return `@${ variableName }: ${ value };\n`;
      case 'stylus':
        return `$${ variableName } = ${ value }\n`;
      }
    }
    function importLine(fileWithoutExtension) {
      switch (packageName) {
      case 'less':
        return `@import "${ fileWithoutExtension }.less";\n`;
      case 'stylus':
        return `@import "${ fileWithoutExtension }.styl"\n`;
      }
    }

    // Writing to a single file only re-renders the root that depends on it.
    s.write('packages/local-pack/p.' + extension,
            setVariable('el4-style', 'inset'));
    expectedBorderStyles.el4 = 'inset';
    cacheMatch(`Ran (#3) on: ["/top.${ extension }"]`);
    run.match("Client modified -- refreshing");
    checkCSS(expectedBorderStyles);

    // This works for changing a root too.
    s.write('subdir/nested-root.' + extension,
            '.el0 { border-style: double; }\n');
    expectedBorderStyles.el0 = 'double';
    cacheMatch(`Ran (#4) on: ["/subdir/nested-root.${ extension }"]`);
    run.match("Client modified -- refreshing");
    checkCSS(expectedBorderStyles);

    // Adding a new root works too.
    s.write('yet-another-root.' + extension,
            '.el6 { border-style: solid; }\n');
    expectedBorderStyles.el6 = 'solid';
    cacheMatch(`Ran (#5) on: ["/yet-another-root.${ extension }"]`);
    run.match("Client modified -- refreshing");
    checkCSS(expectedBorderStyles);

    // We never should have loaded cache from disk, since we only made
    // each compiler once and there were no cache files at this point.
    run.forbid('CACHE(${ packageName }): Loaded');

    // Kill the run. Change one file and re-run.
    run.stop();
    s.write('packages/local-pack/p.' + extension,
            setVariable('el4-style', 'double'));
    expectedBorderStyles.el4 = 'double';
    run = startRun(s);

    // This time there's a cache to load!  Note that for
    // MultiFileCachingCompiler we load all the cache entries, even for the
    // not-up-to-date file 'top', because we only key off of filename, not off
    // of cache key.
    cacheMatch('Loaded {}/subdir/nested-root.' + extension);
    cacheMatch('Loaded {}/top.' + extension);
    cacheMatch('Loaded {}/yet-another-root.' + extension);
    cacheMatch(`Ran (#1) on: ["/top.${ extension }"]`);
    run.match('Hello world');
    checkCSS(expectedBorderStyles);

    s.write('bad-import.' + extension, importLine('/foo/bad'));
    run.match('Errors prevented startup');
    switch (packageName) {
    case 'less':
      run.match('bad-import.less:1: Unknown import: /foo/bad.less');
      break;
    case 'stylus':
      run.match('bad-import.styl: Stylus compiler error: bad-import.styl:1');
      run.match('failed to locate @import file /foo/bad.styl');
      break;
    }
    run.match('Waiting for file change');

    run.stop();
  });
});

// Tests that rebuilding a compiler plugin re-instantiates the source processor,
// but other changes don't.
selftest.define("compiler plugin caching - local plugin", function () {
  var s = new Sandbox({ fakeMongo: true });

  s.createApp("myapp", "local-compiler-plugin");
  s.cd("myapp");

  var run = startRun(s);

  // The compiler gets used the first time...
  run.match("PrintmeCompiler invocation 1");
  // ... and the program runs the generated code.
  run.match("PMC: Print out bar");
  run.match("PMC: Print out foo");

  s.write("quux.printme", "And print out quux");
  // PrintmeCompiler gets reused.
  run.match("PrintmeCompiler invocation 2");
  // And the right output prints out
  run.match("PMC: Print out bar");
  run.match("PMC: Print out foo");
  run.match("PMC: And print out quux");

  // Restart meteor; see that the disk cache gets used.
  run.stop();
  run = startRun(s);
  // Disk cache gets us up to 3.
  run.match("PrintmeCompiler invocation 3");
  // And the right output prints out
  run.match("PMC: Print out bar");
  run.match("PMC: Print out foo");
  run.match("PMC: And print out quux");

  // Edit the compiler itself.
  s.write('packages/local-plugin/plugin.js',
          s.read('packages/local-plugin/plugin.js').replace(/PMC/, 'pmc'));
  // New PrintmeCompiler object, and empty disk cache dir.
  run.match("PrintmeCompiler invocation 1");
  // And the right output prints out (lower case now)
  run.match("pmc: Print out bar");
  run.match("pmc: Print out foo");
  run.match("pmc: And print out quux");

  run.stop();
});

// Test error on duplicate compiler plugins.
selftest.define("compiler plugins - duplicate extension", () => {
  const s = new Sandbox({ fakeMongo: true });

  s.createApp("myapp", "duplicate-compiler-extensions");
  s.cd("myapp");

  let run = startRun(s);
  run.match('Errors prevented startup');
  run.match('conflict: two packages');
  run.match('trying to handle *.myext');

  // Fix it by changing one extension.
  s.write('packages/local-plugin/plugin.js',
          s.read('packages/local-plugin/plugin.js').replace('myext', 'xext'));
  run.match('Modified -- restarting');

  run.stop();
});

// Test error when a source file no longer has an active plugin.
selftest.define("compiler plugins - inactive source", () => {
  const s = new Sandbox({ fakeMongo: true });

  // This app depends on the published package 'glasser:uses-sourcish', and
  // contains a local package 'local-plugin'.
  //
  // glasser:uses-sourcish depends on local-plugin and contains a file
  // 'foo.sourcish'. When glasser:uses-sourcish@0.0.1 was published, a local
  // copy of 'local-plugin' had a plugin which called registerCompiler for the
  // extension '*.sourcish', and so 'foo.sourcish' is in the published isopack
  // as a source file. However, the copy of 'local-plugin' currently in the test
  // app contains no plugins. So we hit this weird error.
  s.createApp('myapp', 'uses-published-package-with-inactive-source');
  s.cd('myapp');

  let run = startRun(s);
  run.match('Errors prevented startup');
  run.match('no plugin found for foo.sourcish in glasser:use-sourcish');
  run.match('none is now');

  run.stop();
});

// Test error when the registerCompiler callback throws.
selftest.define("compiler plugins - compiler throws", () => {
  const s = new Sandbox({ fakeMongo: true });

  s.createApp('myapp', 'compiler-plugin-throws-on-instantiate');
  s.cd('myapp');

  const run = s.run('add', 'local-plugin');
  run.matchErr('Errors while adding packages');
  run.matchErr(
    'While running registerCompiler callback in package local-plugin');
  // XXX This is wrong! The path on disk is packages/local-plugin/plugin.js, but
  // at some point we switched to the servePath which is based on the *plugin*'s
  // "package" name.
  run.matchErr('packages/compilePrintme/plugin.js:5:1: Error in my ' +
               'registerCompiler callback!');
  run.expectExit(1);
});

// Test that compiler plugins can add static assets. Also tests `filenames`
// option to registerCompiler.
selftest.define("compiler plugins - compiler addAsset", () => {
  const s = new Sandbox({ fakeMongo: true });

  s.createApp('myapp', 'compiler-plugin-add-asset');
  s.cd('myapp');

  const run = startRun(s);
  // Test server-side asset.
  run.match("extension is null");  // test getExtension -> null
  run.match("Asset says Print out foo");

  // Test client-side asset.
  const body = getUrl('http://localhost:3000/foo.printme');
  selftest.expectEqual(body, 'Print out foo\n');

  run.stop();
});

// Test that a package can have a single file that is both source code and an
// asset
selftest.define("compiler plugins - addAssets", () => {
  const s = new Sandbox({ fakeMongo: true });

  s.createApp('myapp', 'compiler-plugin-asset-and-source');
  s.cd('myapp');

  const run = startRun(s);

  // Test server-side asset.
  run.match("Printing out my own source code!");

  // Test client-side asset.
  let body = getUrl('http://localhost:3000/packages/' +
    'asset-and-source/asset-and-source.js');
  selftest.expectTrue(body.indexOf('Printing out my own source code!') !== -1);

  // Test that deprecated API still works (added in 1.2.1 in response to people
  // having trouble upgrading to 1.2)
  s.write("packages/asset-and-source/package.js", `Package.describe({
      name: 'asset-and-source',
      version: '0.0.1'
    });

    Package.onUse(function(api) {
      api.addFiles('asset-and-source.js');
      api.addFiles('asset-and-source.js',
        ['client', 'server'], { isAsset: true });
    });
  `);

  // Test server-side asset.
  run.match("Printing out my own source code!");

  // Test client-side asset.
  body = getUrl('http://localhost:3000/packages/' +
    'asset-and-source/asset-and-source.js');
  selftest.expectTrue(body.indexOf('Printing out my own source code!') !== -1);

  // Test error messages for malformed package files
  s.write("packages/asset-and-source/package.js", `Package.describe({
      name: 'asset-and-source',
      version: '0.0.1'
    });

    Package.onUse(function(api) {
      api.addFiles('asset-and-source.js');
      api.addAssets('asset-and-source.js', ['client', 'server']);
      api.addFiles('asset-and-source.js');
    });
  `);

  run.match(/Duplicate source file/);

  s.write("packages/asset-and-source/package.js", `Package.describe({
      name: 'asset-and-source',
      version: '0.0.1'
    });

    Package.onUse(function(api) {
      api.addFiles('asset-and-source.js');
      api.addAssets('asset-and-source.js', ['client', 'server']);
      api.addAssets('asset-and-source.js', ['client', 'server']);
    });
  `);

  run.match(/Duplicate asset file/);

  s.write("packages/asset-and-source/package.js", `Package.describe({
      name: 'asset-and-source',
      version: '0.0.1'
    });

    Package.onUse(function(api) {
      api.addAssets('asset-and-source.js');
    });
  `);

  run.match(/requires a second argument/);

  run.stop();
});


// Log all uncaught errors so they can be printed to the developer through adb

// and logcat with Android or regular log file with iOS.

window.onerror = function (msg, url, line) {

// Cut off the url prefix, the meaningful part always starts at 'www/' in

console.log('Uncaught Error: ' + msg + ':' + line + ':' + url);

replace replace replace replace replace replace replace keep keep

// Log all uncaught errors so they can be printed to the developer.

// But since Android's adb catlog already prints the uncaught exceptions, we

if (! /Android/i.test(navigator.userAgent)) {

var main = require('./main.js');
var path = require('path');
var _ = require('underscore');
var fs = require('fs');
var files = require('./files.js');
var buildmessage = require('./buildmessage.js');
var project = require('./project.js').project;
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 archinfo = require('./archinfo.js');
var tropohouse = require('./tropohouse.js');
var packageCache = require('./package-cache.js');
var packageLoader = require('./package-loader.js');
var PackageSource = require('./package-source.js');
var compiler = require('./compiler.js');
var unipackage = require('./unipackage.js');
var execFileSync = require('./utils.js').execFileSync;
var execFileAsync = require('./utils.js').execFileAsync;

var cordova = exports;

var supportedPlatforms = ['ios', 'android', 'firefoxos'];

var localCordova = path.join(files.getCurrentToolsDir(),
  "scripts", "cordova.sh");

var execFileSyncOrThrow = function (file, args, opts) {
  var process = execFileSync(file, args, opts);
  if (! process.success)
    throw new Error(process.stderr + '\n\n' + process.stdout);
  return process;
};

var getLoadedPackages = _.once(function () {
  var uniload = require('./uniload.js');
  return uniload.load({
    packages: [ 'boilerplate-generator', 'logging' ]
  });
});

var generateCordovaBoilerplate = function (clientDir, options) {
  var clientJsonPath = path.join(clientDir, 'program.json');
  var clientJson = JSON.parse(fs.readFileSync(clientJsonPath, 'utf8'));

  var meteorRelease = project.getMeteorReleaseVersion();
  var Boilerplate = getLoadedPackages()['boilerplate-generator'].Boilerplate;
  var boilerplate = new Boilerplate('web.cordova', clientJson.manifest, {
    urlMapper: function (url) { return url ? url.substr(1) : ''; },
    pathMapper: function (p) { return path.join(clientDir, p); },
    baseDataExtension: {
      includeCordova: true,
      meteorRuntimeConfig: JSON.stringify({
        meteorRelease: meteorRelease,
        ROOT_URL: 'http://' + options.host + ':' + options.port + '/',
        // XXX propagate it from options?
        ROOT_URL_PATH_PREFIX: '',
        DDP_DEFAULT_CONNECTION_URL: 'http://' + options.host + ':' + options.port
      })
    }
  });
  return boilerplate.toHTML();
};

var fetchCordovaPluginFromShaUrl =
  function (urlWithSha, localPluginsDir, pluginName) {
  var pluginPath = path.join(localPluginsDir, pluginName);
  var pluginTarballPath = pluginPath + '.tgz';
  var curlProcess =
    execFileSyncOrThrow('curl', ['-L', urlWithSha, '-o', pluginTarballPath]);

  if (! curlProcess.success)
    throw new Error("Failed to fetch the tarball from " + urlWithSha + ": " +
                    curlProcess.stderr);

  files.mkdir_p(pluginPath);
  var tarProcess = execFileSyncOrThrow('tar',
    ['xf', pluginTarballPath, '-C', pluginPath, '--strip-components=1']);
  if (! tarProcess.success)
    throw new Error("Failed to untar the tarball from " + urlWithSha + ": " +
                    tarProcess.stderr);
  files.rm_recursive(pluginTarballPath);
  return pluginPath;
};

cordova.checkIsValidPlatform = function (name) {
  if (! _.contains(supportedPlatforms, name))
    throw new Error(name + ": no such platform");
};

cordova.checkIsValidPlugin = function (name) {
  var pluginHash = {};
  pluginHash[name.split('@')[0]] = name.split('@')[1];

  // check that every plugin is specifying either an exact constraint or a
  // tarball url with sha
  utils.ensureOnlyExactVersions(pluginHash);
};

// options
//  - debug
var getBundle = function (bundlePath, webArchs, options) {
  var bundler = require(path.join(__dirname, 'bundler.js'));

  var bundleResult = bundler.bundle({
    outputPath: bundlePath,
    buildOptions: {
      minify: ! options.debug,
      arch: archinfo.host(),
      webArchs: webArchs
    }
  });

  if (bundleResult.errors) {
    throw new Error("Errors prevented bundling:\n" +
                    bundleResult.errors.formatMessages());
  }

  return bundleResult;
};

var localPluginsPathFromCordovaPath = function (cordovaPath) {
  return path.join(cordovaPath, 'local-plugins');
};

////////////////////////////////////////////////////////////////////////////////
// ensureCordova
////////////////////////////////////////////////////////////////////////////////

// Creates a Cordova project if necessary.
cordova.ensureCordovaProject = function (localPath, appName) {
  var cordovaPath = path.join(localPath, 'cordova-build');
  var localPluginsPath = localPluginsPathFromCordovaPath(cordovaPath);
  if (! fs.existsSync(cordovaPath)) {
    try {
      var creation = execFileSyncOrThrow(localCordova,
        ['create', path.basename(cordovaPath), 'com.meteor.' + appName, appName.replace(/\s/g, '')],
        { cwd: path.dirname(cordovaPath) });

      // create a folder for storing local plugins
      // XXX cache them there
      files.mkdir_p(localPluginsPath);
    } catch (err) {
      process.stderr.write("Error creating Cordova project: " +
        err.message + "\n");
    }
  }
};

// Ensures that the Cordova platforms are synchronized with the app-level
// platforms.
cordova.ensureCordovaPlatforms = function (localPath) {
  var cordovaPath = path.join(localPath, 'cordova-build');
  var platforms = project.getCordovaPlatforms();
  var platformsList = execFileSyncOrThrow(localCordova, ['platform', 'list'],
                                   { cwd: cordovaPath });

  // eg. ['android 3.5.0', 'ios 3.5.0']
  var platformsStrings = platformsList.stdout.split('\n')[0].match(/Installed platforms: (.*)/)[1].split(', ');
  var installedPlatforms = _.map(platformsStrings, function (s) { return s.split(' ')[0]; });

  _.each(platforms, function (platform) {
    if (! _.contains(installedPlatforms, platform) &&
          _.contains(supportedPlatforms, platform))
      execFileSyncOrThrow(localCordova, ['platform', 'add', platform], { cwd: cordovaPath });
  });

  _.each(installedPlatforms, function (platform) {
    if (! _.contains(platforms, platform) &&
          _.contains(supportedPlatforms, platform))
      execFileSyncOrThrow(localCordova, ['platform', 'rm', platform], { cwd: cordovaPath });
  });

  return true;
};

// Ensures that the Cordova platforms are synchronized with the app-level
// platforms.
// options
//   - packagePlugins: the list of plugins required by packages. If not defined,
//                     we bundle the app to find the required plugins.

cordova.ensureCordovaPlugins = function (localPath, options) {
  options = options || {};
  var plugins = options.packagePlugins;
  if (! plugins) {
    // Bundle to gather the plugin dependencies from packages.
    // XXX slow - perhaps we should only do this lazily
    // XXX code copied from buildCordova
    var bundlePath = path.join(localPath, 'build-tar');
    var webArchName = 'web.cordova';
    plugins =
      getBundle(bundlePath, [webArchName], options).starManifest.cordovaDependencies;
    files.rm_recursive(bundlePath);
  }
  // XXX the project-level cordova plugins deps override the package-level ones
  _.extend(plugins, project.getCordovaPlugins());

  var cordovaPath = path.join(localPath, 'cordova-build');
  var localPluginsPath = localPluginsPathFromCordovaPath(cordovaPath);
  var newSettings = null;

  if (options.settings) {
    newSettings =
      JSON.parse(fs.readFileSync(options.settings, "utf8")).cordova;
  }

  // XXX compare the latest used sha's with the currently required sha's for
  // plugins fetched from a github/tarball url.
  var pluginsOutput = execFileSyncOrThrow(localCordova, ['plugin', 'list'],
                                   { cwd: cordovaPath }).stdout;

  var installedPlugins = {};
  // Check if there are any plugins
  if (! pluginsOutput.match(/No plugins added/)) {
    _.each(pluginsOutput.split('\n'), function (line) {
      line = line.trim();
      if (line === '')
        return;
      var plugin = line.split(' ')[0];
      var version = line.split(' ')[1];
      installedPlugins[plugin] = version;
    });
  }

  var oldSettings;
  var settingsFile = path.join(cordovaPath, 'cordova-settings.json');
  try {
    oldSettings = JSON.parse(
      fs.readFileSync(settingsFile, 'utf8'));
  } catch(err) {
    if (err.code !== 'ENOENT')
      throw err;
    oldSettings = {};
  } finally {
    if (newSettings) {
      fs.writeFileSync(settingsFile, JSON.stringify(newSettings, null, 2),
        'utf8');
    }
  }

  // This block checks to see if we should install or reinstall a plugin.
  _.each(plugins, function (version, name) {
    // no-op if this plugin is already installed
    if (_.has(installedPlugins, name)
        && installedPlugins[name] === version) {

      if (newSettings && newSettings[name] &&
          ! _.isEqual(oldSettings[name], newSettings[name])) {
        // If we have newSettings and they are different, then continue.
      } else {
        return;
      }
    }

    if (_.has(installedPlugins, name))
      execFileSyncOrThrow(localCordova, ['plugin', 'rm', name], { cwd: cordovaPath });

    // XXX do something different for plugins fetched from a url.
    var pluginInstallCommand = version ? name + '@' + version : name;

    if (version && utils.isUrlWithSha(version)) {
      pluginInstallCommand =
        fetchCordovaPluginFromShaUrl(version, localPluginsPath, name);
    }

    var additionalArgs = [];
    // XXX how do we get settings to work now? Do we require settings to be
    // passed every time we add a plugin?
    if (newSettings && newSettings[name]) {
      if (! _.isObject(newSettings[name]))
        throw new Error('Meteor.settings.cordova.' + name + ' is expected to be an object');
      _.each(newSettings[name], function (value, variable) {
        additionalArgs.push('--variable');
        additionalArgs.push(variable + '=' + JSON.stringify(value));
      });
    }
    process.stdout.write('Installing ' + pluginInstallCommand + '\n');
    var execRes = execFileSyncOrThrow(localCordova,
       ['plugin', 'add', pluginInstallCommand].concat(additionalArgs), { cwd: cordovaPath });
    if (! execRes.success)
      throw new Error("Failed to install plugin " + name + ": " + execRes.stderr);
  });

  _.each(installedPlugins, function (version, name) {
    if (! _.has(plugins, name))
      execFileSyncOrThrow(localCordova, ['plugin', 'rm', name], { cwd: cordovaPath });
  });
};

// Build a Cordova project, creating a Cordova project if necessary.
var buildCordova = function (localPath, buildCommand, options) {
  var webArchName = "web.cordova";

  var bundlePath = path.join(localPath, 'build-cordova-temp');
  var programPath = path.join(bundlePath, 'programs');

  var cordovaPath = path.join(localPath, 'cordova-build');
  var wwwPath = path.join(cordovaPath, "www");
  var cordovaProgramPath = path.join(programPath, webArchName);
  var cordovaProgramAppPath = path.join(cordovaProgramPath, 'app');

  var bundle = getBundle(bundlePath, [webArchName], options);

  cordova.ensureCordovaProject(localPath, options.appName);
  cordova.ensureCordovaPlatforms(localPath);
  cordova.ensureCordovaPlugins(localPath, _.extend({}, options, {
    packagePlugins: bundle.starManifest.cordovaDependencies
  }));

  // XXX hack, copy files from app folder one level up
  if (fs.existsSync(cordovaProgramAppPath)) {
    files.cp_r(cordovaProgramAppPath, cordovaProgramPath);
    files.rm_recursive(cordovaProgramAppPath);
  }

  // rewrite the www folder
  files.rm_recursive(wwwPath);
  files.cp_r(cordovaProgramPath, wwwPath);

  // clean up the temporary bundle directory
  files.rm_recursive(bundlePath);

  // generate index.html
  var indexHtml = generateCordovaBoilerplate(wwwPath, options);
  fs.writeFileSync(path.join(wwwPath, 'index.html'), indexHtml, 'utf8');

  var loaderPath = path.join(__dirname, 'client', 'meteor_cordova_loader.js');
  var loaderCode = fs.readFileSync(loaderPath);
  fs.writeFileSync(path.join(wwwPath, 'meteor_cordova_loader.js'), loaderCode);

  // Give the buffer more space as the output of the build is really huge
  execFileSyncOrThrow(localCordova, [buildCommand],
               { cwd: cordovaPath, maxBuffer: 2000*1024 });
};

// checks that every requested platform such as 'android' or 'ios' is already
// added to the project
var checkRequestedPlatforms = function (platforms) {
  platforms = _.uniq(platforms);

  var requestedPlatforms = [];
  // Find the required platforms.
  // ie. ["ios", "android", "ios-device"] will produce ["ios", "android"]
  _.each(platforms, function (platformName) {
    var platform = platformName.split('-')[0];
    if (! _.contains(requestedPlatforms, platform)) {
      requestedPlatforms.push(platform);
    }
  });

  var cordovaPlatforms = project.getCordovaPlatforms();
  _.each(requestedPlatforms, function (platform) {
    if (! _.contains(cordovaPlatforms, platform))
      throw new Error(platform +
        ": platform is not added to the project. Try 'meteor add platform:" +
        platform + "' to add it or 'meteor help add' for help.");
  });
};

// Builds a Cordova project that targets the list of 'platforms'
// options:
//   - appName: the target path of the build
//   - host
//   - port
cordova.buildPlatforms = function (localPath, platforms, options) {
  checkRequestedPlatforms(platforms);
  buildCordova(localPath, 'build', options);
};

cordova.preparePlatforms = function (localPath, platforms, options) {
  checkRequestedPlatforms(platforms);
  buildCordova(localPath, 'prepare', options);
};

// Start the simulator or physical device for a specific platform.
// platformName is of the form ios/ios-device/android/android-device
var execCordovaOnPlatform = function (localPath, platformName) {
  var cordovaPath = path.join(localPath, 'cordova-build');

  // XXX error if an invalid platform
  var platform = platformName.split('-')[0];
  var isDevice = platformName.split('-')[1] === 'device';

  var args = [ 'run',
               isDevice ? '--device' : '--emulator',
               platform ];

  // XXX error if not a Cordova project
  execFileAsync(localCordova, args, { cwd: cordovaPath });
  var Log = getLoadedPackages().logging.Log;

  var androidMapper = function (line) {
    // remove the annoying prefix
    line = line.replace(/^.\/CordovaLog\(\s*\d+\s*\):\s+/, '');
    // remove a part of file url we don't like
    line = line.replace(/^file:\/\/\/android_asset\/www\//, '');
    // filename.js?hashsha1: Line 123 : message goes here
    var parsedLine = line.match(/^([^?]+)\?[a-zA-Z0-9]+: Line (\d+) : (.*)$/);

    if (! parsedLine)
      return Log.format(Log.objFromText(line), { color: true });

    var output = {
      time: new Date,
      level: 'info',
      file: parsedLine[1],
      line: parsedLine[2],
      message: parsedLine[3],
      program: 'android'
    };
    return Log.format(output, {
      metaColor: 'green',
      color: true
    });
  };

  var iosMapper = function (line) {
    // remove the prefix
    line = line.replace(/^\S+\s\S+\s\S+\s/, '');
    return Log.format(Log.objFromText(line, { program: 'ios' }), {
      metaColor: 'cyan',
      color: true
    });
  };

  if (platform === 'ios') {
    var logFilePath =
      path.join(cordovaPath, 'platforms', 'ios', 'cordova', 'console.log');

    // overwrite the file so we don't have to print the old logs
    fs.writeFileSync(logFilePath, '');
    // print the log file
    execFileAsync('tail', ['-f', logFilePath], { lineMapper: iosMapper });
  } else if (platform === 'android') {
    execFileAsync('adb', ['logcat', '-s', 'CordovaLog'], {
      lineMapper: androidMapper,
    });
  }
  return 0;
};

// Start the simulator or physical device for a list of platforms
cordova.runPlatforms = function (localPath, platforms) {
  _.each(platforms, function (platformName) {
    execCordovaOnPlatform(localPath, platformName);
  });
};

// packages - list of strings
cordova.filterPackages = function (packages) {
// We hard-code the 'cordova' and 'platform' namespaces
  var ret = {
    rest: [],
    platforms: [],
    plugins: []
  };

  _.each(packages, function (p) {
    var namespace = p.split(':')[0];
    var name = p.split(':').slice(1).join(':');
    if (namespace === 'cordova') {
      ret.plugins.push(name);
    } else if (namespace === 'platform') {
      ret.platforms.push(name);
    } else
      ret.rest.push(p); // leave it the same
  });
  return ret;
};


var queue = [];
var loaded = document.readyState === "loaded" ||
  document.readyState == "complete";

var ready = function() {
  loaded = true;
  while (queue.length)
    (queue.shift())();
};

if (document.addEventListener) {
  document.addEventListener('DOMContentLoaded', ready, false);
  window.addEventListener('load', ready, false);
} else {
  document.attachEvent('onreadystatechange', function () {
    if (document.readyState === "complete")
      ready();
  });
  window.attachEvent('load', ready);
}

Meteor.startup = function (cb) {
  var doScroll = !document.addEventListener &&
    document.documentElement.doScroll;

  if (!doScroll || window !== top) {
    if (loaded)
      cb();
    else
      queue.push(cb);
  } else {
    try { doScroll('left'); }
    catch (e) {
      setTimeout(function() { Meteor.startup(cb); }, 50);
      return;
    };
    cb();
  }
};


line = line.replace(/^.\/CordovaLog\(\s*\d+\s*\):\s+/, '');

// remove a part of file url we don't like

line = line.replace(/^file:\/\/\/android_asset\/www\//, '');

// filename.js?hashsha1: Line 123 : message goes here

return Log.format(Log.objFromText(line), { color: true });

keep keep keep keep replace keep keep keep keep keep


Package.describe({
  summary: "A package for translating old bundles into stars",
  version: "1.0.0"
});

Package.on_use(function (api) {
  api.use(['dev-bundle-fetcher']);
  api.export('StarTranslator');
  api.add_files(['translator.js'], 'server');
});

Npm.depends({ncp: "0.4.2"});


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

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

var nextSlot = 0;

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

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

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

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

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

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

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

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

    return ret;
  }
});

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

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

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

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

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

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


var output = {
time: new Date,
level: 'info',
file: parsedLine[1],
line: parsedLine[2],
message: parsedLine[3],
program: 'android'
};
return Log.format(output, {
metaColor: 'green',

keep keep keep keep keep replace replace keep keep keep keep

line: parsedLine[3],
message: parsedLine[4],